写点什么

如何处理消息队列中的重复消息

用户头像
废材姑娘
关注
发布于: 2021 年 01 月 19 日
如何处理消息队列中的重复消息

开篇

前面的文章中我们讨论了消息丢失的情况,  也介绍了如何在生产, 存储和消费阶段如何保证消息的可靠性。不知道大家有没有注意到这样一种情况,如果生产者成功将消息发送到 Broker, Broker 也成功将消息写入队列,但是在想生产者返回确认信息的时网络出现问题,导致生产者长时间没有收到确认的回复。这时生产者如果重新发送消息,是不是就重复了?

再搬出我们经常举的转账的例子,比如我要给我男朋友转 21 块钱, 如果我发送了两次转账的消息,那不就亏大方了, 我这么穷怎么能允许把我的钱都转走了。那该怎么处理重复的消息的问题呢?


image.png


消息中间件可以保证消息不重复吗

作为消息中间件的使用者,你是不是跟我一样,如果消息中间件可以帮我们保证消息不重复是不是就太棒了,毕竟开箱即用的简单是每个使用者都爱的呀, 那我们就看看消息中间件有没有可能实现这个功能。

MQTT 协议中, 给出了三种传递消息时能够提供的服务质量标准, 分别是:

At most once: 至多一次。消息在传递时,最多会被送达一次。换一个说法就是,没什么消息可靠性保证,允许丢消息。一般都是一些对消息可靠性要求不太高的监控场景使用,比如每分钟上报一次机房温度数据,可以接受数据少量丢失。

At least once: 至少一次。消息在传递时,至少会被送达一次。也就是说,不允许丢消息,但是允许有少量重复消息出现。

Exactly once:恰好一次。消息在传递时,只会被送达一次,不允许丢失也不允许重复,这个是最高的等级。

目前我们常用的消息中间件都使用的是 At least once(至少一次的方案),也就是说,消息中间件都不能保证消息不重复。这么看来,我们希望消息中间件替我们解决消息不重复的梦想算是破灭了。


解决方案----幂等性

目前解决重复消息的方法是在消费端保证消息消费的幂等性,即在消息的消费过程中,无论消费多少次,执行的结果都是相同的。

在回到我们开篇提到的转账的例子,我要给我男盆友转账 21 元,那每消费一次都会都会给他转账 21 元,这种情况就不是幂等的。但是我的消息是把我男朋友的账号余额变成 21 元,那无论这个消息被消费多少次,结果都是一样的, 就是账户余额变成 21 元,这个操作就是幂等性的。

从上面的分析可以知道尽管消息中间件做不到 Exactly once, 但是我们可以通过 At least once 和幂等性来避免重复消息带来的影响,那如果实现消息的幂等性呢?

利用数据库的唯一约束实现幂等性

基于数据库的唯一键来保证重复数据不会重复插入多条,比如我们的转账例子,可以创建一个转账记录表,表中为账户 ID 和账单 ID 建立唯一的索引, 所以针对同一个账户 ID 和账单 ID 至多只有一条记录。

那实际过程中,就可以先将转账记录表中插入一条数据,然后再根据转账记录表,异步的更新用户的账户余额,这样重复的账单和账户就不会重复插入多条数据来,也就避免了重复消息。

为更新的数据设置前置条件

如果在实际场景中没办法建立唯一的索引, 我们还可以转换一种思路:为要更新的数据设置前置条件,即当满足一定条件时才执行更新操作。

还是转账的例子,我们的消息可以携带着当前的账户余额,在执行更新操作前,先判断账户中的余额是否和消息中携带的余额一致, 一致才进行更新,否则不执行更新操作。

记录并检查操作

如果前两种方式都不适用,还可以适用最后这种记录并检查的通用方法,即在发送消息时,为每条消息制定一个全局唯一的 ID,消费时,要先根据全局唯一 ID 检查这条消息是否被消费过,如果没有消费过才会更新数据,然后将消费状态变成已消费。

总结

今天,我们学习了消费队列的重复消息的问题,并介绍了解决重复消息的核心方法----幂等性。也介绍了三种常用的方法,利用数据库唯一约束, 为更新的数据设置前置条件和记录并检查操作方法。实际使用中可以根据自己的实际使用场景采用合适的方案。







发布于: 2021 年 01 月 19 日阅读数: 48
用户头像

废材姑娘

关注

废材姑娘 2018.01.24 加入

大家叫我双儿,梦想着成为韦小宝的老婆 欢迎关注我的个人公众号----废材姑娘,回复“双儿”加我微信,让我们一起探索多彩的世界。

评论

发布
暂无评论
如何处理消息队列中的重复消息