消息队列—消息不重复
首先先明确一下,消息不重复是不可能滴
因为为了确保消息的可靠性(消息不丢失),必须要保证消息能够重复发送和消费。
但是对于某些业务,比如银行转账的情况,如果消息一直重复,那么可能客户的钱都被扣光了,客服电话咚咚响,那就寄了,咋办?准备跑路吧!
虽然我们无法解决消息重复的问题,但是我们可以通过一些手段来实现即使是重复消费仍能和只消费一次的结果一样。
消息幂等消费
幂等是一个数学等式: f( f( x ) ) = f( x ) 。这个操作要如何应用到消息重复的解决呢?
###通过业务改造符合天然幂等写法
我们可以在项目初期设计的时候,考虑幂等的设计,例如:给订单数据库中加入 status 字段作为状态符,在修改订单的时候,我们就可以先修改订单的状态,因此可以使用如下的 SQL 语句进行更新操作:
update order set status = 1 where orderId = 666 and status = 0;
添加了 status = 0 的判断,如果消息已经被消费过,那么 orderId 666 这个订单的 status 肯定已经是 1 了。
通过这个操作,后续的消息进入会先判断 status 字段,如果 status = 1,就无法进行订单修改操作。
数据库唯一索引
项目很多都是后面迭代的,此时业务流程已经基本定型,很难改造。可以通过利用数据库的唯一索引约束来保证消息幂等。
我们可以通过消息的主键或者给消息添加事务ID,确保这个字段是全局唯一的。当应用对这个全局唯一字段对应的数据完成处理后,就在数据库中存储这条消息的处理记录,后续重复消息过来时,插入操作一定是会抛出错误的。因为出现了重复设置主键或者唯一标识的情况。
redis 唯一判断
还可以通过 redis 的 SETNX 命令实现幂等消费。
使用全局唯一值标记这条消息,如订单号或者事务ID,第一次执行消息的业务逻辑时,将其 SETNX 到 redis 中。
在每次业务逻辑执行之前,先使用 SETNX 来判断一下消息是否已经存在,如果是就直接返回,否则正常执行业务逻辑。
但是这里有一个问题,如果 redis 设定了唯一值,但是此时如果消费者宕机了,业务逻辑没有执行成功,那么即使领域给消费者顶上消费这条消息,由于 redis 仍存储着这个唯一值,会使得这条消息无法被消费,继而导致消息丢失。