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

 

 

1、本章学习内容

 

  • 客户端请求-应答
  • 最近最少使用队列
  • 心跳机制
  • 面向服务的队列
  • 基于磁盘(脱机)队列
  • 主从备份服务
  • 无中间件的请求-应答

 

2、我的个人理解

2.1 什么是可靠性

容易出现的故障的类型如下,那么可靠性,就是在很大程度上不希望出现下面的这些情形

  • 应用程序代码是最大的故障来源。程序会崩溃或中止,停止对数据来源的响应,或是响应得太慢,耗尽内存等。
  • 系统代码,如使用ZMQ编写的中间件,也会意外中止。系统代码应该要比应用程序代码更为可靠,但毕竟也有可能崩溃。特别是当系统代码与速度过慢的客户端交互时,很容易耗尽内存。
  • 消息队列溢出,典型的情况是系统代码中没有对慢客户端做积极的处理,任由消息队列溢出。
  • 网络临时中断,造成消息丢失。这类错误ZMQ应用程序是无法及时发现的,因为ZMQ会自动进行重连。
  • 硬件系统崩溃,导致所有进程中止。
  • 网络会出现特殊情形的中断,如交换机的某个端口发生故障,导致部分网络无法访问。
  • 数据中心可能遭受雷击、地震、火灾、电压过载、冷却系统失效等。

我们所希望的结果就是,即使出现上面的这些情况,也能够系统正常的运行下去。

 

本章主要讲解请求-应答模式中的可靠性设计,其他模式将在后续章节中讲解。

最基本的请求应答模式是REQ客户端发送一个同步的请求至REP服务端,这种模式的可靠性很低。如果服务端在处理请求时中止,那客户端会永远处于等待状态。

相比TCP协议,ZMQ提供了自动重连机制、消息分发的负载均衡等。但是,在真实环境中这也是不够的。唯一可以完全信任基本请求-应答模式的应用场景是同一进程的两个线程之间进行通信,没有网络问题或服务器失效的情况。

但是,只要稍加修饰,这种基本的请求-应答模式就能很好地在现实环境中工作了。我喜欢将其称为“海盗”模式。

粗略地讲,客户端连接服务端有三种方式,每种方式都需要不同的可靠性设计:

  • 多个客户端直接和单个服务端进行通信。使用场景:只有一个单点服务器,所有客户端都需要和它通信。需处理的故障:服务器崩溃和重启;网络连接中断。

  • 多个客户端和单个队列装置通信,该装置将请求分发给多个服务端。使用场景:任务分发。需处理的故障:worker崩溃和重启,死循环,过载;队列装置崩溃和重启;网络中断。

  • 多个客户端直接和多个服务端通信,无中间件。使用场景:类似域名解析的分布式服务。需处理的故障:服务端崩溃和重启,死循环,过载;网络连接中断。

以上每种设计都必须有所取舍,很多时候会混合使用。下面我们详细说明。

 

2.2 几种可靠性设计的实现与理解

2.2.0 说在前面的话

   在本章,实际上有很多代码的情况实现起来相对要麻烦很多,博主时间有限,因此,仅仅提出思想。 博主等到后面实际涉及到关于可靠性设计的时候,将会针对自己设计的系统进行仔细的可靠性的设计。

2.2.1 客户端的可靠性设计(懒惰海盗模式-lazy pirate)

在接收应答时,我们不进行同步等待,而是做以下操作:

  • 对REQ套接字进行轮询,当消息抵达时才进行接收;
  • 请求超时后重发消息,循环多次;
  • 若仍无消息,则结束当前事务。

 

1

 

2.2.2 基本的可靠队列 简单海盗模式

对于上面的过程,我们进行拓展:

 

在所有的海盗模式中,worker是无状态的,或者说存在着一个我们所不知道的公共状态,如共享数据库。队列装置的存在意味着worker可以在client毫不知情的情况下随意进出。一个worker死亡后,会有另一个worker接替它的工作。这种拓扑结果非常简洁,但唯一的缺点是队列装置本身会难以维护,可能造成单点故障。

在第三章中,队列装置的基本算法是最近最少使用算法。那么,如果worker死亡或阻塞,我们需要做些什么?答案是很少很少。我们已经在client中加入了重试的机制,所以,使用基本的LRU队列就可以运作得很好了。这种做法也符合ZMQ的逻辑,所以我们可以通过在点对点交互中插入一个简单的队列装置来扩展它:

 

