RabbitMQ性能缓慢的分析-RabbitMQ限流策略Flow

2021-02-07  本文已影响0人  人要不怕开水烫

公司一个生产项目,重度使用了多层混合MQ来作为异构系统和微服务间的数据流转,大概结构如下图:

1、C++模块负责产生原始数据,并推入ActiveMQ中(为什么不统一使用RabbitMQ,因为原有代码牵涉较多,不宜乱动,相信很多人有这样的经历吧);

2、JAVA模块1作为ActiveMQ的消费者,将数据接收后进行清洗后,再作为生产者发到RabbitMQ中;

3、下级JAVA微服务作为RabbitMQ的消费者,接收清洗后的数据后再做各自的业务处理。

RabbitMQ中主要使用了Fanout Exchange和Direct Exchange两种交换机,为什么使用Fanout Exchange,是因为部分数据需要平行分发给多个处理模块各自处理。

MQ数据走向图

在平时的运行中,系统运行平稳,源头C++模块推送的数据在2000-3000条/秒之间,下级各平台也能正常的消化处理。

但在某一天的某个时候,ActiveMQ开始出现数据堆积,下层消费者无法像往常的速率处理消息。我们检查了JAVA模块1的运行日志,发现与ActiveMQ的连接是正常的,接收到的数据也是正常的,观察CPU、内存、线程的情况也是正常的,但就是无法消费ActiveMQ中的消息。那再向下检查,发现往RabbitMQ推送的速率明显变慢了,但是与RabbitMQ有关系的生产者模块和消费者模块,没有发现程序有出错。在没有办法的情况下,只有尝试将JAVA模块1的部分实例进行了重启,结果发现在重启后一段时间,消费速度有短暂恢复,但很快就无济于事。

这下无计可施,只有翻看RabbitMQ的管理UI,从中仔细翻查哪里有问题。先是在连接管理界面翻查了一下,发现有部分连接的状态(state)变成了flow(以下图片是后续重抓的,flow状态已经不存在了),由此怀疑是被限流了,再对应具体IP和服务地址核对,果真就是这几个服务被限流,导致了生产者无法达到有效的速率发送消息,并因此导致上游的ActiveMQ消费者变慢。在JAVA模块1中,对应ActiveMQ的Listener,在接收消息后,会立即清洗并直接推送到RabbitMQ中,而在推送中被限流就让消息无法处理,进一步无法被ACK,导致消费变得缓慢,就产生了上游ActiveMQ的消息堆积。

队列连接管理界面

下图是我从官网上截的部分图,就是中间的flow,代表连接已经被限流了。

限流状态示意图

在发现问题所在后,我们再向下梳理,去RabbitMQ的UI中的Queue界面看一下,这里有个重要的参数:Consumer utilisation(消费者利用率,我不知道自己翻译是否正确,暂且这样定义吧) 需要关注。在排序后,发现有一个队列的消费者利用率不高,只在50%上下。再看它绑定的交换机,就是上面JAVA模块1发消息的Fanout Exchange,宾果,就是它了。 在无法马上优化代码处理的情况下,实时增加了两个实例,消费者数量从50变成了150后,消费者利用率慢慢从50%攀升到了100%,同时也观察到上游的ActiveMQ消息开始在不断的减少。由此,算是解决了生产者性能缓慢的问题,生产环境又变得平稳正常了。

队列管理界面

但是这中间的机制还不算是非常清楚,所以在优化完代码后,也去官网了仔细查了相关的资料,算是勉强弄清楚了RabbitMQ的限流机制。RabbitMQ的流控制机制不仅在连接上,还会在通道和队列上都可以处于流控制状态,我遇到这个问题是下游队列消费者导致,所以上游生产者连接被限流。根据官方论坛上的员工回复,大概可能以下几点会造成流控制:

1、队列正在全力工作,没有什么空闲时间

2、队列实际上还有与消费有关的工作(这一句我还没能理解他的意思,可能指队列还在等待消费者,或是消费者本身还在消费中?)

3、消息被消费的速率没有达到它们被生产的速率的110%

而大部分的情况下,都是消费者本身消费性能较低,产生了较低的消费者利用率,从而导致被限流。因此,我们可以着重从消费者的处理逻辑上下手,找出可以优化的地方去解决,提高消费者本身的处理性能。

但这里也有一个办法,直接启用自动ACK,或是异步处理,这样就能先在短时间解决问题,但是又将面临数据可能会丢失的问题了。

当然RabbitMQ不止因为消费者引起原因限流,还会当RabbitMQ主机的CPU、内存、硬盘I/O或空间消耗(这里主要硬盘资源主要是持久化的时候)达到一个阈值后,也会引起限流。所以在RabbitMQ的管理界面发现有相应的硬件资源消耗得差不多的时候,就应该引起注意了,避免因为硬件资源导致限流,整个性能迅速下降。而稍注意的是,这里关于内存的使用,目前官方默认内存流控阀值设置为0.4,即RabbitMQ 进程内存占用到系统总内存的百分之四十生产者会发生流控。 

由于 RabbitMQ 是基于Erlang 开发,RabbitMQ 将每个队列设计为一个 Erlang 进程,Erlang 进程GC也是采用分代策略,当新老生代一起参与Major GC时,Erlang虚拟机会新开内存,根据root set将存活的对象拷贝至新空间,这个过程会造成新老内存空间同时存在,极端情况下,一个队列可能短期内需要两倍的内存占用量,所以内存流控阀值设置为0.4相对是一个比较安全的值,设置太高,有可能系统内存被全部占用导致系统进程 kill RabbitMQ进程,设置过低导致内存使用率不高。

资源监控图
上一篇下一篇

猜你喜欢

热点阅读