面试题:如何保证消息的幂等性?
现在微服务开发中为了满足限流消峰、减少系统之间的耦合等实际业务的需要,于是系统中往往会引入了MQ,加入了MQ之后如何保证消费者的消费幂等性便是需要解决的问题了。
1、数据库幂等性问题
幂等性是数学上的一个概念,在我们开发中可以理解成就是多次执行某个方法并且得到结果是一样的,那么我们就认为这个过程就是幂等性。如下就是一些常见的幂等性的案例:
查询是具备天然的幂等性(不考虑数据更新/删除的情况下,多次查询始终是一个结果)。
如上所示的更新/删除都是具备幂等性的,无论这个更新执行多少次,最终的结果都是一样的。
如上所示的添加也是具备幂等性的,因为userId字段是唯一键,重新添加数据库会提示异常的。
非幂等性的就是指多次执行的结果会不一样,如下所示是常见的一些非幂等性的案例:
如上所示的案例是非幂等性的,因为执行多次的结果是不一样的。所以针对这些非幂等性操作需要做单独的处理,保证一次请求只会执行一次。
2.接口幂等性
2、MQ出现幂等性问题的原因
(1)生产者重复生产
由于网络原因,第一次生产者的发送的消息MQ已经接收到了,但是给服务响应的时候超时了,导致服务器再次投递了相同的消息,在消息队列中存在了两条相同的消息。下游的消费者就需要消费两次个相同的消息,消费者需要自己做幂等性的处理。
(2)MQ重试机制
消费者第一次消费id=1的消息时候,此时消费此时消费成功但是响应MQ的时候超时导致MQ认为当前的消息消费者没有成功消费,过一段时间之后重新投递给消费者消费,那么就导致同样的消息消费者消费了两次,此时消费者需要自己做幂等性的处理。
3、MQ的幂等性解决方案
幂等性保证的基本原则
生产端:
状态检查:在消息发送前,先查询数据库,确认此消息是否已被处理过(一般通过单据状态)。如果是,则直接忽略;否则,继续处理,并在处理完成后更新消息状态为已处理。
例如: 订单状态 10 已支付 20 已推送仓库
如果需要再次推送,只需要推送状态是10的订单
消费端:
唯一标识:每个消息都携带一个业务ID(BizId)business[ˈbɪznəs],如订单号、交易流水号等,以便在消费端能够识别重复的消息。
例如:member.register.ok.+用户id
@RabbitListener(queues = "send_point")
public void consume(RegisterOk msg) {
String bizId = msg.getBizId();
//1、查看 jd_log 表里面有没有这个 refId = bizId
//2、如果有 说明这个单据已经处理过了,不再处理,只需要记录一个log.warn(消息)
//3、如果没有 说明这个单据没有处理过,需要处理,写业务
//4、书写一个独立的方法,并开启事务,至少两张表 (业务表和日志表)
log.debug("送积分->{}",msg);
}