实际上,在本来的这种模式的基础上,就已经有了很好的可靠性了,我们需要做的实际上很少。

 

 

2.2.3 健壮的可靠队列 (偏执海盗模式)

 

简单海盗队列”模式工作得非常好,主要是因为它只是两个现有模式的结合体。不过,它也有一些缺点:

  • 该模式无法处理队列的崩溃或重启。client会进行重试,但worker不会重启。虽然ZMQ会自动重连worker的套接字,但对于新启动的队列装置来说,由于worker并没有发送“已就绪”的消息,所以它相当于是不存在的。为了解决这一问题,我们需要从队列发送心跳给worker,这样worker就能知道队列是否已经死亡。

  • 队列没有检测worker是否已经死亡,所以当worker在处于空闲状态时死亡,队列装置只有在发送了某个请求之后才会将该worker从队列中移除。这时,client什么都不能做,只能等待。这不是一个致命的问题,但是依然是不够好的。所以,我们需要从worker发送心跳给队列装置,从而让队列得知worker什么时候消亡。

为了加入心跳管理,判断我们的worker是不是有可能出现了什么不可预知的问题

之前我们使用REQ套接字作为worker的套接字类型,但在偏执海盗模式中我们会改用DEALER套接字,从而使我们能够任意地发送和接受消息,而不是像REQ套接字那样必须完成发送-接受循环。而DEALER的缺点是我们必须自己管理消息信封。如果你不知道信封是什么,那请阅读第三章。

 

3
 

引用书中 的考虑“

当我在写偏执海盗模式的示例时,大约花了五个小时的时间来协调队列至worker的心跳,剩下的请求-应答链路只花了约10分钟的时间。心跳机制在可靠性上带来的益处有时还不及它所引发的问题。使用过程中很有可能会产生“虚假故障”的情况,即节点误认为他们已失去连接,因为心跳没有正确地发送。

在理解和实施心跳时,需要考虑以下几点:

  • 心跳不是一种请求-应答,它们异步地在节点之间传递,任一节点都可以通过它来判断对方已经死亡,并中止通信。

  • 如果某个节点使用持久套接字(即设定了套接字标识),意味着发送给它的心跳可能会堆砌,并在重连后一起收到。所以说,worker不应该使用持久套接字。示例代码使用持久套接字是为了便于调试,而且代码中使用了随机的套接字标识,避免重用之前的标识。

  • 使用过程中,应先让心跳工作起来,再进行后面的消息处理。你需要保证启动任一节点后,心跳都能正确地执行。停止并重启他们,模拟冻结、崩溃等情况来进行测试。

  • 当你的主循环使用了zmq_poll(),则应该使用另一个计时器来触发心跳。不要使用主循环来控制心跳的发送,这回导致过量地发送心跳(阻塞网络),或是发送得太少(导致节点断开)。zhelpers包提供了s_clock()函数返回当前系统时间戳,单位是毫秒,可以用它来控制心跳的发送间隔。

  • 主循环应该使用心跳间隔作为超时时间。显然不能使用无超时时间的设置,而短于心跳间隔也只是浪费循环次数而已。

  • 使用简单的追踪方式来进行追踪,如直接输出至控制台。这里有一些追踪的窍门:使用zmsg()函数打印套接字内容;对消息进行编号,判断是否会有间隔。

  • 在真实的应用程序中,心跳必须是可以配置的,并能和节点共同商定。有些节点需要高频心跳,如10毫秒,另一些节点则可能只需要30秒发送一次心跳即可。

  • 如果你要对不同的节点发送不同频率的心跳,那么poll的超时时间应设置为最短的心跳间隔。

  • 也许你会想要用一个单独的套接字来处理心跳,这看起来很棒,可以将同步的请求-应答和异步的心跳隔离开来。但是,这个主意并不好,原因有几点:首先、发送数据时其实是不需要发送心跳的;其次、套接字可能会因为网络问题而阻塞,你需要设法知道用于发送数据的套接字停止响应的原因是死亡了还是过于繁忙而已,这样你就需要对这个套接字进行心跳。最后,处理两个套接字要比处理一个复杂得多。

  • 我们没有设置client至队列的心跳,因为这太过复杂了,而且没有太大价值

 

实际上,在实现的过程中就将会遇到其提及到的这些问题:

