对于消息队列来说,最重要的事情就是要保障消息不丢失,这也是开发者最常遇到的问题。其实,现在主流的消息队列产品都提供了非常完善的可靠性消息传输机制,我们唯一要做的就是知道如何使用它,大部分线上数据丢失的问题都是由于配置不当引起的。

一. 检测消息丢失的方法
一个新上线的系统,各方面都不太稳定,需要一个磨合期,这个时候特别需要监控你的系统中是否有消息丢失的情况。如果是IT基础设施比较完善的公司,一般会有分布式链路追踪系统,使用类似的追踪系统可以很方便的追踪每一条消息。如果没有这样的追踪系统,我们如何检查是否有消息丢失的情况呢?答:利用消息的有序性。
在生产者发送的每条消息中都附加一个连续递增的序号,然后消费者去验证这个序号的连续性。如果没有消息丢失,那么消费者收到的消息序号必定是连续的,否则会出现序号缺失,我们再根据缺失的序号去生产端排查哪个消息出了什么问题。
大多数消息队列都支持拦截器机制,我们可以在发送消息之前的拦截器中将序号注入到消息中,这样实现的好处是消息检测的代码不会侵入到你的业务代码中,待你的系统稳定后,也方便将这部分代码逻辑关闭或者删除。
我们同时要注意RocketMQ和kafka只能保证主体队列中的连续性,并且消费组中的多个消费者是竞争关系的。所以,我们在发消息时要根据业务逻辑指定合理的队列,比如同一地区的订单发送到同一个队列,再在各个队列中单独检测消息序号的连续性。
如果,你的系统中生产者也是多实例的,由于不好协调多个生产者的序号有序性,每个生产者生成的序号也要加上各自的唯一标识,在消费端按照每个生产者分别来检测序号的连续性。
一个消费组内消费者的数量最好跟主体队列的数量一致,一个队列对应一个消费者,方便检测序号的连续性。
二. 确保消息可靠传递
说完了检测消息丢失的方法,我们再来看看,在消息队列传递消息时都有哪些地方可能会导致消息的丢失,以及如何避免消息丢失。
1. 生产阶段
在这个阶段,生产者将消息生产出来,并经过网络传输发送到服务器Broker端。
在这里,最常用的就是"发送-确认"机制来保证消息的可靠传递。当你的代码调用发送消息方法时,消息队列的客户端就会发送消息,服务端收到消息后会向给客户端发送一个确认响应,表明消息被收到了。客户端收到确认响应后,完成一次消息传递。如果客户端长时间没有收到确认响应,有些消息队列会重新发送这条消息,如果重发再失败,会以返回值或者异常的方法通知用户。
你在编写发送消息代码的时候,只要正确处理返回值或者捕捉异常就能保证消息不会丢失。同步发送时,只要加上try catch捕捉即可。异步发送在回调函数中检查。
2. 存储阶段
在存储阶段只要broker在正常运行,就能保证消息不会丢失,但是如果进程死掉了,或者服务器宕机了,还是有可能出现消息丢失的。
如果对消息的可靠性要求比较高,可以通过配置Broker参数来避免因为宕机丢消息。
对于单个节点的broker,需要配置broker参数,在收到消息后,将消息写入磁盘再给客户端发送确认响应。如果broker是多个节点组成的集群,那么可以配置成至少将消息发送到2个以上的节点,再给客户端发送确认响应,当单个broker宕机时,其他的broker可以替代宕机的broker,也不会发生消息丢失。
3. 消费阶段
消息队列在消费阶段采用了和生产阶段相同的确认机制。你需要注意的是,不要受到消息以后立即发送确认响应,而是应该在业务逻辑处理完成后再发送。
我们在上诉三个阶段做出正确的配置和编写代码,配合消息队列的可靠性机制,就能保证消息的可靠性传递。我们来总结一下:
-
在生产阶段:正确的捕获没有接受到确认响应的异常,并做出相应的处理
-
在存储阶段:正确的配置消息队列相关参数,选择刷盘或者将消息至少发送至两个节点再发送确认响应
-
在消费阶段:收到消息以后,先进行业务逻辑处理,再发送消费确认响应
思考:如果确认响应在网络传输过程中丢失,也会导致重发消息。也就是说,无论是broker还是consumer都是有可能收到重复消息的。如何处理这一些重复消息才能不能影响到正常的业务逻辑呢?
浙公网安备 33010602011771号