问题:消息传递过程中,消息传输失败,发送方会执行重试,重试的过程中就有可能会产生重复的消息,对使用消息队列的业务系统来说,如果没有对重复消息进行处理,就有可能导致系统的数据出现问题。
在MQTT协议中,给出了三种传递消息时能够提供的服务质量标准,服务质量从低到高:
- At most once
- At least once
- Exactly once
用幂等行解决重复消息问题
一般解决重复消息的办法是,在消费端,让我们消费消息的操作具备幂等性:
幂等(ldempotence) 本来是一个数学概念,他是这样定义:
如果一个函数f(x)满足:f(f(x))=f(x),则函数f(x)满足幂等性。
一个幂等操作特点是,其任意多次执行所产生的影响与一次执行的影响是相同。例子:将账户X的余额设置为100元。
从系统的影响结果来说:At least once + 幂等消费 = Exactly once。
为了处理消费过程的重复消息,从业务逻辑设计上上手,将消费的业务逻辑设计成具备幂等性的操作。
常用的设计幂等操作的方法:
1.利用数据的唯一约束实现幂等
2.为更新的数据设置前置条件
3.记录并检查操作
具体的实现方法是,在发送消息时,给每条消息指定一个全局唯一的ID,消费时,先根据这个ID检查这条消息是否被消费过,如果没有消费过,才更新数据,然后将消费状态置为已消费。
原理和实现是不是很简单?其实一点都不简单,在分布式系统中,这个方法其实是非常难实现的。首先,给每个消息指定一个全局唯一的ID这是一件不那么简单的事儿,方法很多,但都不是太好同时满足简单,高可用和高性能,或多或少都要牺牲。更加麻烦的是,在”检查消费状态,然后更新数据并且设置消费状态“中,三个操作操作必须作为一组操作保证原子性,才能真正实现幂等,否则会出BUG。
思考:
为什么大部分消息队列选择只提供At least once的服务质量,而不是级别更高的Exactly once呢?
我觉得最重要的原因是消息队列即使做到了Exactly once级别,consumer也还是要做幂等。因为在consumer从消息队列取消息这里,如果consumer消费成功,但是ack失败,consumer还是会取到重复的消息,所以消息队列花大力气做成Exactly once并不能解决业务侧消息重复的问题。
网友评论