本地消息表方案
基于消息补偿的一致性方案主要支持事务的消息队列和本地消息表等。本篇文章主要介绍下恩地消息表,本地消息表的方案最初由ebay的工程师提出,核心思想是将分布式事务拆分成本地事务进行处理。本地消息表实现最终一致性。
具体业务处理流程如下图:
- 步骤1和2,系统收到用户下单请求,将订单业务数据写入订单表中,同时把该订单对应的消息数据写入本地消息表中,订单表与本地消息表为同一个数据库,更新订单和存储消息为同一个本地事务,数据库事务处理,要么都成功,要么都失败。
- 步骤345,订单服务发送消息到消息队列,库存服务收到消息,进行库存业务操作,更新库存数据
- 步骤6和7,返回业务处理结果,订单服务收到结果后,将本地消息表中的数据设置完成状态或者删除数据。
- 步骤8,另起定时任务,定时扫描本地消息表,看是否有未完成的任务,有则重试。
案例
下面以注册送积分为例来说明 :
下例共有两个微服务交互,用户服务和积分服务,用户服务负责添加用户,积分服务负责增加积分。
交互流程如下 :
1、用户注册
用户服务在本地事务新增用户和增加“积分消息日志”。(用户表和消息表通过本地事务保证一致)
begin transaction;
// 1.新增用户
// 2.存储积分消息日志
commit transation;
这种情况下,本地数据库操作与存储积分消息日志处于同一事务中,本地数据库操作与记录消息日志操作具备原子性。
2、定时任务扫描日志
如何保证将消息发送给消息队列呢?
经过第一步消息已经写到消息日志表中,可以启动独立的线程,定时对消息日志表中的消息进行扫描并发送至消息中间件,在消息中间件反馈发送成功后删除该消息日志,否则等待定时任务下一周期重试。
3、消费消息
如何保证消费者一定能消费到消息呢?
这里可以使用MQ的ack(即消息确认)机制,消费者监听MQ,如果消费者接收到消息并且业务处理完成后向MQ发送ack(即消息确认),此时说明消费者正常消费消息完成,MQ将不再向消费者推送消息,否则消费者会不断重试向消费者来发送消息。
积分服务接收到“增加积分”消息,开始增加积分,积分增加成功后消息中间件回应ack,否则消息中间件将重复投递此消息。
由于消息会重复投递,积分服务的“增加积分”功能需要实现幂等性。
总结:
上诉的方式是一种非常经典的实现,基本避免了分布式事务,实现了“最终一致性”。但是,关系型数据库的吞吐量和性能方面存在瓶颈,频繁的读写消息会给数据库造成压力。所以,在真正的高并发场景下,该方案也会有瓶颈和限制的。