本地消息表实现最终一致性

背景

传统的单体应用不会横跨多个数据库,可以通过单机事务保证一致性。然而在海量数据的场景下,我需要对数据库做拆分,即分库分表,而CobarMyCat这类分库分表中间并不提供分布式事务的特性,并且基于二阶段提交的分布式事务性能较差,对于大多数业务场景来说,并不需要强一致,只需要保证最终一致性即可。

实践

下面我们举个下订单的场景,总共有3个实体,商品用户订单,我们按照user_id来sharding。所以相同user_id用户订单在同一个物理库下,而商品表中不存在user_id,所以商品表在不同的物理库下。

下订单的场景,主要涉及到两个事务操作,扣减库存生成订单,因为两个操作涉及不同的数据库,所以无法保证强一致性。

我们可以通过本地消息表,来实现最终一致性,具体流程如下图:

alt text

  • 调用外部服务,生成全局唯一的交易流水号trans_id
  • 事务一:1) 扣减库存 2) 根据流水单号,生成对应商品的冻结记录。消息表主要由商品ID交易流水号冻结数消息状态这四个字段构成,因为消息表和商品表在同一个物理库下,所以TX1中的操作1和操作2是可以构成事务操作的。冻结记录的状态有三种:已冻结释放已售出释放未售出。冻结记录的初始状态为已冻结
  • 事务一如果成功,则进行事务二;如果事务一失败,则直接返回。
  • 事务二:根据交易流水号trans_id生成订单,订单的状态有三种:未支付已支付超时,订单的初始状态为未支付
  • 若订单创建成功,则进行后续的支付流程。
  • 如果事务二失败,由于网络抖动超时等原因,不一定是真的生成订单失败,即 在事务二失败的情况下,可能生成了订单,也可能确实没有生成订单。
  • 定时任务一:设置一个每隔15分钟的定时任务(即一个订单必须在15分钟内完成支付),从订单表里捞出最近半小时内的所有订单,对每一个订单做如下处理:若订单超时未支付,开启事务SELECT FOR UPDATE 锁住该订单,即用悲观锁阻止用户对订单进行支付等操作,然后通过订单的trans_id去冻结表更新对应冻结记录的状态,置为释放未售出,并回滚商品数量,回滚商品的操作完成后,将订单状态置为超时,若事务中调用的回滚商品数量的服务失败,则可以发出报警人工处理,或通过更长时间的定时任务去处理;若订单为已支付,则将冻结表中记录的状态置为释放已售出
  • 定时任务二:因为存在事务一成功,而事务二的订单确实没有创建成功的情况,这样会冻结一部分商品的数量,所以可以捞取出 创建超过10分钟 状态为已冻结的所有冻结记录,根据每个冻结记录的trans_id去订单表查询,若不存在对应的订单,则将冻结记录的状态更新为释放未售出,并回滚商品数量。
  • 另一个需要注意的点,在定时任务一中,对于超时未支付的订单,会先回滚冻结表,然后将订单状态置为超时,但这最后一步将订单置为超时可能会失败,这样会出现不一致的状态,即订单状态为未支付,而冻结记录的状态为释放未售出。所以,在支付的时候需要做一个前置校验,检查冻结记录的状态是否为已冻结,若不是,则拒绝支付。

变种

在上面这种模型的基础上,还有一种变种,如下图:

alt text

即在TX2失败的情况下,跳转到TX3

  • 根据trans_id查询订单,若订单不存在,则直接将冻结记录置为释放未售出,并回滚库存;若订单存在,则说明TX2因为网络抖动等原因而失败,其实订单创建成功,则进行正常的支付流程。
  • 需要注意的是:根据trans_id查询订单的时候,一定要开启事务,这样才会强制走主库,若不开启事务,则会走备库,因为MySQL主从同步延迟的问题,备库很可能无法查询到订单,从而回滚库存,这显然是错误的。
变种的优点

将定时任务的压力均匀地分配到每一次调用中,提高数据库的可用性。

总结

在不需要强一致性的业务场景下,都可以通过定时任务+幂等操作来实现最终一致性。

以上。

原文链接

https://segmentfault.com/a/11...

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
RocketMQ 是一个开源的分布式消息队列系统,它支持可靠消息传输和最终一致性。 RocketMQ 的可靠消息传输是通过消息的持久化和复制来实现的。当生产者发送消息时,消息会被持久化到本地磁盘,并且会根据配置的复制因子将消息复制到其他的 Broker 节点上。这样即使某个 Broker 节点出现故障,消息仍然可以从其他节点获取。 RocketMQ 通过使用主题(Topic)和分区(Partition)的概念来实现消息的负载均衡和扩展性。一个主题可以由多个分区组成,每个分区可以在不同的 Broker 节点上存储。这样可以保证同一个主题的消息在多个节点上进行分布式存储,提高了系统的可靠性和可扩展性。 最终一致性是指当消息被消费者消费后,消息队列系统会保证所有消费者看到的消息顺序是一致的。RocketMQ 使用了消息消费者组(Consumer Group)的概念,每个消费者组内的消费者共同消费一个主题的消息,系统会确保每个消费者按照相同的顺序消费消息。 此外,RocketMQ 还提供了事务消息和顺序消息等特性来满足不同业务场景下的需求,进一步提高了消息传输的可靠性和一致性。 总结来说,RocketMQ 通过持久化、复制、负载均衡、分区和消费者组等机制来实现可靠消息传输和最终一致性。这使得 RocketMQ 在分布式系统中被广泛应用于解决可靠消息传输的需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值