本地消息 - 直播间送礼事件入MQ

9/14/2022

本地消息的实现方式,是在幂等设计的基础上做的一些补充。对于不支持自带重试的业务,可以用本地消息来处理。

# A.场景

直播业务中,很多业务是依赖于用户的打赏行为。

为了解耦,每次打赏都应当向 MQ 写入一个事件。其他业务监听这个打赏事件。

那么如何保证这个消息一定能被写入。

这是个典型的分布式事务场景,要保证送礼行为和写入MQ 必须都成功。

# B.分析

# 1.不能回滚

经过分析可知,这个事务的关键行为在于送礼,只要送礼能成功,MQ就应该必须成功。

哪怕 MQ 故障,也应该在MQ恢复后再送进去。

因此只要送礼成功,那么这个事务就必须成功,不能回滚。

# 2.业务无法自行重试

对于业务发起方(接口调用方)可以很方便重试的场景,可以用幂等实现+发起方重试,来保证最终一致性。

但是送礼场景,如果用户调用接口超时,下次调用将会是下次送礼,无法再重试。

因此只依赖幂等设计无法保证事务最终一致性。

# C.本地消息1 - 添加投递状态的字段

每次送礼必定有一个唯一的业务记录。我们在表中保留一个投递MQ的字段。

  • 插入记录的时候,该字段值为 未投递
  • 业务处理完成后,修改该字段值为 已投递

向MQ生产消息,和最后修改投递状态都有可能失败。

因此我们还需要一个补偿脚本,来扫描 “业务成功但投递状态为N” 的记录,进行重复投递,直至投递成功,并修改投递状态为Y。

假如向MQ投递成功,但修改投递状态时失败,会导致重复投递。

而消费端有可能还是并发消费。

因此消费端必须做好幂等和防并发的处理。

# D.本地消息2 - 另外建表保存投递状态

上一个方案中,需要在业务表单独保留一个字段。

其实这个与业务无关的字段,实在不应该添加在业务表里

而且对于某些已经存在的表来说,尤其是大表,改表风险很大

因此可以考虑为投递状态单独建一个表,然后将业务数据的插入和投递状态的插入放入同一个数据库事务

要注意的是,业务记录的唯一标识,在投递状态里也应该是唯一标识,并且建唯一索引。

# E.本地消息3 - 自产自消

第二种方案比第一种方案略好。

但如果业务逻辑本身已经很复杂,不太方便添加事务逻辑,可以采用自产自消的方式来处理。

生产消息的系统,自己消费自己产生的消息。

  • 添加一个消费者,消费MQ中的数据
  • 每消费到一个消息,就将其保存到消费记录表
  • 另起一个定时任务做补偿,将消费记录表与原本的业务记录做对账
  • 如果出现了gap,则表示生产消息有丢失,然后做消息补偿