MQ不丢消息,究竟是怎么实现的?

本文探讨了消息队列(MQ)确保消息可靠传递的核心架构与流程,包括消息落地及超时重传机制,旨在解决MQ重启导致消息丢失的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

以下内容转载自 https://www.toutiao.com/i6834090029292192264/

技术联盟总坛2020-06-03 19:59:40

作者: 58沈剑 架构师之路

前几天有水友提问:

通过消息队列(MsgQueue,MQ)发送任务和消息,万一MQ重启了怎么办?能否保证MQ不丢消息?

 

今天就聊聊MQ的消息必达性架构与流程。

 

不丢消息,MQ架构设计的核心方向是什么?

MQ要想消息必达,架构上有两个核心设计点:

(1)消息落地;

(2)消息超时、重传、确认;

 

为了实现上述两个核心点,MQ架构如何?

MQ不丢消息,究竟是怎么实现的?

 

上图是一个MQ的核心架构图,可以分为三大块:

(1)发送方 -> 左侧粉色部分;

(2)MQ核心集群 -> 中间蓝色部分;

(3)接收方 -> 右侧屎黄色部分;

 

粉色发送方又由两部分构成:

(1)业务调用方;

(2)MQ-client-sender;

其中后者向前者提供了两个核心API

(1)SendMsg(bytes[] msg);

(2)SendCallback();

 

蓝色MQ核心集群又分为四个部分:

(1)MQ-server

(2)zk;

(3)db;

(4)管理后台web;

 

黄色接收方也由两部分构成:

(1)业务接收方;

(2)MQ-client-receiver;

其中后者向前者提供了两个核心API

(1)RecvCallback(bytes[] msg);

(2)SendAck();

 

MQ是一个系统间解耦的利器,它能够很好的解除发布订阅者之间的耦合,它将上下游的消息投递解耦成两个部分,如架构图中的1箭头和2箭头:

MQ不丢消息,究竟是怎么实现的?

 

箭头1:发送方将消息投递给MQ,上半场;

箭头2:MQ将消息投递给接收方,下半场;

 

MQ消息可靠投递核心流程如何?

MQ既然将消息投递拆成了上下半场,为了保证消息的可靠投递,上下半场都必须保证消息必达。

MQ不丢消息,究竟是怎么实现的?

 

MQ消息投递上半场,MQ-client-sender到MQ-server流程见上图1-3:

(1)MQ-client将消息发送给MQ-server;

画外音:此时业务方调用API:SendMsg。

(2)MQ-server将消息落地,落地后即为发送成功;

(3)MQ-server将应答发送给MQ-client;

画外音:此时回调业务API:SendCallback。

MQ不丢消息,究竟是怎么实现的?

 

MQ消息投递下半场,MQ-server到MQ-client-receiver流程见上图4-6:

(4)MQ-server将消息发送给MQ-client;

画外音:此时回调业务API:RecvCallback。

(5)MQ-client回复应答给MQ-server;

画外音:此时业务方主动调用API:SendAck。

(6)MQ-server收到ack,将之前已经落地的消息删除,完成消息的可靠投递;

 

如果消息丢了怎么办?

MQ消息投递的上下半场,都可以出现消息丢失,为了保证消息可达性,MQ需要进行超时和重传。

 

上半场如何实施超时与重传?

MQ不丢消息,究竟是怎么实现的?

 

MQ上半场的1或者2或者3如果丢失或者超时,MQ-client-sender内的timer会重发消息,直到期望收到3,如果重传N次后还未收到,则SendCallback回调发送失败,需要注意的是,这个过程中MQ-server可能会收到同一条消息的多次重发。

 

下半场如何实施超时与重传?

MQ不丢消息,究竟是怎么实现的?

 

MQ下半场的4或者5或者6如果丢失或者超时,MQ-server内的timer会重发消息,直到收到5并且成功执行6,这个过程可能会重发很多次消息。

画外音:一般采用指数退避的策略,先隔x秒重发,2x秒重发,4x秒重发,以此类推。

需要注意的是,这个过程中MQ-client-receiver也可能会收到同一条消息的多次重发。

 

总结

 

MQ是系统之间的解耦利器,MQ为了保证消息必达,架构设计方向为:

(1)消息收到先落地;

(2)消息超时、重传、确认保证消息必达;

 

遗留问题:

上半场,MQ-server可能收到重复的消息;下半场,MQ-client-receiver,也就是消息接收方可能收到重复的消息,怎么办?

画外音:如何去重,如何幂等设计。

