RocketMq消息生产消费简图

1.生产消息分为:  同步,异步,单向发送。 建议使用异步回调的方式,注意这里的retryTimesWhenSendFailed是指生产者连接到broker的通信失败,连接成功后,因为broker的page cache busy,导致的生产失败,rocketmq本身不会重试(可参考DefaultMQProducerImpl.sendKernelImpl查看哪些异常是重试的)。如有必要,需要程序自行重试。 生产消息broker的处理图中是简化了的,其实broker接到消息后会先放一个SendThreadPoolQueue中,然后有一个线程不断拉取追加到MappedFile中。这里还涉及到一个快速失败,即SendThreadPoolQueue.size>10000,或者消息在SendThreadPoolQueue中存放时间过长(队头元素超过200ms),则执行快速失败逻辑。

2.如果开启trasientStorePoolEnable,RocketMQ将单独创建一个MappedByteBuffer内存缓存池,用来临时存储数据,数据先写入该内存映射中,然后由commit线程定时将数据从该内存复制到与目的物理文件(commitLog)对应的内存映射(MappedFile)中。RocketMQ引入该机制主要的原因是提供一种内存锁定,将当前堆外内存一直锁定在内存中,避免被进程将内存交换到磁盘。

3.ReputMessageService轮询CommitLog将新增的消息派发到ConsumerQueue和IndexFile中。

4. DefaultMQPushConsumerImpl其实本质是还是一种pull模式,它会从每一个ConsumerQueue中拉取未消费的消息,如果存在直接返回,如果不存在未消费的消息,broker会hold住此请求30s(长轮询机制,默认开启,这种长轮询机制也常见于统一配置中心,如 apollo客户端从服务端拉取配置监听配置的修改),在hold期间如果ReputMessageService往ConsumerQueue中推送了新消息,则触发释放hold,给consumer返回数据。通过回调的方式,consumer将拉取到的数据存入到ProcessQueue的msgTreeMap中,触发下一次拉取请求。长轮询机制达到准实时的效果,给人一种push的感觉。

5.在上一步骤中consumer将拉取到的数据存入到ProcessQueue的msgTreeMap后,将拉取到的消息根据consumerMessageBatchMaxSize来分组封装成若干个任务,提交到ConsumeMessageConcurrently的异步线程池中进行消费。

在这里要注意一些参数,pullBatchSize(默认32)是指消费端向broker的某个consumerQueue拉取消息时,指定的最大拉取数,broker的maxTransferCountOnMessageInMemory(默认32)为单次pull消息(内存)传输的最大条数,因为针对每一次拉取任务,最大可能拉取数为maxTransferCountOnMessageInMemory和pullBatchSize的最小值,当然broker端还受到单次拉取最大字节数等参数的限制。在上述步骤5中,consumer拉取到任务后,根据consumerMessageBatchMaxSize来分组切割封装任务,假设pullBatchSize和maxTransferCountOnMessageInMemory都采用默认值32,那么consumerMessageBatchMaxSize(默认值为1)设置为>32的值也是没有意义的。因为总量最大为32,consumerMessageBatchMaxSize>=32都只会封装成一个消费处理任务。

同时,走读ConsumeMessageConcurrently的源码,我们发现处理消费任务的线程池consumeExecutor的实现采用的jdk中的原生线程池,阻塞队列使用的是无界的LinkedBlockingQueue,并未指定大小,因此配置参数consumeThreadMax并无意义,默认值

consumeThreadMax=consumeThreadMin=20,这里可能算是一个小瑕疵。


问题: rocketmq消息堵塞了,怎么来查原因?

1. 发送消息时,消息体增加一个属性值,发送时间, 消费消息时,打印或抽样打印开始消费的时间

2.MessageExt是rocketmq对消息的封装类,在刷盘存储时,会对属性storeTimestamp进行赋值。如果storeTimestamp-发送时间>0.5s(异步刷盘默认值为0.5s),这种情况下说明broker端磁盘IO压力大或者page cache busy,如果伴随着发送消息时过多的失败,则应该考虑broker扩容了。配置transientStorePoolEnable=true,设置transientStorePoolSize的大小,也会有一定的帮助。

3. Consumer将消息拉取到本地的时间,从刷盘到拉取到本地,这中间包括ReputMessageService将commitlog中的新增消息追加到ConsumerQueue和长轮询拉取消息两个环节,正常情况下这两个步骤的耗时很短,rocketmq的官方描述为“准实时".这一环节出现问题,大多因为环境因素,比如说broker的物理机上还装着redis,redis服务在一段时间内占据着大量的宽带,导致网络堵塞,consumer拉取的消息缓慢。那这一类问题怎么监控呢?有什么表象?稍后分析

4.消费端每一个ConsumerQueue对应一个ProcessQueue,拉取的任务先存储在ProcessQueue的msgTreeMap,消费完后清除。

如果msgTreeMap的size过大,会影响到拉取的频次,默认拉取是0延迟的,即一次拉取成功或者hold30s过程中返回,数据存储在消费端后即发起下一次拉取任务,但msgTreeMap的size大于配置值(默认单队列1000),会延迟拉取。这样子做防止客户端OOM,那么导致size过大的原因呢?有可能有两种情况:一种是ConsumeMessageConcurrently中的消费任务的线程池堵塞,即线程数配置过小,另一种情况是消费消息的能力不足,单个消息消费时间过长。综上所述,需要监控三个指标来定位问题: 1.监控ProcessQueue中msgTreeMap的大小  2.监控 ConsumeMessageConcurrently中consumeRequestQueue的大小(阻塞队列) 3.监控单条消息的处理速度(perf4j).     

监控消息延迟一般是拿当前消费消息在consumerQueue中的偏移量和consumerQueue中的最大偏移量作比较,如果差值过大,则认为是消费阻塞。最后回到这么一种场景,消费阻塞报警,msgTreeMap的近期大小稳定(或趋近于0),consumeRequestQueue的大小趋近于0,单条消息的处理速度近期较稳定,那很大可能出现在消息拉取上。即broker机器的带宽出现了问题。

rocketmq正在学习过程中,上述总结难免存在不足甚至bug,有问题希望小伙伴指出或一起探讨,我的微信号:qian_348840260

猜你喜欢

转载自blog.csdn.net/qian_348840260/article/details/106783899