RabbitMQ可以对内存和磁盘的使用量设置阈值,当到达阈值后,生产者将被阻塞,直到对应项恢复正常。除了这两个阈值,从2.8.0版本开始,RabbitMQ还引入了流控(Flow Control)机制来确保稳定性。流控机制是用来避免消息的发送频率过快而导致服务器难以支撑的情形。内存和磁盘告警相当于全局的流控,一旦触发会阻塞集群中所有的Connection,而流控是针对单个Connection的,可以称之为Per-Connection Flow Control或者 Internal Flow Control。
流控的原理
Erlang进程之间并不共享内存(binary类型的除外),而是通过消息传递来通信,每个进程都有自己的进程邮箱(mailbox)。默认情况下,Erlang并没有对进程邮箱的大小进行限制,所以当有大量消息持续发往某个进程时,会导致该进程邮箱过大,最终内存溢出并崩溃。在RabbitMQ中,如果生产者持续高速发送,而消费者消费速度较低时,如果没有流控,很快就会使内部进程邮箱的大小达到内存阈值。
RabbitMQ使用了一种基于信用正算法(credit-based algorithm)的流控机制来限制发送消息的速率以解决这个问题。它通过监控各个进程的进程邮箱,当某个进程负载过高而来不及处理消息时,这个进程的邮箱就会开始慢慢堆积消息,当堆积到一定量时,就会阻塞而不接收上游的新消息。从而慢慢的,上游的进程邮箱也会开始慢慢的堆积消息。当堆积到一定量的时候也会阻塞而停止接收上游的消息,最后就会使负责网络数据包接收的进程阻塞而暂停接收新数据。以下图为例:
进程A接收消息并转发至进程B,进程B接收消息并转发至进程C。每个进程中都有一对关于收发消息的credit值。以进程B为例,{{credit_from,C},value}表示能发多少条消息给C,每发一条消息就将该值减1,当为0时,进程B不再往进程C发送消息,也不再接收进程A的消息。{{credit_to,A},value}表示再接收多少条消息就向进程A发送增加credit值的通知,进程A接收到通知后就增加{{credit_from,B},value}所对应的值,这样进程A就能持续发送消息。当上游发送速率高于下游接收速率时,credit值就会逐渐被消耗光,这时进程就会被阻塞,阻塞的情况会一直传递到最上游。当上游进程收到来自下游的增加credit值的通知时,若此时上游处于阻塞状态则解除最上游的阻塞状态,开始接收更上游的进程消息,一个一个传导最终能够解除最上游的阻塞状态。由此可见,基于信用证的流控机制最终将消息发送进程的发送速率限制在消息处理进程的处理能力范围之内。
一个连接触发流控时会处于“flow”状态,也就意味着这个Connection的状态每秒在blocked和unblocked之间来回切换数次,这样可以将消息发送的速率控制在服务器能够支撑的范围之内,可以通过rabbitmqctl list_connections命令或者Web管理界面查看Connection的状态。
处于flow状态的Connection和处于running状态并没有什么不同,这个状态只是告诉管理员相应的发送速率受限了,而对于客户端而言,它看到的只是服务器的带宽要比正常情况下小一些。
流控机制不只是作用于Connection,同样作用于信道和队列。从Connection到Channel,再到队列,最后是消息持久化存储形成一个完整的流控链,对于处于整个流控链中的任意进程,只要该进程阻塞,上游的进程必定阻塞。也就是说,如果某个进程达到性能瓶颈,必然会导致上游所有的进程被阻塞。所以我们可以利用流控机制的这个特点找出瓶颈之所在,处理消息的几个关键进程及其对应的顺序如下图:
其中各个进程如下所述:
❤ rabbit_reader:Connection的处理进程,负责接收、解析AMQP协议数据包等;
❤ rabbit_channel:Channel的处理进程,负责处理AMQP协议的各种办法、进行路由解析等;
❤ rabbit_amqqueue_process:队列的处理进程,负责实现队列的所有逻辑;
❤ rabbit_msg_store:负责实现消息的持久化;
当某个Connection处于flow状态,但这个Connection中没有一个Channel处于flow状态时,这就意味着Connection中有一个或者多个Channel出现了性能瓶颈,某些Channel进程的运作(比如处理路由逻辑)会使得服务器CPU的负载过高而导致出现此种情形。尤其是在发送大量较小的非持久化消息时,此种情形最易显现。
当某个Connection处于flow状态,并且这个Connection中也有若干个Channel处于flow状态,但没有任何一个对应的队列处于flow状态,这就意味着有一个或者多个队列出现了性能瓶颈。这可能是由于将消息存入队列的过程中引起服务器CPU负载过高,或者将队列中的消息存入磁盘的过程中引起服务器I/O负载过高而引起此种情形。尤其是在发送大量较小的持久化消息时,此种情形最易显现。
当某个Connection处于flow状态,同时这个Connection中也有若干Channel处于flow状态,并且也有若干队列处于flow状态,这就意味着在消息持久化的时候出现了性能瓶颈。在将队列中的消息存入磁盘的过程中引起服务器I/O负载过高而引起的此种情形。尤其是在发送大量较大的持久化消息时,此种情形最易显现。