<think>嗯,用户问的是关于JMS消费者在处理消息时,如果发现是自己的消息,如何将消息重新放回队列的问题。首先,我需要回顾一下JMS的消息处理机制。JMS消费者在接收到消息后,通常会有确认机制,比如自动确认或手动确认。如果消费者处理消息时发现这是它应该处理的消息,正确的做法可能涉及到消息的重新投递或者回滚。 用户可能是在使用某种消息中间件,比如ActiveMQ或者RabbitMQ,虽然JMS是Java的标准API,但具体实现可能会有同的处理方式。我需要确认JMS的标准做法,以及在常见消息队列系统中的实现。 首先,消费者在手动确认模式下,如果处理失败或者拒绝消息,可以调用recover()方法,这样会重新传递未确认的消息。但这是否适用于“是自己的消息”的情况呢?或者是否需要更明确的拒绝操作,比如通过NACK或者重新发送到队列? 另外,JMS提供了一些消息属性,比如JMSRedelivered,用来标识消息是否被重新投递过。但用户的问题是如何将消息放回队列,以便其他消费者可以处理。这时候可能需要将当前消息拒绝,并且让消息中间件重新排队。例如,在ActiveMQ中,如果消费者调用session.recover(),未确认的消息会被重新传递,包括当前的消息。但这是否会导致消息被放回原队列的开头还是末尾呢? 还有,可能需要注意消息的重试次数,避免消息被无限循环处理。这时候可能需要自定义重试次数,并在达到阈值后将消息转移到死信队列。 另外,还有一种方法是将消息作为新消息重新发送到原队列,然后确认当前消息。但这样可能会导致消息的顺序被打乱,或者产生重复消息,需要根据业务场景权衡。 还需要考虑消息的属性和头信息,比如设置JMSXGroupID来分组消息,确保同一组消息由同一个消费者处理。如果是因为分组正确导致的消息错乱,可能需要调整消息的分组策略。 总结来说,可能的解决方案包括:使用手动确认并拒绝消息以重新入队、利用重发机制、或者重新发布消息到队列。但需要注意消息的顺序、重复和重试次数的问题。</think>在JMS(Java Message Service)中,当消费者发现接收到的消息属于自己处理范围时,可以通过以下步骤将消息安全放回队列,确保消息能被其他消费者重新消费: --- ### 一、核心机制:消息确认模式 JMS的 **消息确认模式** 是关键: - **自动确认(AUTO_ACKNOWLEDGE)**:消费后自动确认,消息立即从队列移除。 - **手动确认(CLIENT_ACKNOWLEDGE)**:需显式调用`message.acknowledge()`,此时可通过异常或回滚操作控制消息去向。 --- ### 二、推荐解决方案:手动确认 + 会话回滚 若需将消息放回队列,建议使用 **手动确认模式** 并结合 **会话回滚**: 1. **开启手动确认模式** 创建消费者时指定`CLIENT_ACKNOWLEDGE`: ```java Session session = connection.createSession(false, Session.CLIENT_ACKNOWLEDGE); ``` 2. **拒绝当前消息并触发重投** 通过回滚会话(`session.recover()`)使消息重新入队: ```java try { // 判断消息是否属于当前消费者 if (!isMessageForMe(message)) { session.recover(); // 回滚当前会话,消息重新进入队列 } else { // 正常处理消息 processMessage(message); message.acknowledge(); // 确认消息 } } catch (Exception e) { session.recover(); // 异常时回滚 } ``` --- ### 三、注意事项 1. **消息重试次数控制** 避免无限循环重投,需通过消息头(如`JMSXDeliveryCount`)记录重试次数,超出阈值后转移到死信队列: ```java int redeliveryCount = message.getIntProperty("JMSXDeliveryCount"); if (redeliveryCount > MAX_RETRY) { sendToDLQ(message); // 转入死信队列 message.acknowledge(); } else { session.recover(); } ``` 2. **消息顺序性影响** 回滚后消息可能被插入队列末尾,导致顺序变化。如需严格顺序,需依赖消息中间件的特性(如ActiveMQ的Exclusive Consumer)。 3. **幂等性设计** 因消息可能被多次投递,消费者需支持幂等处理(例如通过唯一ID去重)。 --- ### 四、备选方案:重新发布消息 若中间件支持回滚重投,可手动将消息重新发布到原队列: ```java if (!isMessageForMe(message)) { MessageProducer producer = session.createProducer(queue); producer.send(message); // 重新发送消息 message.acknowledge(); // 确认原消息(避免重复) } ``` **注意**:此方法可能影响消息顺序,且需处理潜在重复问题。 --- ### 五、消息分组优化 若消息需定向到特定消费者,可结合 **消息分组(JMSXGroupID)** 实现: ```java message.setStringProperty("JMSXGroupID", "GroupA"); ``` 确保同一分组消息始终由同一消费者处理。 --- ### 总结 | 场景 | 方案 | 优点 | 缺点 | |-------------------------|-----------------------------|---------------------|---------------------| | 需保留消息顺序 | 手动确认 + 会话回滚 | 自动重投,兼容性好 | 可能影响顺序 | | 需精准控制重试 | 手动确认 + 重试计数器 + 死信队列 | 避免无限循环,可控性高 | 需额外编码 | | 允许消息顺序变化 | 重新发布消息到队列 | 灵活性强 | 可能重复,顺序可控 | 建议优先使用 **手动确认 + 会话回滚**,并根据业务需求补充重试控制或分组策略。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值