• 分布式事务
所谓事务,通俗一点讲就是一系列操作要么同时成功,要么同时失败。而分布式事务就是这一系列的操作在不同的节点上,那要如何保证事务的ACID特性呢。


原子性(atomicity)。一个事务是一个不可分割的工作单位,事务中包括的操作要么都成功,要么都失败。


一致性(consistency)。事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。


隔离性(isolation)。一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。


持久性(durability)。持久性也称永久性(permanence),指一个事务一旦提交或回滚,数据库会对数据持久化的保存。

分布式事务案例
在电商背景下,以订单和库存系统之间的分布式事务为例,来介绍分布式事务基于消息队列的最终一致性方案。下单和扣减库存操作要么同时成功,要么同时失败,是事务的。如果只是本地事务的话,操作同一数据库,依赖数据库本身的事务特性,就可以完成。但是,对于分布式系统而言,订单系统和库存系统是操作不同的数据库的,那要如何实现这样的分布式事务。

基于消息队列的解决方案分析

我们使用RabbitMQ来实现分布式事务的最终一致性。

1. 业务分析

  1. 由于用户下单和支付并不是同时进行,一般都是下单成功后,30min内可以支付。那我们来思考这样一个问题,如果我们在下单成功就扣减库存的话,会不会有什么问题。
    • 恶意刷单。下单后不支付,导致其他人无法下单。
  2. 那如果支付成功后再扣减库存呢?
    • 在支付订单时,会出现库存不足,支付失败。
  3. 综合这两种情况考虑,我们在下单成功后先锁定库存,支付成功再去扣减库存,如果超时未支付,则解锁库存。

2. 业务流程

  1. 下单成功,锁定库存
  2. 订单支付超时,则需要自动关闭订单。
  3. 订单关闭,库存需要解锁。

3. 定时任务

如何确保下单后,30min内保留订单。最先想到的方法应该时定时任务。

  1. 定时任务使用的是系统时间,我们无法为每一个订单都生成一定时任务。
  2. 我想大家应该都发现了使用定时任务会带来的问题,那就是每一个订单的保留时间并不是一致的30min,订单保留的时间区间为(0min,60min)。即在定时任务即将到来前完成下单,和定时任务刚结束完成下单。

所以使用定时任务来完成这个操作是不可行。

4. RabbitMQ延时队列

利用消息的存活时间和死信来完成延时任务。

1. 消息的存活时间(TTL:Time To Live)

  1. RabbitMQ可以对队列和消息分别设置TTL。
  2. 对队列设置就是队列没有消费者连着的保留时间,也可以对每一个单独的消息做单独的设置。超过了这个时间,我们认为这个消息就死了,称之为死信。
  3. 如果队列和消息都设置了,那么会取小的。所以一个消息如果被路由到不同的队列中,这个消息的死亡时间有可能不一样(不同的队列设置)。
  4. 单个消息的TTL,才是实现延迟任务的关键。可以通过设置消息的expiration字段或者x-message-ttl属性来设置时间,两者是一样的效果。

2. 死信交换机(DLX:Dead Letter Exchanges)

  1. 一个消息如果满足如下条件,就会进入死信路由(不是队列,一个路由可以对应多个队列)
    • 一个消息被消费者拒收,并且reject方法的参数里requeue是false。也就是说不会被再次放在队列里,被其他消费者使用。
    • 消息的TTL到了,消息过期了。
    • 队列长度限制满了,排在前面的消息会被丢弃或者扔到死信路由上。
  2. 在某一个设置Dead Letter Exchange的队列中有消息过期了,会自动触发消息的转发,发送到Dead Letter Exchange中去
  3. 先控制消息在一段时间后变成死信,然后控制变成死信的消息被路由到某个指定的交换机。二者结合就可以实现一个延时队列。

在下单成功后,发送一条消息到死信队列,经过一段时间(TTL),死信路由到订单释放队列,订单服务监听到消息释放放订单。

5. 分布式解决方案

1. 对以下情况,库存需要解锁。

  1. 首先之前提到的,订单服务在远程调用成功后,发生异常,导致订单回滚,库存也需要解锁。
  2. 订单延时取消,或者主动取消订单,都需要解锁库存。
  3. 订单服务下单成功后,订单服务宕机,超过订单支付时间,仍然无法恢复,导致无法发送消息通知库存服务解锁库存,故需要自动解锁库存。

2. 解决方案

针对以上情形,库存解锁的方案。

  1. 第一种情况,可以利用库存自动解锁来解决。库存锁定时发送消息到延时队列,经过TTL后,成为死信路由到库存解锁队列,库存服务监听到消息后,解锁库存。
    • 不能在订单未支付时就解锁库存,所以库存自动解锁的延迟时间应该大于订单延时取消的时间。
  2. 对于第二种情况,主动取消或者延时取消,都可以通过库存的自动解锁来完成库存的解锁。
  3. 自动解锁时,需要判断订单的状态,只有为取消状态的订单才可以解锁库存。但是这样仍然会存在问题。
    • 订单服务卡顿,导致订单状态消息一直改不了,而库存消息先到期,查询订单状态为新建状态,不解锁库存,并删除消息,导致库永远无法解锁。
    • 解决:订单超时取消的同时,发送订单取消的消息到队列,库存服务监听该消息,则解锁库存。
    • 为了防止重复解锁,需要满足幂等性。

若需要整套项目代码,提供收费指导搭建此项目服务(费用仅:30元),请联系微信:

发表回复

您的电子邮箱地址不会被公开。