ZeroMQ云时代极速消息通信库--阅读笔记-第五章

1、本章学习内容--高级发布-订阅模式

 

  • 处理慢订阅者(自杀的蜗牛模式)
  • 高速订阅者(黑箱模式)
  • 构建一个共享键值缓存(克隆模式)

 

2、我的个人理解

2.1  检测慢订阅者

如何处理慢订阅者?最好的方法当然是让订阅者高效起来,不过这需要额外的工作。以下是一些处理慢订阅者的方法:

  • 在发布者中贮存消息。这是Gmail的做法,如果过去的几小时里没有阅读邮件的话,它会把邮件保存起来。但在高吞吐量的应用中,发布者堆积消息往往会导致内存溢出,最终崩溃。特别是当同是有多个订阅者时,或者无法用磁盘来做一个缓冲,情况就会变得更为复杂。

  • 在订阅者中贮存消息。这种做法要好的多,其实ZMQ默认的行为就是这样的。如果非得有一个人会因为内存溢出而崩溃,那也只会是订阅者,而非发布者,这挺公平的。然而,这种做法只对瞬间消息量很大的应用才合理,订阅者只是一时处理不过来,但最终会赶上进度。但是,这还是没有解决订阅者速度过慢的问题。

  • 暂停发送消息。这也是Gmail的做法,当我的邮箱容量超过7.554GB时,新的邮件就会被Gmail拒收或丢弃。这种做法对发布者来说很有益,ZMQ中若设置了阈值(HWM),其默认行为也就是这样的。但是,我们仍不能解决慢订阅者的问题,我们只是让消息变得断断续续而已。

  • 断开与满订阅者的连接。这是hotmail的做法,如果连续两周没有登录,它就会断开,这也是为什么我正在使用第十五个hotmail邮箱。不过这种方案在ZMQ里是行不通的,因为对于发布者而言,订阅者是不可见的,无法做相应处理。

看来没有一种经典的方式可以满足我们的需求,所以我们就要进行创新了。我们可以让订阅者自杀,而不仅仅是断开连接。这就是“自杀的蜗牛”模式。当订阅者发现自身运行得过慢时(对于慢速的定义应该是一个配置项,当达到这个标准时就大声地喊出来吧,让程序员知道),它会哀嚎一声,然后自杀。

订阅者如何检测自身速度过慢呢?一种方式是为消息进行编号,并在发布者端设置阈值。当订阅者发现消息编号不连续时,它就知道事情不对劲了。这里的阈值就是订阅者自杀的值。

这种方案有两个问题:一、如果我们连接的多个发布者,我们要如何为消息进行编号呢?解决方法是为每一个发布者设定一个唯一的编号,作为消息编号的一部分。二、如果订阅者使用ZMQ_SUBSRIBE选项对消息进行了过滤,那么我们精心设计的消息编号机制就毫无用处了。

有些情形不会进行消息的过滤,所以消息编号还是行得通的。不过更为普遍的解决方案是,发布者为消息标注时间戳,当订阅者收到消息时会检测这个时间戳,如果其差别达到某一个值,就发出警报并自杀。

当订阅者有自身的客户端或服务协议,需要保证最大延迟时间时,自杀的蜗牛模式会很合适。撤销一个订阅者也许并不是最周全的方案,但至少不会引发后续的问题。如果订阅者收到了过时的消息,那可能会对数据造成进一步的破坏,而且很难被发现。

2.2高速订阅者(黑箱模式)

 

上设置一个发布者,获取价格信息,并发送给一组订阅者。如果我们有很多订阅者,我们可以使用TCP。如果订阅者到达一定的量,那我们就应该使用可靠的广播协议,如pgm。

假设我们的发布者每秒产生10万条100个字节的消息。在剔除了不需要的市场信息后,这个比率还是比较合理的。现在我们需要记录一天的数据(8小时约有250GB),再将其传入一个模拟网络,即一组订阅者。虽然10万条数据对ZMQ来说很容易处理,但我们需要更高的速度。

假设我们有多台机器,一台做发布者,其他的做订阅者。这些机器都是8核的,发布者那台有12核。

在我们开始发布消息时,有两点需要注意:

  1. 即便只是处理很少的数据,订阅者仍有可能跟不上发布者的速度;
  2. 当处理到6M/s的数据量时,发布者和订阅者都有可能达到极限。

首先,我们需要将订阅者设计为一种多线程的处理程序,这样我们就能在一个线程中读取消息,使用其他线程来处理消息。一般来说,我们对每种消息的处理方式都是不同的。这样一来,订阅者可以对收到的消息进行一次过滤,如根据头信息来判别。当消息满足某些条件,订阅者会将消息交给worker处理。用ZMQ的语言来说,订阅者会将消息转发给worker来处理。

