持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第10天,点击查看活动详情
简介
消息的可靠性指的是最大程度保证发送的消息不丢失。
消息发送流程
从消息发送流程中可以看出,消息发送失败可能存在如下几个场景中。
- 生产者发送消息到Broker。可能由于网络或者Broker的问题导致消息发送失败。
- Broker接收到消息暂存到内存中,Consumer还没来得及消费,Broker异常了。
- 消费者消费消息失败。
Produce发送消息到Broker。可能因为网络或者Broker的问题导致消息发送失败
生产者发送消息失败,会根据设置的重试策略进行重试,RocketMq重试具体逻辑如下:
- 如果生产者发送时本身产生异常,则消息将不会重试。
- 如果发送为同步消息异常,则线程进行轮询到下一个Broker发送,如果是异步消息发送异常,则Broker机进行重试,重试执行方法的时间不能超过sendMsgTimeout设置的值,默认为10秒。
- RocketMq默认重试次数为2次,客户端可以进行设置
rocketmq: # 生产者配置 producer: # 发送消息失败重试次数,默认2 retryTimesWhenSendFailed: 3
业务扩展保证消息可靠性
业务系统中,系统对消息的可靠性要求比较严格,虽然提供了重试机制,但业务还需要进行特殊处理保。添加业务消息日志记录表记录消息信息,消息发送成功记录日志为成功,消息发送失败记录状态为失败,对于失败的消息,系统需要通过定时任务进行重新进行发送。
伪代码实现
public void sendOrderMessage(String bizKey,String type,String content)
{
logger.info("send add order message ");
Message message =new Message();
//业务的主键:可以为订单id、商品id等
message.setKeys(bizKey);
message.setTopic("AddOrder");
message.setTags("TAG-AddOrder");
//消息内容,创建订单,退款订单等
message.putUserProperty("type", type);
try
{
message.setBody(content.getBytes(RemotingHelper.DEFAULT_CHARSET));
}
catch (Exception e)
{
logger.error("");
}
// 记录消息日志状态为成功
rocketMQTemplate.asyncSend("testtopic", message,new SendCallback()
{
@Override
public void onSuccess(SendResult result)
{
logger.info("发送消息成功");
//业务日志记录成功
}
@Override
public void onException(Throwable e)
{
logger.error("发送消息失败",e);
//业务进行日志记录失败
}
});
}
说明:采用异步发送消息,如果发送成功业务记录消息已发送成功日志,如果失败业务记录消息发送失败。
Broker接收到消息暂存到内存中,Consumer还没来得及消费,Broker异常了。
当Broker接收到消息后先将消息存储到PageCache中,然后进行持久化存储操作。
消息存储结构
- CommitLog:存储消息的元数据。所有消息都会顺序存入到CommitLog文件当中。 CommitLog由多个文件组成,每个文件固定大小1G。以第一条消息的偏移量为文件名。
- ConsumerQueue:存储消息在CommitLog的索引
- IndexFile:消息查询提供了一种通过key或时间区间来查询消息的方法,这种通过IndexFile来 查找消息。
消息持久化整体流程
- Broker先将消息写入内存中,然后将消息按照顺序写到CommitLog文件中。
- 线程根据消息的队列信息,写入到相关的ConsumerQueue中(minOffset为写入的初始位置,consumerOffset为当前消费到的位置,maxOffset为ConsumerQueue最新写入的位置)和IndexFile文件中。
- 消费者从ConsumerQueue的consumerOffset读取到当前应该消费的消息在CommitLog中的偏移量,到CommitLog中找到对应的消息,消息消费成功后移动consumerOffset。
消息持久化刷盘机制
同步刷盘
同步刷盘指的 Broker 端收到消息发送者的消息后,先写入内存,然后将内容持久化到磁盘后才向客户端返回消息发送成功。
- 优点:不会造成消息丢失
- 缺点: 吞吐量低。
异步刷盘
异步刷盘指的是 Broker 将消息存储到 PageCache 后就立即返回成功,然后开启一个异步线程定时执行 FileChannel 的 forece 方法将内存中的数据定时刷写到磁盘,默认间隔为 500ms。在 RocketMQ 的异步刷盘实现类为 FlushRealTimeService。
- 优点:吞吐量高
- 缺点: 当磁盘损坏时,会造成丢失消息。
小结
同步刷盘的优点是能保证消息不丢失,即向客户断返回成功就代表这条消息已被持久化到磁盘,即消息非常可靠,但是以牺牲写入性能为前提条件的,但由于RocketMQ 的消息是先写入 PageCache,故消息丢失的可能性较小,如果能容忍一定几率的消息丢失,可以考虑使用异步刷盘。
消费者消费失败
消费者消费失败,会重新把消费发回给Broker进行处理。其具体流程如下:
说明:
- 从流程图可以看出,消费者消息重新消息只对集群模式生效,对于消息订阅模式,消费者消费失败直接丢弃不做任何处理。
- 关于消费者重试的次数,MQ默认允许每条消息最多重试16次,每次消费失败发送一条延时消息到重试队列,同一条消息失败一次将延时等级提高一次,然后再放到重试队列。重试16次后如果还没有消费成功,则将消息放到死信队列中。
- 重试队列topic的名称被名命为%RETRY% + consumerGroup,而死信队列的名称名命为:%DLQ% + consumerGroup,可以登录RocketMQ的消息控制台进行查看。
- 消费端进行重试机制,会造成重复消息的问题,关于如何处理重复消息以及消息的幂等性,将在后续的文章进行讲解。
总结
项目中一些特殊的业务场景中,如果需要保证消息的可靠性,就会大大降低RocketMQ的吞吐量,所以需要根据业务进行综合性考虑。