一、如何保证消息不丢失?
消息从发送到消费的全链路中,可能在跨网络传输或数据暂存缓存时丢失。解决思路是在每个环节"加保障":
生产者发送消息:通过"确认机制"确保消息到Broker(消息服务器)。比如RocketMQ的同步发送(等Broker回复)、异步发送(回调通知),Kafka的Future.get()同步等待,RabbitMQ的Publisher Confirms回调,都是让生产者知道消息是否发成功,失败了就重试。
Broker存储消息:Broker收到消息后,默认先放内存缓存(PageCache),再异步刷到硬盘,断电会丢数据。解决办法是选"同步刷盘"(写入即刷盘,安全但慢)或"异步刷盘"(定期刷盘,快但有风险),RocketMQ、Kafka都有类似配置,RabbitMQ则建议结合生产者确认机制兜底。
Broker主从同步:Broker集群中,主节点(Master)数据要同步到从节点(Slave)。RocketMQ普通集群中,主节点挂了数据还在,重启后继续同步;Kafka若主节点挂了,从节点变主节点,未同步的旧数据会丢失;RocketMQ的Dledger集群用Raft协议,多数节点确认后才提交,安全性更高。
消费者消费消息:消费者处理完消息需给Broker"确认",没收到确认就重发。但要注意:若消费者用多线程异步处理消息(比如开新线程处理,直接返回"成功"),可能导致消息处理失败却被标记成功,造成丢失,需避免这种写法。
极端情况(MQ全挂):生产者发消息失败时,先写"降级缓存"(如本地文件、Redis),再开线程不断重试发送,等MQ恢复后消息能补发给Broker。
二、如何保证消息的顺序性?
通常只需保证"局部有序"(比如同一订单的消息按顺序处理),而非全局所有消息有序。关键是"同一组有序消息走同一个队列,且被同一个消费者单线程处理":
生产者:把同一类有序消息(如同一用户的消息)发到同一个队列(RocketMQ/Kafka的分区、RabbitMQ的队列),利用队列的FIFO特性保证存储顺序。
消费者:RocketMQ通过控制消费线程并发,让一个队列只被一个线程处理;Kafka的分区天然单线程消费;RabbitMQ的经典队列若对应一个消费者,也能保证顺序,多个消费者则会拆分消息导致乱序。
三、如何保证消息幂等性(不重复处理)?
消息可能因网络问题重复发送或消费(比如生产者没收到Broker确认而重试,消费者确认丢失导致Broker重发),需避免重复处理:
生产者防重复:RocketMQ给每条消息分配唯一messageId,Broker通过ID去重;Kafka开启幂等性后,用PID(生产者唯一标识)和Sequence Number(分区内递增序号),Broker只接收序号连续的消息,防止重复。
消费者防重复:消费者处理消息时,记录"唯一标识"(如messageId或业务ID,如订单号),处理前先查是否处理过。若多次重试仍失败,消息会进"死信队列",需单独处理(默认死信队列不可消费,需手动开启权限)。
四、如何处理消息积压?
消息积压是因为消费者处理太慢,积压太久可能导致消息过期删除(RocketMQ/Kafka)或拖慢Broker性能(RabbitMQ经典队列)。解决办法:
提升消费效率:优化业务逻辑,或增加消费者实例(但RocketMQ/Kafka的消费者数不能超过队列数,否则多余实例没用)。
临时扩容:若队列数不够,可创建新Topic(配更多队列),用临时消费者把旧Topic的消息快速转发到新Topic,再用更多消费者在新Topic上消费,加速处理。
总结
不同MQ(如RocketMQ、Kafka、RabbitMQ)的设计差异,源于业务场景的取舍:RocketMQ重视金融级消息安全,Kafka侧重日志收集的高可用,RabbitMQ灵活但经典队列对积压敏感。MQ的各种方案本质是"安全"和"性能"的权衡(比如同步刷盘安全但慢,异步刷盘快但可能丢消息)。没有"最优解",需根据业务场景选择(金融场景重安全,日志收集重效率)。根据业务对"安全""效率""成本"的需求灵活选择方案