如何正确处理心跳信号实际上, 对于不同系统环境是不同的应用的方案

2.2.4 面向服务的可靠队列  管家模式

pp 协议

http://rfc.zeromq.org/spec:7

管家模式协议(MDP)在扩展PPP协议时引入了一个有趣的特性:client发送的每一个请求都有一个“服务名称”,而worker在像队列装置注册时需要告知自己的服务类型。MDP的优势在于它来源于现实编程,协议简单,且容易提升。

 

在实施管家模式之前,我们需要为client和worker编写一个框架。如果程序员可以通过简单的API来实现这种模式,那就没有必要让他们去了解管家模式的协议内容和实现方法了。 所以,我们第一个协议(即管家模式协议)定义了分布式架构中节点是如何互相交互的,第二个协议则要定义应用程序应该如何通过框架来使用这一协议。 管家模式有两个端点,客户端和服务端。因为我们要为client和worker都撰写框架,所以就需要提供两套API。以下是用简单的面向对象方法设计的client端API雏形,使用的是C语言的ZFL library

 

 

几点说明:

  • API是单线程的,所以说worker不会再后台发送心跳,而这也是我们所期望的:如果worker应用程序停止了,心跳就会跟着中止,代理便会停止向该worker发送新的请求。

  • wroker API没有做回退算法的设置,因为这里不值得使用这一复杂的机制。

  • API没有提供任何报错机制,如果出现问题,它会直接报断言(或异常,依语言而定)。这一做法对实验性的编程是有用的,这样可以立刻看到执行结果。但在真实编程环境中,API应该足够健壮,合适地处理非法消息。

也许你会问,worker API为什么要关闭它的套接字并新开一个呢?特别是ZMQ是有重连机制的,能够在节点归来后进行重连。我们可以回顾一下简单海盗模式中的worker,以及偏执海盗模式中的worker来加以理解。ZMQ确实会进行自动重连,但如果代理死亡并重连,worker并不会重新进行注册。这个问题有两种解决方案:一是我们这里用到的较为简便的方案,即当worker判断代理已经死亡时,关闭它的套接字并重头来过;另一个方案是当代理收到未知worker的心跳时要求该worker对其提供的服务类型进行注册,这样一来就需要在协议中说明这一规则。

 

 

2.2.5 异步管家 模式

