生产消息可靠性
发送模式与可靠性
模式 | 性能 | 可靠性 |
---|---|---|
SYNC | 快 | 不丢失 |
ASYNC | 快 | 不丢失 |
ONEWAY | 最快 | 可能丢失 |
生产者
消息同步/异步发送请求,发送如果失败可以基于具体业务场景决定是否需要重试,保障消息推送成功。
重试
同步消息默认支持自动retry,默认配置为2(org.apache.rocketmq.client.producer.DefaultMQProducer#retryTimesWhenSendFailed)
事务消息
数据库更新逻辑与事务消息组合使用,保障数据库与消息一致性
Broker
收到生产者消息由org.apache.rocketmq.broker.processor.SendMessageProcessor逻辑进行处理并响应客户端结果
提交日志存储消息:org.apache.rocketmq.store.CommitLog#putMessage
消息存储本地失败直接响应失败交由客户端决策
数据丢失保护
同步存储消息:org.apache.rocketmq.store.CommitLog#putMessage会同时提交刷盘请求submitFlushRequest
异步存储消息:org.apache.rocketmq.store.CommitLog#asyncPutMessage
org.apache.rocketmq.store.CommitLog#handleDiskFlush
数据写入本地内存MappedFile后刷盘策略默认配置为异步:org.apache.rocketmq.store.config.MessageStoreConfig#flushDiskType
- 同步模式:FlushDiskType.SYNC_FLUSH
- 异步模式:FlushDiskType.ASYNC_FLUSH
如果想要消息有不丢失保障需要指定为同步模式,不然就由可能存在丢失问题,异步一定是有风险的
提交消息代码逻辑:org.apache.rocketmq.store.CommitLog.GroupCommitService
高可用
同步存储消息:org.apache.rocketmq.store.CommitLog#putMessage会同时提交复制至从节点请求submitReplicaRequest
异步存储消息:org.apache.rocketmq.store.CommitLog#asyncPutMessage
org.apache.rocketmq.store.CommitLog#handleHA
高可用策略默认为打开,可以通过消息体的property属性(org.apache.rocketmq.common.message.MessageConst#PROPERTY_WAIT_STORE_MSG_OK)设置为关闭
如果打开则等待主从同步,默认等待超时时间配置为5秒(org.apache.rocketmq.store.config.MessageStoreConfig#syncFlushTimeout)
同步消息至从节点代码逻辑:org.apache.rocketmq.store.ha.HAService.GroupTransferService
消费消息可靠性
仅讨论推送消息模式,拉取模式消费者自主维护下标,风险由开发者控制
消费模式
- 同步模式:不多说,直接能获取到消费消息结果,如果失败客户端来决定重试策略
- 异步模式:推送消息的方式消费消息,重点关注该模式
消费者启动执行拉取消费消息:org.apache.rocketmq.client.consumer.DefaultMQPushConsumer#start
负载均衡
mq分配
- AllocateMachineRoomNearby,分配策略装饰模式,机房分配,按照机房分配的基础上实际按照代理分配算法分配,机房解析规则支持自定义实现。如果mq机房不存在对应的消费者,则直接按照代理队列分配策略进行分配
- AllocateMessageQueueAveragely,平均分配策略,例如:10个消费者,22个队列,前两个消费者(下标0,1)每人3个队列,队列下标分别对应(0,1,2),(3,4,5),后面消费者每人两个队列
- AllocateMessageQueueAveragelyByCircle,环形平均分配策略,例如:10个消费,22个队列,消费者下标0分配队列为队列下标(0,10,20),消费者下标1分配队列下标(1,11,21),消费者下标2分配队列下标(2,12),即队列下标对消费者数量取余数等于当前消费者下标
- AllocateMessageQueueByConfig,配置分配策略,直接返回配置的队列列表
- AllocateMessageQueueByMachineRoom,机房分配,按照配置的消费者idc机房机器host或hostName列表取出队列列表,然后队列列表按照平均分配策略算法进行分配队列
- AllocateMessageQueueConsistentHash,一致性hash分配策略
如果消费者数量大于队列数量,多余的消费者则不会消费消息。例如:队列个数3,消费者(一个group)数量7,那么只有下标为0,1,2的消费者可以消费消息,其余消费者不会消费
推送模式(rmq是基于拉模式实现):org.apache.rocketmq.client.impl.consumer.DefaultMQPushConsumerImpl#pullMessage
计算消费下标:从上次消费下标处消费,默认为ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET
调度拉取消息请求:org.apache.rocketmq.client.impl.consumer.RebalanceImpl#dispatchPullRequest
流控
流控休眠时间为50ms
- 每个队列的消息数量维度(org.apache.rocketmq.client.consumer.DefaultMQPushConsumer#pullThresholdForQueue),默认为1000个
- 每个队列的消息大小维度(org.apache.rocketmq.client.consumer.DefaultMQPushConsumer#pullThresholdSizeForQueue),默认为100MiB
- 队列最大范围(org.apache.rocketmq.client.consumer.DefaultMQPushConsumer#consumeConcurrentlyMaxSpan),默认为2000。
异步拉取消息
请求异常,重试休眠时间默认3s(org.apache.rocketmq.client.impl.consumer.DefaultMQPushConsumerImpl#pullTimeDelayMillsWhenException)
- 拉取链接异常:RemotingConnectException
- 获取信号量超时(org.apache.rocketmq.remoting.netty.NettySystemConfig#CLIENT_ASYNC_SEMAPHORE_VALUE,默认:65535):RemotingTimeoutException(“invokeAsyncImpl call timeout”),或:“invokeAsyncImpl tryAcquire semaphore timeout, %dms, waiting thread nums: %d semaphoreAsyncValue: %d”
- 发送请求异常:RemotingSendRequestException
- 限流异常:RemotingTooMuchRequestException(“invokeAsyncImpl invoke too fast”)
请求成功执行异步回告callback
异步回告
org.apache.rocketmq.client.consumer.PullCallback,DefaultMQPushConsumerImpl内部类实现
- 拉取消息为空则继续消费下个索引的消息
- 拉取消息不为空则消费消息列表
消费者下标维护
- 无消息匹配,滚动下标再次拉取
- 下标异常,滚动下标,10s后异步执行:更新RemoteBrokerOffsetStore下标缓存,持久化下标至broker,移除本地对应的处理队列
- 下标消息响应正常,下面重点关注此场景
消费消息响应正常
- 消息列表缓存至本地processQueue
- 提交消费请求,如果是顺序消费者单独消费请求ConsumeRequest处理所有消息:ConsumeRequest;否则按照配置切分为多个消费请求进行消费:org.apache.rocketmq.client.consumer.DefaultMQPushConsumer#consumeMessageBatchMaxSize
消费请求逻辑:org.apache.rocketmq.client.impl.consumer.ConsumeMessageConcurrentlyService.ConsumeRequest
- 执行前置钩子方法:org.apache.rocketmq.client.impl.consumer.DefaultMQPushConsumerImpl#executeHookBefore
- 执行消费:org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently#consumeMessage
- 执行后置钩子方法:org.apache.rocketmq.client.impl.consumer.DefaultMQPushConsumerImpl#executeHookAfter
- 处理消费结果:org.apache.rocketmq.client.impl.consumer.ConsumeMessageConcurrentlyService#processConsumeResult
- 消费成功:CONSUME_SUCCESS,更新RemoteBrokerOffsetStore下标缓存
- 消费失败:RECONSUME_LATER,发送消费结果ack至broker(RequestCode.CONSUMER_SEND_MSG_BACK),如果发送失败5s后重试;发送消费结果ack成功后更新RemoteBrokerOffsetStore下标缓存
RemoteBrokerOffsetStore下标缓存定时任务10s(org.apache.rocketmq.client.impl.factory.MQClientInstance)定时上报至broker,如果上报前重启会出现重复消费,RMQ保证至少一次消费
broker处理CONSUMER_SEND_MSG_BACK请求:org.apache.rocketmq.broker.processor.SendMessageProcessor#asyncConsumerSendMsgBack
- 如果没有超出重试次数,基于原消息内容异步存储一条重试主题消息(org.apache.rocketmq.store.CommitLog#asyncPutMessage):“%RETRY%”,并设置延迟属性DELAY:org.apache.rocketmq.common.message.Message#setDelayTimeLevel
- 如果超出重试次数,基于原消息内容异步存储一条死信队列(dead letter queue)主题消息:“%DLQ%”
- 异步存储消息(org.apache.rocketmq.store.CommitLog#asyncPutMessage),如果消息类型为非事务或事务消息的提交消息并且延迟属性DELAY大于0,则将重试消息转为调度主题消息:SCHEDULE_TOPIC_XXXX(org.apache.rocketmq.common.topic.TopicValidator#RMQ_SYS_SCHEDULE_TOPIC)存储。
- 调度任务(ScheduleMessageService)定时将重试队列消息转存至原消息主题队列
小结
- 消息发送失败会发送CONSUMER_SEND_MSG_BACK请求至broker来进行重试
- CONSUMER_SEND_MSG_BACK请求失败会在5s后重试
- 如果重试消息全部失败可以消费死信队列来获取重试全部失败的消息
消息重试
消息重试是在ack时判断存在需要重试的消息,根据%RETRY%+consumerGroup作为新topic,队列则随机选择,进行requeue消息
重试消息复用延迟消息能力
- 如果是延迟消息(org.apache.rocketmq.common.message.Message#getDelayTimeLevel>0),且消息类型非事务类型:TRANSACTION_NOT_TYPE,或者是事务提交消息类型:TRANSACTION_COMMIT_TYPE,将消息写入延迟消息主题:RMQ_SYS_SCHEDULE_TOPIC,队列queueId为延迟级别delayLevel-1。delayLevel(org.apache.rocketmq.store.config.MessageStoreConfig#messageDelayLevel)对应延迟时间:“1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h”,1对应1s,2对应5s,3对应10s以此类推
- 延迟消息定时器处理延迟消息requeue:org.apache.rocketmq.store.schedule.ScheduleMessageService.DeliverDelayedMessageTimerTask
- 如果是CLUSTERING模式,客户端消费者启动时会订阅retry topic:org.apache.rocketmq.client.impl.consumer.DefaultMQPushConsumerImpl#start->org.apache.rocketmq.client.impl.consumer.DefaultMQPushConsumerImpl#copySubscription
总结
- 生产者由开发者根据业务场景决定重试保障消息必达broker。事务消息保障消息与数据库(或其他存储)组合操作的原子性
- broker刷盘策略可以选择同步或异步,同步保障数据在broker宕机时不会出现数据丢失。默认启用HA会将主节点数据同步至从节点,同样提供同步异步两种模式,同步保障数据在broker宕机时不会出现数据丢失
- 消费消息是先通知监听器再上报下标,并且提供重试策略,如果重试全部失败则需要开发者提供保障方案,可以借助DLQ也可以选择其他实现方案来保障可靠性