这样一来,订阅者看上去就像是一个队列装置,我们可以用各种方式去连接队列装置和worker。如我们建立单向的通信,每个worker都是相同的,可以使用PUSH和PULL套接字,分发的工作就交给ZMQ吧。这是最简单也是最快速的方式:

 

ZeroMQ云时代极速消息通信库--阅读笔记-第五章

订阅者和发布者之间的通信使用TCP或PGM协议,订阅者和worker的通信由于是在同一个进程中完成的,所以使用inproc协议。

 

 

但是者存在问题,一个线程的问题   分成多进程就可以解决类似这样的问题,充分利用硬件!

 

很多高性能产品使用的方案是分片,就是将工作量拆分成独立并行的流。如,一半的专题数据由一个流媒体传输,另一半由另一个流媒体传输。我们可以建立更多的流媒体,但如果CPU核心数不变,那就没有必要了。 让我们看看如何将工作量分片为两个流:

 

ZeroMQ云时代极速消息通信库--阅读笔记-第五章

 

要让两个流全速工作,需要这样配置ZMQ:

  • 使用两个I/O线程,而不是一个;
  • 使用两个独立的网络接口;
  • 每个I/O线程绑定至一个网络接口;
  • 两个订阅者线程,分别绑定至一个核心;
  • 使用两个SUB套接字;
  • 剩余的核心供worker使用;
  • worker线程同时绑定至两个订阅者线程的PUSH套接字。

创建的线程数量应和CPU核心数一致,如果我们建立的线程数量超过核心数,那其处理速度只会减少。另外,开放多个I/O线程也是没有必要的。

 

 

 

2.3共享键值缓存(克隆模式)#

发布-订阅模式和无线电广播有些类似,在你收听之前发送的消息你将无从得知,收到消息的多少又会取决于你的接收能力。让人吃惊的是,对于那些追求完美的工程师来说,这种机器恰恰符合他们的需求,且广为传播,成为现实生活中分发消息的最佳机制。想想非死不可、推特、BBS新闻、体育新闻等应用就知道了。

但是,在很多情形下,可靠的发布-订阅模式同样是有价值的。正如我们讨论请求-应答模式一样,我们会根据“故障”来定义“可靠性”,下面几项便是发布-订阅模式中可能发生的故障:

  • 订阅者连接太慢,因此没有收到发布者最初发送的消息;
  • 订阅者速度太慢,同样会丢失消息;
  • 订阅者可能会断开,其间的消息也会丢失。

还有一些情况我们碰到的比较少,但不是没有:

  • 订阅者崩溃、重启,从而丢失了所有已收到的消息;
  • 订阅者处理消息的速度过慢,导致消息在队列中堆砌并溢出;
  • 因网络过载而丢失消息(特别是PGM协议下的连接);
  • 网速过慢,消息在发布者处溢出,从而崩溃。

其实还会有其他出错的情况,只是以上这些在现实应用中是比较典型的。

我们已经有方法解决上面的某些问题了,比如对于慢速订阅者可以使用自杀的蜗牛模式。但是,对于其他的问题,我们最后能有一个可复用的框架来编写可靠的发布-订阅模式。

难点在于,我们并不知道目标应用程序会怎样处理这些数据。它们会进行过滤、只处理一部分消息吗?它们是否会将消息记录起来供日后使用?它们是否会将消息转发给其下的worker进行处理?需要考虑的情况实在太多了,每种情况都有其所谓的可靠性。

所以,我们将问题抽象出来,供多种应用程序使用。这种抽象应用我们称之为共享的键值缓存,它的功能是通过唯一的键名存储二进制数据块。

不要将这个抽象应用和分布式哈希表混淆起来,它是用来解决节点在分布式网络中相连接的问题的;也不要和分布式键值表混淆,它更像是一个NoSQL数据库。我们要建立的应用是将内存中的状态可靠地传递给一组客户端,它要做到的是:

  • 客户端可以随时加入网络,并获得服务端当前的状态;
  • 任何客户端都可以改变键值缓存(插入、更新、删除);
  • 将这种变化以最短的延迟可靠地传达给所有的客户端;
  • 能够处理大量的客户端,成百上千。

克隆模式的要点在于客户端会反过来和服务端进行通信,这在简单的发布-订阅模式中并不常见。所以我这里使用“服务端”、“客户端”而不是“发布者”、“订阅者”这两个词。我们会使用发布-订阅模式作为核心消息模式,不过还需要夹杂其他模式。

 

 

 

接下来  书中对其讲了很多很多~

https://github.com/Scottars/zguide-cn/blob/master/chapter5.md

具体请自己参考好了。

 

 

 

2.4  自己的话

 有的时候很难让自己沉浸这样复杂的情况,尤其是在没有需求的情况下,因此,这个时候,我们只能大致浏览书中的思想,当自己的项目搭建起来的时候,再回炉重造!!!(扎心了)