阿里巴巴是个快乐的青年
分类: 云计算
2013-02-16 22:53:02
一、高可用队列
如果RabbitMQ broker由单节点组成,那么该节点失败将会导致停机、服务临时不可用和消息丢失(尤其是非持久化队列上的非持久化消息)。你可以发布所有消息到持久化队列,但即使这样也可能会丢失,因为在消息被发送或者被写入并刷新到磁盘也有一定的时间间隔。使用是一种确认客户端知道哪些消息已经写入磁盘的方式,但即使如此,你也不希望忍受停机,或者因节点失败而导致的服务不可用,或者因每条消息都写入磁盘而导致的性能下降。
你可以使用RabbitMQ节点来构造RabbitMQ broker。这样就能做到单点失效但整体服务可用,但是需要注意的是:exchange和bind幸存下来,而queue和message却悲剧了,因为queue和它的内容正好驻留在失效节点上,因此,失效节点将导致其queue不可用。
可以使用active/passive节点对,这样当active节点失效时,passive节点就会被提升而取代失效节点。active/passive节点对也可以在集群中使用。这种方法能确保节点失败能被很快检测到并得以恢复,这也是passive节点需要花很长时间才能启动甚至启动失败的原因。最好的结果是临时不可用队列在失败节点上。
为了解决这一系列问题,active/active高可用队列横空出世。通过将队列镜像到集群内其它节点来达到高可用。这样当集群内某个节点失败,队列会自动切换到镜像队列而继续工作。这种解决方案也需要RabbitMQ集群,这也就意味着不能解决分区容忍问题,因此,不推荐在广域网中使用。
二、镜像队列特性
正常情况下,每个镜像队列中有一个master和多个slave,每个在不同节点上。slave运用操作同步master以维持相同的状态。master的所有操作除了publish外都会广播到slave。因此,客户端从镜像队列消费消息实际上是从master消费的。
slave如果失败,除了统计外只有很少一部分工作要做:master依然是master,没有客户端需要任何操作或者知道slave失败了。
如果master失败了,则其中一个slave必须被提升。此时,会发生如下情况:
(1)其中一个slave被提升为master。被提升的slave是最旧的slave。就其本身而言,最旧的slave与master状态最一致。然而,需要注意的是message仅仅保持在master上,因此也将丢失。
(2)slave认为所有之前的消费者都已经意外断开了。因此,其重新发布所有已经传递给客户端的消息,然后等待ack。这包括客户端已经回复ack的消息:要么ack在到达master之前丢失,要么丢失在master广播给slave时。无论哪种情况,新的master都没有选择余地,除了重新发布它认为没有收到ack的所有消息。
(3)客户端从镜像队列消费时支持扩展,客户端会收到通知告知它们从镜像队列的订阅已经意外被取消了。此时,它们需要重新从队列消费。发送这个通知是因为告知丢失master的客户端是必要的:否则客户端可能会继续发送过时消息的ack(失效的master),或者会看到同样的消息但是由新的master发送。当然,客户端连接到失效的节点并发现连接失败,则需要重连到集群内一个存活的节点。
(4)由于重新发布消息,客户端重新从队列消费,其需要意识到随后的消息之前已经收到过了。
随着选择的slave变成master,没有消息发布到在这期间失效的镜像队列:消息发布到镜像队列是直接发布到master和所有slave。因此master失效,消息继续被发送到slave,包括被提升为master的slave。
相似地,客户端使用发布的消息将仍被正确确认,尽管在消息被发布到消息发布者收到确认之间master(或者任何slave)失效。因此从发布者的角度来看,发布到一个镜像队列不同于发布到任何其它类型的队列。消费者需要意识到其需要根据收到的来重新从镜像队列消费。
如果从镜像度列(noAck=true)消费(也就是客户端不发送ack),则消息可能会丢失。这不同于标准:broker认为ack一发送给消费者(noACk=true),客户端就会突然断开连接,ack可能永远不会被收到。对于镜像队列,master可能死掉了,消息可能永远不会被那些客户端收到,并且消息不会被新的master重新发布。因为可能客户端连接到幸存节点,在这种事件发生时很有用。当然,在实践中,如果你关心不丢失消息,则建议你设置noAck=false。
1、 发布者确认和事务
镜像队列支持和。从语义上来看,确认和事务在所有镜像队列上都生效。所以一个事务仅仅返回给所有镜像队列的一个客户端,同样地,确认只会被所有镜像队列的一个发布者收到。
三、不同步的slave
一个节点可以在任何时候加入一个集群。当一个节点加入一个集群时,队列可能在新的节点上添加一个slave,这取决于队列的配置。此时,新的slave将是空的:其不包含任何队列的内容,目前没有同步协议。slave将收到发布给队列的新消息,消息将被添加到镜像队列的结尾。
可以用rabbitmqctl或者管理插件来决定从哪个slave同步数据:
rabbitmqctl list_queues name slave_pids synchronised_slave_pids
四、启动和停止节点
如果你停止包含镜像队列的RabbitMQ master节点,某个节点上的某个slave将会提升为master。如果你继续停止节点,将会出现镜像队列没有slave:其仅仅在一个节点上存在,该节点是其master。如果镜像队列声明为持久化的,则如果最后剩下的节点关机,队列中的持久化消息将会出现在重新启动的节点上。通常,你重启其它节点,如果其之前是镜像队列的一部分,则其会重新加入镜像队列。
然而,当前没有方法能让slave知道它的队列是否已经从master分离而不得不重新加入(这种情况在网络分区中发生)。当slave重新加入镜像队列时,其扔掉任何已经持久化的本地内容而启动一个空的。它的行为如同一个新节点加入集群。
五、配置镜像
队列通过来使能镜像。策略能在任何时刻改变;它有效创建一个非镜像队列,然后使它镜像到后面的节点(反之亦然)。非镜像队列和镜像队列之间是有区别的,前者缺乏额外的镜像基础设施,没有任何slave,因此会运行得更快。
为了使队列称为镜像,你将会创建一个策略来匹配队列,设置策略key ha-mode和 ha-params(可选)。下面表格说明这些key的选项:
ha-mode |
Ha-params |
结果 |
all |
(absent) |
队列镜像到集群内所有节点。当新节点加入集群时,队列将被镜像到那个节点。 |
exactly |
count |
队列镜像到集群内指定数量的节点。如果集群内节点数少于此数,则队列镜像到集群内所有节点。如果集群内节点数多于此数,而且一个包含镜像的节点停止,则新的镜像将不会在另外一个节点上创建。(这阻止队列在集群内发生迁移。) |
nodes |
node names |
队列镜像到指定节点。如果任何指定节点不在集群中,都不会产生错误。当队列声明时,如果没有任何指定节点在线,则队列会被创建在发起声明的客户端所连接的节点上。 |
表1 key选项
无论什么时候,队列的HA策略发生改变,它将尽力保持其已经存在的镜像直到其符合新的策略。
1、“nodes”策略和迁移master
需要注意的是设置和修改一个“nodes”策略将不会引起已经存在的master离开,尽管你让其离开。比如:如果一个队列在{A},并且你给它一个节点策略告知它在{B C},它将会在{A B C}。如果节点A那时失败或者停机了,那个节点上的镜像将不回来且队列将继续保持在{B C}。
2、一些例子
队列名称以“ha.”开头的队列,其策略镜像到集群内所有节点:
rabbitmqctl |
rabbitmqctl set_policy ha-all "^ha\." '{"ha-mode":"all"}' |
rabbitmqctl (Windows) |
rabbitmqctl set_policy ha-all "^ha\." "{""ha-mode"":""all""}" |
HTTP API |
PUT /api/policies/%2f/ha-all {"pattern":"^ha\.", "definition":{"ha-mode":"all"}} |
Web UI |
· Navigate to Admin > Policies > Add / update a policy. · Enter "ha-all" next to Name, "^ha\." next to Pattern, and "ha-mode" = "all" in the first line next to Policy. · Click Add policy. |
队列名称以“two.”开头的队列,其策略镜像到集群内任何两个节点:
rabbitmqctl |
rabbitmqctl set_policy ha-two "^two\." \ '{"ha-mode":"exactly","ha-params":2}' |
rabbitmqctl (Windows) |
rabbitmqctl set_policy ha-two "^two\." ^ "{""ha-mode"":""exactly"",""ha-params"":2}" |
HTTP API |
PUT /api/policies/%2f/ha-two {"pattern":"^two\.", "definition":{"ha-mode":"exactly", "ha-params":2}} |
Web UI |
· Navigate to Admin > Policies > Add / update a policy. · Enter "ha-two" next to Name and "^two\." next to Pattern. · Enter "ha-mode" = "exactly" in the first line next to Policy, then "ha-params" = 2 in the second line, and set the type on the second line to "Number". · Click Add policy. |