最近在使用镜像队列的过程中遇到了一些坑,通过阅读相关源码,大量的测试,不敢说对其中的原理掌握得非常透彻, 但基本能分析定位问题的原因,并且能自圆其说。这里整理总结下, 方便后续的回溯。欢迎大家交流指正。
【问题现象】
在镜像队列模式下,镜像队列所在的节点全部停止然后同时启动,启动后可能会出现一些奇怪的现象,比如:
-
WEB上部分队列为stopped状态
-
部分队列并没有slave
-
队列看着是存在的,但消息无法投递到该队列中
部分现象如下图所示:
其实所有这些现象最终本质是同一个问题,下面重点讲述镜像队列的相关原理并对该问题进行分析。
【准备知识】
在分析问题前,先讲解镜像队列相关的信息进行铺垫。
1、队列进程
懂一点erlang知识的都知道,erlang应用程序内部由成千上万个进程组成,这些进程大体可以分为两类,一类是工作者进程;一类是监督者进程。工作进程负责处理业务逻辑;监督者进程负责启动工作者进程,并对其进行监控,在必要的时候重启工作者进程,比如工作者进程异常退出时。
在rabbitmq中,队列对应的进程(rabbit_amqqueue_process)就属于工作者进程,每个这样的进程负责一个队列消息的处理;每个工作者进程也都有一个自己的监督者进程(rabbit_amqqueue_sup);每个监督者进程又共同有一个监督者进程(rabbit_amqqueue_sup_sup),这些进程构成了逻辑上的父子关系,rabbitmq在启动时会按照其父子关系依次将进程创建启动。具体层级关系如下图所示:
对于镜像队列模式,除了队列进程外,还有用于队列master/slave协调选主的coordinator进程,以及用于广播消息生产消费操作的gm进程。
生产者发送的消息、消费者消费的消息都由队列的master进程处理,master进程对消息的处理通过gm广播给其他节点的gm进程,其他节点的gm进程收到消息后再转发给对应的slave进程,slave进程收到消息后进行相应的处理保证与master的同步。(这里的master进程,slave进程就是前面图中的rabbit_amqqueue_process)
2、队列对应的内存数据表
在rabbitmq内部,维护了一个队列信息的表,记录了队列名称,队列master进程PID,slave进程PID等等信息,具体的表结构为:
这里主要关注的字段有
-
name:队列的名称。
-
pid:队列master进程的PID,生产者发送的消息,通过routing-key匹配找到对应的队列后,在查找该表找到队列master进程的PID,然后将消息发送给master进程。
-
slave_pids:队列slave进程PID集合,按照加入先后顺序进行排序
-
sync_slave_pids:已完成消息同步的slave进程PID集合
-
gm_pids:gm进程PID集合
-
state:队列的状态,live、crashed、stopped其中一个
队列进程在启动过程中会动态更新这几个字段的值,并在集群中实时同步。
注:实际上,在rabbitmq内部为队列维护了两张表,一个是记录持久化队列信息的rabbit_durable_queue表,该表中的数据会定期刷到磁盘中,便于重启后的恢复;一个是rabbit_queue表,这个表是内存态的,记录了所有队列的全部信息。
rabbit_durable_queue表会记录PID这个可能动态变化的字段信息,用于重启后判断创建持久化队列的master进程。而其他可能动态变化的字段信息例如队列的slave进程pid集合,gm进程pid集合则不会记录;rabbit_queue表则记录队列的全部信息,运行过程中也都是通过查找该表找队列的master进程、slave进程,gm进程。
【启动流程】
1、队列master的启动流程
1)完成自身初始化,并在rabbit_queue表中插入记录,填充相关字段,例如pid字段。
2)启动coordinator、gm进程,并增量更新rabbit_queue表数据中的gm_pids字段信息。
3)根据镜像配置规则,在合适的节点上创建队列的镜像,即执行队列slave创建启动的相关流程。
4)如果slave创建成功,则进行消息同步,然后处理生产者发送消息,消费者消费消息。
2、队列slave启动流程
1)完成自身初始化,创建gm进程,并在rabbit_queue表对应记录中增量更新slave_pids、gm_pids字段
2)与master进行消息的同步
3)同步完成后,在rabbit_queue表对应记录中增量更新sync_slave_pids字段。
这里有几点需要说明:
1)两个节点同时启动,怎么判断谁将是队列的master,谁是队列的slave?
答案是查rabbit_queue表。