本地消息 - 直播间送礼事件入MQ
本地消息的实现方式,是在幂等设计的基础上做的一些补充。对于不支持自带重试的业务,可以用本地消息来处理。
# A.场景
直播业务中,很多业务是依赖于用户的打赏行为。
为了解耦,每次打赏都应当向 MQ 写入一个事件。其他业务监听这个打赏事件。
那么如何保证这个消息一定能被写入。
这是个典型的分布式事务场景,要保证送礼行为和写入MQ 必须都成功。
# B.分析
# 1.不能回滚
经过分析可知,这个事务的关键行为在于送礼,只要送礼能成功,MQ就应该必须成功。
哪怕 MQ 故障,也应该在MQ恢复后再送进去。
因此只要送礼成功,那么这个事务就必须成功,不能回滚。
# 2.业务无法自行重试
对于业务发起方(接口调用方)可以很方便重试的场景,可以用幂等实现+发起方重试,来保证最终一致性。
但是送礼场景,如果用户调用接口超时,下次调用将会是下次送礼,无法再重试。
因此只依赖幂等设计无法保证事务最终一致性。
# C.本地消息1 - 添加投递状态的字段
每次送礼必定有一个唯一的业务记录。我们在表中保留一个投递MQ的字段。
- 插入记录的时候,该字段值为 未投递
- 业务处理完成后,修改该字段值为 已投递
向MQ生产消息,和最后修改投递状态都有可能失败。
因此我们还需要一个补偿脚本,来扫描 “业务成功但投递状态为N” 的记录,进行重复投递,直至投递成功,并修改投递状态为Y。
假如向MQ投递成功,但修改投递状态时失败,会导致重复投递。
而消费端有可能还是并发消费。
因此消费端必须做好幂等和防并发的处理。
# D.本地消息2 - 另外建表保存投递状态
上一个方案中,需要在业务表单独保留一个字段。
其实这个与业务无关的字段,实在不应该添加在业务表里。
而且对于某些已经存在的表来说,尤其是大表,改表风险很大。
因此可以考虑为投递状态单独建一个表,然后将业务数据的插入和投递状态的插入放入同一个数据库事务。
要注意的是,业务记录的唯一标识,在投递状态里也应该是唯一标识,并且建唯一索引。
# E.本地消息3 - 自产自消
第二种方案比第一种方案略好。
但如果业务逻辑本身已经很复杂,不太方便添加事务逻辑,可以采用自产自消的方式来处理。
生产消息的系统,自己消费自己产生的消息。
- 添加一个消费者,消费MQ中的数据
- 每消费到一个消息,就将其保存到消费记录表
- 另起一个定时任务做补偿,将消费记录表与原本的业务记录做对账
- 如果出现了gap,则表示生产消息有丢失,然后做消息补偿