RocketMQ 重复消费问题 | 订单系统核心流程引入幂等性机制

消息重复 | 订单系统核心流程引入幂等性机制

客服接到用户的反馈:订单支付成功之后,用户收到了多张优惠券。按照正常业务来说,完成订单之后只会给用户发送一张优惠券,而现在发送了多张。

重复发放优惠券

如上图所示,订单系统与优惠券系统通过 RocketMQ 进行解耦,当发生消息重复消费问题时,表现出来的就是重复发送优惠券。

消息重复消费的问题是如何产生的?

重复消息的问题有可能是生产者重复发送消息到 MQ,导致 MQ 中有多条重复的消息;也有可能是消费者重复消费同一条消息:

  1. 接口调用超时,导致重复调用订单接口发送消息到 MQ
  2. 订单系统采用同步 + 消息重试的方式向 MQ 发送消息
  3. 优惠券系统正在处理消息,还没来得及返回 CONSUME_SUCCESS 状态就宕机了

订单系统创建完订单之后,需要通过支付系统进行支付。完成支付操作之后,由支付系统回调订单系统接口,此时才算是正式创建了订单,并由订单系统向 MQ 发送消息。

支付系统回调订单系统创建订单时,对于回调的接口我们不可能无限制等待其执行成功,会接口设置超时重试或者降级熔断策略。超时重试的情况下,支付系统认为订单系统创建订单失败,多次调用接口。实际上,订单系统执行成功并向 MQ 发送了消息,但是响应速度太慢。这种情况下,订单系统就向 MQ 发送了多条重复消息。

此外,订单系统向 MQ 发送的消息的时候,为了确保消息到达 MQ,可能会采用同步发送消息 + 反复重试的策略。当消息发送到 MQ 之后,MQ 应当给订单系统返回响应。我们知道网络环境是复杂的,极有可能发生网络抖动的问题,此时 MQ 存储了消息,但是响应在网络传输过程中丢失了。订单系统就会认为发送消息失败,继续重试发送消息,MQ 中就会有多条重复的消息。

最后,即使订单系统只发送了一条消息到 MQ 中,难道就万无一失了么?

优惠券系统消费消息时,我们需要有一个响应告诉 MQ 我们已经成功处理了该条消息。这个响应可以是自动响应,也可以是编码响应,在 RocketMQ 中就是提交 offset 给 MQ。以手动提交 offset 为例,也就是在发放优惠券的代码后面,返回 CONSUME_SUCCESS 状态。

在执行完发送优惠券的代码,还没来得及提交 CONSUME_SUCCESS状态时,该台机器宕机的话,即使消息已经被处理了,但是 MQ 会认为还没有处理。当优惠券系统重启之后任然会消费到这条消息。

接口幂等性 | 如何避免消费重复消费的问题?

前面描述了可能导致消息重复消费现象发生的一些的情景:接口调用超时、消息重试、消费者宕机。

这就要依靠接口的幂等性机制,比如你有一个接口,然后如果别人对一次请求重试了多次,来调用你的接口,你必须保证自己系统的数据是正常的,不能多出来一些重复的数据,这就是幂等性的意思。

接口的幂等性一般有两种实现方案:基于业务判断,基于状态判断。

基于业务判断

基于业务判断是指通过具有唯一性的字段进行判断,比如说注册用户的时候通过手机号或者身份证号判断用户是否已经注册过了,在订单系统中则可以订单号来进行判断。

订单在将消息发送到 MQ 之前,先查询 MQ 看看有没有一条 id = 1234的消息,如果有的话就不发送消息到 MQ。这样做的缺点就是使用 MQ 的性能会差很多。

基于状态判断

基于状态判断需要借助于 redis 缓存,每当订单系统往 MQ 发送一条消息,同时往 redis 里面插入订单的 id。以后发送消息之前都从 redis 中插叙是否已经发送过该消息。

这样的方案并不能 100% 保证幂等性,假设成功发送消息到 MQ 之后,下一步应该是往 redis 里面插入数据,但此时机器宕机了,就不会写入 redis 了。

最佳方案 —— 消费侧的业务判断法

在生产侧使用业务判断法需要查询 MQ 影响性能,在生产侧使用状态判断法无法 100% 保证幂等。更加推荐的做法是在消费侧通过业务判断法来保证幂等。

我们默认允许生产者会重复发送消息到 MQ,但是在消费侧查询 MySQL 数据库,通过字段(比如订单id)来判断消息是不是有被消费过。

猜你喜欢

转载自www.cnblogs.com/shuiyj/p/13195430.html
今日推荐