【前言】
最近又被流控问题缠住了,不懂其中原理的总会以界面上显示为flow来说事。那界面上连接、通道的flow状态的显示到底是怎么回事?显示为flow是否就是影响或阻塞生产者的消息发送了?本文来对此进行总结说明。
【flow状态】
要了解web界面上为什么会显示flow状态,首先得搞清楚flow状态的定义是怎样的,也就是说如何判断连接、通道当前是flow状态。
连接、通道(其实还包括队列)的flow状态主要由credit_flow算法决定(详细请戳《RabbitMQ——流控》)。
当连接或通道进程的信用值用完了,下游进程还未赋予更多的信用值时,在连接或通道的进程中,会在进程字典的credit_blocked字段里记录被下游的某个进程block,同时在进程字典的credit_blocked_at字段中记录block的时间。
如果进程字典中credit_blocked中有记录被下游block的进程,那么当前连接、通道的状态肯定是flow状态。
如果进程字典中credit_blocked中没有记录任何block的进程,则取credit_blocked_at字段中的值并与当前时间进行比较。
如果小于1秒,则仍旧还是flow状态,大于1s,则不认为是flow状态了。
也就是,只有在过去1s内未曾记录被任何下游进程block,才是非flow状态。
说完了flow状态的定义,那么连接、通道什么时候进行状态的检测与改变呢?
每个连接、通道的进程,在被创建后,会启动一个定时器,定时收集自身的各种状态信息,然后保存到 channel_metrics、connection_metrics等ets表中。
定时器的时间是由配置项collect_statistics_interval决定的,默认时间为5秒。
也就是每5秒,连接、通道进程收集自身的状态并写入对应的ets表中。
说到这里,聪明的你是不是已经猜到,界面上flow状态的显示是通过向rabbitmq发送http请求,http请求的响应处理从这个ets表里取数据并返回由前端展示了。
实际情况差不多也就是这样的。启用rabbitmq_management插件后,在rabbitmq内部会启动一个metrics的收集进程,定时从上面说的channel_metrics、connection_metrics等表中采集数据,重新存放到channel_state、connection_state等表中。http请求的响应处理则是从这些表中取出对应的信息作为结果返回。
总的来说,界面上的状态最终还是根据连接、通道进程自身的汇报状态来决定的,如果连接、通道状态采集的时间较长,并且采集的时间点上又恰好出现了block的情况,那么界面上一段时间内都将持续显示为flow。
【flow状态的影响】
web界面上通道或连接显示为flow状态就一定会阻塞生产者的发送吗?
答案当然是否定的,官方文档中有一段是这么描述的。
显示为flow状态的连接和一般连接没有什么不同,生产者依旧可以持续进行消息的发送,直到rabbitmq的tcp接收缓冲区满了,并出现tcp的零窗口才会出现无法发送的情况。与此同时,rabbitmq暂时是不会从tcp的接收缓冲区中拷贝数据进行处理。这也就是描述中显示为flow状态是通知系统管理员,生产者的速度被限制的正确理解。
另外,有些文章中会提到,rabbitmq节点的内存到达一定水位,或者磁盘空间的使用到达一定程度也会导致连接的流控。
简单来说我们可以这么理解,但更准确的解释是,出现这种情况的时候,连接或通道的状态是显示为blocked的,同时rabbitmq会给生产者发送一条basic.block的amqp信令。当然很多客户端的实现中并没有处理这个信令,还是继续给rabbitmq发送消息,这种情况下看着是和flow差不多,但在内存或磁盘空间没有得到释放之前,rabbitmq是不会处理生产者的消息的。
【总结】
本文总结了连接、通道flow状态的产生,web中的显示,以及显示flow状态对生产者的影响。至于连接、通道flow状态的产生,其原因有很多,包括可能的内存达到高水位、磁盘IO有瓶颈,又或者erlang虚拟机内部调度器,进程的gc等等。有兴趣的可以进一步深入探索。