上文那种实现管家模式的方法比较简单,client还是简单海盗模式中的,仅仅是用API重写了一下。我在测试机上运行了程序,处理10万条请求大约需要14秒的时间,这和代码也有一些关系,因为复制消息帧的时间浪费了CPU处理时间。但真正的问题在于,我们总是逐个循环进行处理(round-trip),即发送-接收-发送-接收……ZMQ内部禁用了TCP发包优化算法(Nagle's algorithm),但逐个处理循环还是比较浪费。

理论归理论,还是需要由实践来检验。我们用一个简单的测试程序来看看逐个处理循环是否真的耗时。这个测试程序会发送一组消息,第一次它发一条收一条,第二次则一起发送再一起接收。两次结果应该是一样的,但速度截然不同。

 

由于worker获得消息需要通过LRU队列机制,所以并不能做到完全的异步。但是,worker越多其效果也会越好。在我的测试机上,当worker的数量达到8个时,速度就不再提升了——四核处理器只能做这么多。但是,我们仍然获得了近四倍的速度提升,而改造过程只有几分钟而已。此外,代理其实还没有进行优化,它仍会复制消息,而没有实现零拷贝。不过,我们已经做到每秒处理2.5万次请求-应答,已经很不错了。

当然,异步的管家模式也并不完美,有一个显著的缺点:它无法从代理的崩溃中恢复。可以看到mdcliapi2的代码中并没有恢复连接的代码,重新连接需要有以下几点作为前提:

  • 每个请求都做了编号,每次应答也含有相应的编号,这就需要修改协议,明确定义;
  • client的API需要保留并跟踪所有已发送、但仍未收到应答的请求;
  • 如果代理发生崩溃,client会重发所有消息。

可以看到,高可靠性往往和复杂度成正比,值得在管家模式中应用这一机制吗?这就要看应用场景了。如果是一个名称查询服务,每次会话会调用一次,那不需要应用这一机制;如果是一个位于前端的网页服务,有数千个客户端相连,那可能就需要了。

 

2.2.6 服务查询

   

现在,我们已经有了一个面向服务的代理了,但是我们无法得知代理是否提供了某项特定服务。如果请求失败,那当然就表示该项服务目前不可用,但具体原因是什么呢?所以,如果能够询问代理“echo服务正在运行吗?”,那将会很有用处。最明显的方法是在MDP/Client协议中添加一种命令,客户端可以询问代理某项服务是否可用。但是,MDP/Client最大的优点在于简单,如果添加了服务查询的功能就太过复杂了。

另一种方案是学电子邮件的处理方式,将失败的请求重新返回。但是这同样会增加复杂度,因为我们需要鉴别收到的消息是一个应答还是被退回的请求。

让我们用之前的方式,在MDP的基础上建立新的机制,而不是改变它。服务定位本身也是一项服务,我们还可以提供类似于“禁用某服务”、“提供服务数据”等其他服务。我们需要的是一个能够扩展协议但又不会影响协议本身的机制。

这样就诞生了一个小巧的RFC - MMI(管家接口)的应用层,建立在MDP协议之上:http://rfc.zeromq.org/spec:8 。我们在代理中其实已经加以实现了,不知你是否已经注意到。下面的代码演示了如何使用这项服务查询功能:

代理在运行时会检查请求的服务名称,自行处理那些mmi.开头的服务,而不转发给worker。你可以在不开启worker的情况下运行以上代码,可以看到程序是报告200还是404。MMI在示例程序代理中的实现是很简单的,比如,当某个worker消亡时,该服务仍然标记为可用。实践中,代理应该在一定间隔后清除那些没有worker的服务。

 

2.3 幂等服务

幂等是指能够安全地重复执行某项操作。如,看钟是幂等的,但借钱给别人老婆就不是了。有些客户端至服务端的通信是幂等的,但有些则不是。幂等的通信示例有:

  • 无状态的任务分配,即管道模式中服务端是无状态的worker,它的处理结果是根据客户端的请求状态生成的,因此可以重复处理相同的请求;
  • 命名服务中将逻辑地址转化成实际绑定或连接的端点,可以重复查询多次,因此也是幂等的。

非幂等的通信示例有:

  • 日志服务,我们不会希望相同的日志内容被记录多次;
  • 任何会对下游节点有影响的服务,如该服务会向下游节点发送信息,若收到相同的请求,那下游节点收到的信息就是重复的;
  • 当服务修改了某些共享的数据,且没有进行幂等方面的设置。如某项服务对银行账户进行了借操作(debit),这一定是非幂等的。

如果应用程序提供的服务是非幂等的,那就需要考虑它究竟是在哪个阶段崩溃的。如果程序在空闲或处理请求的过程中崩溃,那不会有什么问题。我们可以使用数据库中的事务机制来保证借贷操作是同时发生的。如果应用程序在发送请求的时候崩溃了,那就会有问题,因为对于该程序来说,它已经完成了工作。

如果在返回应答的过程中网络阻塞了,客户端会认为请求发送失败,并进行重发,这样服务端会再一次执行相同的请求。这不是我们想要的结果。

常用的解决方法是在服务端检测并拒绝重复的请求,这就需要:

  • 客户端为每个请求加注唯一的标识,包括客户端标识和消息标识;
  • 服务端在发送应答时使用客户端标识和消息标识作为键,保存应答内容;
  • 当服务端发现收到的请求已在应答哈希表中存在,它会跳过该次请求,直接返回应答内容。

 

 

2.4 脱机可靠性(巨人模式)

 

当你意识到管家模式是一种非常可靠的消息代理时,你可能会想要使用磁盘做一下消息中转,从而进一步提升可靠性。这种方式虽然在很多企业级消息系统中应用,但我还是有些反对的,原因有:

  • 我们可以看到,懒惰海盗模式的client可以工作得非常好,能够在多种架构中运行。唯一的问题是它会假设worker是无状态的,且提供的服务是幂等的。但这个问题我们可以通过其他方式解决,而不是添加磁盘。

  • 添加磁盘会带来新的问题,需要额外的管理和维护费用。海盗模式的最大优点就是简单明了,不会崩溃。如果你还是担心硬件会出问题,可以改用点对点的通信模式,这会在本章最后一节讲到。

虽然有以上原因,但还是有一个合理的场景可以用到磁盘中转的——异步脱机网络。海盗模式有一个问题,那就是client发送请求后会一直等待应答。如果client和worker并不是长连接(可以拿电子邮箱做个类比),我们就无法在client和worker之间建立一个无状态的网络,因此需要将这种状态保存起来。

于是我们就有了巨人模式,该模式下会将消息写到磁盘中,确保不会丢失。当我们进行服务查询时,会转向巨人这一层进行。巨人是建立在管家之上的,而不是改写了MDP协议。这样做的好处是我们可以在一个特定的worker中实现这种可靠性,而不用去增加代理的逻辑。

  • 实现更为简单;
    • 代理用一种语言编写,worker使用另一种语言编写;
    • 可以自由升级这种模式。

唯一的缺点是,代理和磁盘之间会有一层额外的联系,不过这也是值得的。

我们有很多方法来实现一种持久化的请求-应答架构,而目标当然是越简单越好。我能想到的最简单的方式是提供一种成为“巨人”的代理服务,它不会影响现有worker的工作,若client想要立即得到应答,它可以和代理进行通信;如果它不是那么着急,那就可以和巨人通信:“嗨,巨人,麻烦帮我处理下这个请求,我去买些菜。”

 

5

这样一来,巨人就既是worker又是client。client和巨人之间的对话一般是:

  • Client: 请帮我处理这个请求。巨人:好的。
  • Client: 有要给我的应答吗?巨人:有的。(或者没有)
  • Client: OK,你可以释放那个请求了,工作已经完成。巨人:好的。

巨人和代理之间的对话一般是:

  • 巨人:嗨,代理程序,你这里有个叫echo的服务吗?代理:恩,好像有。
  • 巨人:嗨,echo服务,请帮我处理一下这个请求。Echo: 好了,这是应答。
  • 巨人:谢谢!

你可以想象一些发生故障的情形,看看上述模式是否能解决?worker在处理请求的时候崩溃,巨人会不断地重新发送请求;应答在传输过程中丢失了,巨人也会重试;如果请求已经处理,但client没有得到应答,那它会再次询问巨人;如果巨人在处理请求或进行应答的时候崩溃了,客户端会进行重试;只要请求是被保存在磁盘上的,那它就不会丢失。

这个机制中,握手的过程是比较漫长的,但client可以使用异步的管家模式,一次发送多个请求,并一起等待应答。

我们需要一种方法,让client会去请求应答内容。不同的client会访问到相同的服务,且client是来去自由的,有着不同的标识。一个简单、合理、安全的解决方案是:

  • 当巨人收到请求时,它会为每个请求生成唯一的编号(UUID),并将这个编号返回给client;
  • client在请求应答内容时需要提供这个编号。

这样一来client就需要负责将UUID安全地保存起来,不过这就省去了验证的过程。有其他方案吗?我们可以使用持久化的套接字,即显式声明客户端的套接字标识。然而,这会造成管理上的麻烦,而且万一两个client的套接字标识相同,那会引来无穷的麻烦。

在我们开始制定一个新的协议之前,我们先思考一下client如何和巨人通信。一种方案是提供一种服务,配合三个不同的命令;另一种方案则更为简单,提供三种独立的服务:

  • titanic.request - 保存一个请求,并返回UUID
  • titanic.reply - 根据UUID获取应答内容
  • titanic.close - 确认某个请求已被正确地处理

我们需要创建一个多线程的worker,正如我们之前用ZMQ进行多线程编程一样,很简单。但是,在我们开始编写代码之前,先讲巨人模式的一些定义写下来:http://rfc.zeromq.org/spec:9 。我们称之为“巨人服务协议”,或TSP。

 

几点说明:

  • 我们使用MMI协议去向代理询问某项服务是否可用,这一点和MDP中的逻辑一致;
  • 我们使用inproc(进程内)协议建立主循环和titanic.request服务间的联系,保存新的请求信息。这样可以避免主循环不断扫描磁盘目录,读取所有请求文件,并按照时间日期排序。

这个示例程序不应关注它的性能(一定会非常糟糕,虽然我没有测试过),而是应该看到它是如何提供一种可靠的通信模式的。你可以测试一下,打开代理、巨人、worker和client,使用-v参数显示跟踪信息,然后随意地开关代理、巨人、或worker(client不能关闭),可以看到所有的请求都能获得应答。

如果你想在真实环境中使用巨人模式,你肯定会问怎样才能让速度快起来。以下是我的做法:

  • 使用一个磁盘文件保存所有数据。操作系统处理大文件的效率要比处理许多小文件来的高。
  • 使用一种循环的机制来组织该磁盘文件的结构,这样新的请求可以被连续地写入这个文件。单个线程在全速写入磁盘时的效率是比较高的。
  • 将索引保存在内存中,可以在启动程序时重建这个索引。这样做可以节省磁盘缓存,让索引安全地保存在磁盘上。你需要用到fsync的机制来保存每一条数据;或者可以等待几毫秒,如果不怕丢失上千条数据的话。
  • 如果条件允许,应选择使用固态硬盘;
  • 提前分配该磁盘文件的空间,或者将每次分配的空间调大一些,这样可以避免磁盘碎片的产生,并保证读写是连续的。

另外,我不建议将消息保存在数据库中,甚至不建议交给那些所谓的高速键值缓存,它们比起一个磁盘文件要来得昂贵。

如果你想让巨人模式变得更为可靠,你可以将请求复制到另一台服务器上,这样就不需要担心主程序遭到核武器袭击了。

如果你想让巨人模式变得更为快速,但可以牺牲一些可靠性,那你可以将请求和应答都保存在内存中。这样做可以让该服务作为脱机网络运行,不过若巨人服务本身崩溃了,我也无能为力。

 

2.5 高可靠对称节点 双子星模式

2.5.1 基本思想

双子星模式由Pieter Hintjens和Martin Sustrik设计,应用在iMatix的OpenAMQ服务器中。它的设计理念是:

  • 提供一种简明的高可靠性解决方案;
  • 易于理解和使用;
  • 能够进行可靠的故障切换。

6

详细要求

双子星模式可以非常简单,但能工作得很出色。事实上,这里的实现方法已经历经三个版本了,之前的版本都过于复杂,想要做太多的事情,因而被我们抛弃。我们需要的只是最基本的功能,能够提供易理解、易开发、高可靠的解决方法就可以了。

以下是该架构的详细需求:

  • 需要用到双子星模式的故障是:系统遭受灾难性的打击,如硬件崩溃、火灾、意外等。对于其他常规的服务器故障,可以用更简单的方法。
  • 故障恢复时间应该在60秒以内,理想情况下应该在10秒以内;
  • 故障恢复(failover)应该是自动完成的,而系统还原(recover)则是由人工完成的。我们希望应用程序能够在发生故障时自动从主机切换到备机,但不希望在问题解决之前自动切换回主机,因为这很有可能让主机再次崩溃。
  • 程序的逻辑应该尽量简单,易于使用,最好能封装在API中;
  • 需要提供一个明确的指示,哪台主机正在提供服务,以避免“精神分裂”的症状,即两台服务器都认为自己是主机;
  • 两台服务器的启动顺序不应该有限制;
  • 启动或关闭主从机时不需要更改客户端的配置,但有可能会中断连接;
  • 管理员需要能够同时监控两台机器;
  • 两台机器之间必须有专用的高速网络连接,必须能使用特定IP进行路由。

我们做如下架假设:

  • 单台备机能够提供足够的保障,不需要再进行其他备份机制;
  • 主从机应该都能够提供完整的服务,承载相同的压力,不需要进行负载均衡;
  • 预算中允许有这样一台长时间闲置的备机。

双子星模式不会用到:

  • 多台备机,或在主从机之间进行负载均衡。该模式中的备机将一直处于空闲状态,只有主机发生问题时才会工作;
  • 处理持久化的消息或事务。我们假设所连接的网络是不可靠的(或不可信的)。
  • 自动搜索网络。双子星模式是手工配置的,他们知道对方的存在,应用程序则知道双子星的存在。
  • 主从机之间状态的同步。所有服务端的状态必须能由应用程序进行重建。

以下是双子星模式中的几个术语:

  • 主机 - 通常情况下作为master的机器;
  • 备机 - 通常情况下作为slave的机器,只有当主机从网络中消失时,备机才会切换成master状态,接收所有的应用程序请求;
  • master - 双子星模式中接收应用程序请求的机器;同一时刻只有一台master;
  • slave - 当master消失时用以顶替的机器。

配置双子星模式的步骤:

  1. 让主机知道备机的位置;
  2. 让备机知道主机的位置;
  3. 调整故障恢复时间,两台机器的配置必须相同。

比较重要的配置是应让两台机器间隔多久检查一次对方的状态,以及多长时间后采取行动。在我们的示例中,故障恢复时间设置为2000毫秒,超过这个时间备机就会代替主机的位置。但若你将主机的服务包裹在一个shell脚本中进行重启,就需要延长这个时间,否则备机可能在主机恢复连接的过程中转换成master。

要让客户端应用程序和双子星模式配合,你需要做的是:

  1. 知道两台服务器的地址;
  2. 尝试连接主机,若失败则连接备机;
  3. 检测失效的连接,一般使用心跳机制;
  4. 尝试重连主机,然后再连接备机,其间的间隔应比服务器故障恢复时间长;
  5. 重建服务器端需要的所有状态数据;
  6. 如果要保证可靠性,应重发故障期间的消息。

这不是件容易的事,所以我们一般会将其封装成一个API,供程序员使用。

双子星模式的主要限制有:

  • 服务端进程不能涉及到一个以上的双子星对称节点;
  • 主机只能有一个备机;
  • 当备机处于slave状态时,它不会处理任何请求;
  • 备机必须能够承受所有的应用程序请求;
  • 故障恢复时间不能在运行时调整;
  • 客户端应用程序需要做一些重连的工作。

2.5.2 防止精神分裂

精神分裂”症状指的是一个集群中的不同部分同时认为自己是master,从而停止对对方的检测。双子星模式中的算法会降低这种症状的发生几率:主备机在决定自己是否为master时会检测自身是否收到了应用程序的请求,以及对方是否已经从网络中消失。

但在某些情况下,双子星模式也会发生精神分裂。比如说,主备机被配置在两幢大楼里,每幢大楼的局域网中又分布了一些应用程序。这样,当两幢大楼的网络通信被阻断,双子星模式的主备机就会分别在两幢大楼里接受和处理请求。

为了防止精神分裂,我们必须让主备机使用专用的网络进行连接,最简单的方法当然是用一根双绞线将他们相连。

我们不能将双子星部署在两个不同的岛屿上,为各自岛屿的应用程序服务。这种情况下,我们会使用诸如联邦模式的机制进行可靠性设计。

最好但最夸张的做法是,将两台机器之间的连接和应用程序的连接完全隔离开来,甚至是使用不同的网卡,而不仅仅是不同的端口。这样做也是为了日后排查错误时更为明确。

 

 

 

 

 

 

2.6 无中间件的可靠性(自由者模式)

 

 

 

巴拉巴拉,参考原文,我到现在实际上,也没有完全理解作者写后面这个作用,实际上应当是归咎于自己没有实际的经验以及刚入门,随后,等待自己的系统搭建起来后,再进行详细讨论。

 

 

 

 

 

 

 

 

 

 

 

 

 

ZeroMQ是一种在时代提供极速消息通信ZeroMQ是一个开源的、高性能的消息传递,它能够以非常低的延迟和高吞吐量进行快速的消息传输。 ZeroMQ的设计理念是简单易用,同时具备灵活性和可扩展性。它提供了简单而灵活的API,允许开发人员使用不同的通信模式和拓扑结构来构建自己的通信系统。 ZeroMQ的架构基于Socket通信模型,它提供了各种编程语言的绑定,包括C、C++、Python、Java等。这意味着开发人员可以使用自己熟悉的编程语言来开发使用ZeroMQ的应用程序。 ZeroMQ支持多种消息传输模式,包括点对点通信、发布-订阅模式、请求-回应模式和路由模式。开发人员可以根据具体的需求选择最合适的模式来进行消息通信ZeroMQ的特点之一是其高性能。它通过使用高效的消息队列机制,最大限度地减少了消息传输的延迟。同时,ZeroMQ还支持并发处理和多线程操作,可以在多核系统充分利用计算资源,提高系统的吞吐量和性能。 另一个重要的特点是ZeroMQ的可扩展性。它的架构允许开发人员构建分布式系统,并通过添加更多的节点来扩展系统的规模和容量。ZeroMQ还提供了一套高级的路由和负载均衡机制,使得开发人员可以轻松地构建高可用性和高可伸缩性的系统。 综上所述,ZeroMQ是一个在时代非常有用的极速消息通信。它简单易用、性能高效、可扩展性强,可以满足各种复杂的消息通信需求。无论是构建实时数据处理系统、构建高并发的网络服务,还是构建分布式应用程序,ZeroMQ都是一个值得考虑的选择。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

没有水杯和雨伞的工科男

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值