logo

真实的队列秒杀架构——住范儿电商

在设计住范儿电商秒杀架构时,笔者对开团瞬间的高并发需求做过全链路的分析,经过各种推导、建模,发现高并发下单过程的单点最终只集中到了一个点上——库存。于是为了提升库存的处理能力,笔者把设计出的几种看起来很复杂的架构合并成了一个“略微复杂”的队列扣库存架构,如图 11-4 所示。接下来,我们对每一步进行详细解读。

图 11-4 住范儿电商平台真实秒杀架构图

1. 获取下单令牌

下单令牌可以看做一个用户在向后端发送了下单请求之后拿到的唯一查询凭证,此处笔者使用 MySQL 来承载,理论上用 Redis 也可以,推荐在性能压力更大的情况下使用 Redis。

2. 将下单任务插入进队列

第二步,我们使用下单令牌作为标记,将这个订单的下单任务插入到队列中,等待队列处理器进行处理。

3. 队列处理器执行下单任务

第三步,队列处理器取出一个下单任务,开始执行。需要注意的是,此处的下单队列处理器并不只有一个,只要保持一个比较低的并发量就可以实现对 MySQL 的保护。

4. 第一个 MySQL 事务:扣减 sku 的库存

随后,在队列处理器进程中,我们会利用 MySQL 事务来进行 sku 库存的真正扣减。

  1. 执行一段 SQL update set store_num = store_num - 1 where store_num >= 1,然后判断影响的行数,如果大于 0,说明这个 sku 的库存扣减成功了。
  2. 如果此时恰好别的队列处理器也在扣减同一个 sku 的库存,那我们的进程会等待,只要最终不超时即可。
  3. 我们需要对该订单包含的每一个 sku 都执行一条上述 SQL,任意一个 sku 库存扣减失败,手动将已经扣掉的库存全部回滚。根据住范儿电商系统运行的实际经验,遇到冲突全部回滚的概率非常低,下单成功率是非常高的。

需要说明的是,4、5 步的两个 MySQL 事务是被同一个 Try Catch 包裹住的,所以如果第一个事务成功而第二个事务失败的话,需要再新增一个队列任务,把库存再加回去。

5. 第二个 MySQL 事务:生成订单

如果所有 sku 扣库存操作都成功了,那我们就需要执行第二个新建订单的事务了。读者可能会疑惑,生成订单不是一个简单的 Insert 吗,为什么需要事务呢?因为真实世界中,下单的时候需要做的检查和数据的写入还是很多的,例如用户状态检查、收货地址有效性检查、活动和优惠有效性检查、商品有效性检查、店铺有效性检查、购物车对应的商品删除等操作;更极端一点,数据库服务器磁盘满了怎么办。所以下单这么重要的操作加上事务是非常有必要的,我们需要确保订单信息的自洽和完整。

在生成订单后,还有各种繁杂的生成快照和日志的需求,笔者在图中没有画,但是依然需要使用队列任务来异步进行。

6. 给客户端返回下单结果

下单完成后,就可以将下单令牌标记为下单成功了,客户端的轮询终于可以拿到结果了。之后,我们就可以唤起支付了。

为什么要使用两步事务

理论上前面的两个 MySQL 事务完全可以用一个事务来实现,但笔者设计成了两个,这是为了尽量减少多个队列处理线程之间的冲突:在最有可能发生冲突的 sku 扣减库存操作中,一个 MySQL 事务需要执行尽量少的任务,这对我们宏观上的系统总容量来说意义重大。

阅读数:1891      字数:1217 最后更新:2023-10-26 22:52:28