【开发系列】秒杀系统的设计

简介: 【开发系列】秒杀系统的设计
1、整体的秒杀设计
2、定时上架商品和预库存
3、秒杀商品展示
4、用户下单
   4.1 每场每个商品用户只能下一单的处理
   4.2 商品预库存
   4.3 用户异步下单
5、订单支付

原创 龙虾编程

现在许多商家了吸引顾客都会使用低价的秒杀商品来做活动,下图展示了京东的秒杀活动页面:

秒杀活动在开始的时候,用户根据自己的需要下单自己喜欢的商品,此时服务器瞬间会打入大量的流量进来,如何设计一套可以扛住高并发的秒杀系统来支撑秒杀活动?下面我们具体的做秒杀系统的设计。

1、整体的秒杀设计

秒杀系统中我们使用了LVS和Nginx来扛住第一波流量,并且在Nginx做一波限流控制;然后Nginx将请求转发到网关上,由网关将请求分发到处理服务器上(秒杀服务),接下来由处理服务器做相关的操作。下单中使用的是异步下单加WebSocket推送下单成功的消息到客户端上,使用Redis缓存来缓存商品信息、预库存信息和用户下单成功之后的订单信息。

2、定时上架商品和预库存

在秒杀活动中为了提高秒杀系统的性能,将秒杀商品、商品的库存信息提前添加到Redis中。

在秒杀活动的当天凌晨,将秒杀商品信息和库存都放到Redis中缓存起来,由于每场秒杀活动有不同的商品,所以针对不同的场次将商品信息使用Hash数据结构缓存起来。

3、秒杀商品展示

秒杀活动开始的时候,用户访问商品的页的时候,直接从redis中将缓存的商品信息给用户,这样就不用走数据库查询,提高了系统的吞吐量。

4、用户下单

4.1 每场每个商品用户只能下一单的处理

用户下单的时候,首先的要去判断一下用户本场是否已经下过单(限定每场活动的每个商品用户只能买一件,防止某个用户同时多个请求来到秒杀系统,为此在Redis中增加一个Set数据结构来记录用户本场针对某个商品已经下过单。如果Set中存在了本场次当前商品已经下过单就不可以让用户继续下单,反之可以下单。

但是高并发下此方式不能完全避免用户多次下单的问题,如下:

在t1时刻,两个线程同时请求用户的下单,此时发现没有当前的用户没有下单,放行两个线程到库存检查这一步,此时如果库存足够会让线程下单,那么就造成了同一个用户针对这个商品就多次下单了。解决方案就是在数据库中增加一个唯一键来兜底。唯一键可以使用时间+商品的id+场次来定义,如果数据插入失败就需要回补库存到Redis中。

4.2 商品预库存

商品的预库存主要作用是阻挡大部分的无效请求,因为秒杀商品往往数量比实际用户的请求数量小很多(如秒杀商品100个,用户在某个时间段中来了500个请求,此时真正要处理的请求其实就是100个,其余的400个请求多是多余的请求,因为库存不足无法下单),使用预库存可以让无效请求直接返回而不需要到订单服务中的处理。

如果存在用户下单后没有付款或者同一个用户多个请求的情况下,会出现预库存被占用导致想要购买的用户无法下单,此时需要回补预库存。回补库存我们直接采用将数据库的剩余库存数量直接放入到预库存队列中。

高并发这种方案可能会出现预库存数量比实际真实的剩余库存要多的情况,如下:

A线程和B线程回补库存的时候,此时发现真实库存是100个,B线程CPU的时间片恰好用完了,A线程首先执行回补100个库存到Redis,然后C下单成功后扣减了1个预库存,随后B线程获得了时间片,然后将100库存又回补到Redis中,此时真实的库存其实是99个,但是B线程回补多1个预库存;针对这种情况需要数据库层做兜底来保证不会超卖(创建订单的时候兜底处理)。

4.3 用户异步下单

如果用户在本场本商品没有下单,并且预库存扣减成功(实质是利用Redis的自减操作,如果自减后的结果大于0就库存是足够的)那么允许用户下单,此时我们利用MQ来异步下单。

MQ会发送两个消息,一个是实时的下单消息,一个是延迟的检查规定时间用户是否支付的消息(如果超时未支付就取消本单,并且回补预库存);MQ消息发送成功之后通知客户已经在下单队列中等待处理。

订单服务开始消费消息队列中的消息,首先会创建订单基本的信息、下单的商品信息等,创建成功之后订单信息会保存到数据库中并且数据还会同步一份到Redis中用于用户查询订单的信息。最后通过WebSocket技术将用户下单的成功的消息推送给客户端,方便用户支付。

给用户创建订单的时候底层需要使用乐观锁机制,先去扣减秒杀商品的库存(如update item set stock = stock - 1 where id = #{itemId} and stock > 0),如果扣减影响行数大于0,那么就代表扣减库存成功,可以让用户下单,这样可以保证商品不被超卖。

延迟消息(如延迟15分钟)过来之后,查询一下当前订单是否已经被支付,如果订单没有支付就将订单的状态修改成交易关闭。在修改订单的状态的中,极端情况下出现超时未支付的请求和用户支付请求同时来操作订单,如下:

此时如果不做预防措施,订单就会出现状态错乱的现象,为了解决这个问题,采用订单的状态机来处理。如下状态的转换:

超时未支付的请求和用户支付请求谁先操作谁为准(底层的Sql语句:update order set status = #{status} where order_id = #{orderId} and status = '待支付'),这样另一个线程的就操作失败,我们需要打印日志抛出异常提示。

5、订单支付

服务端创建订单成功之后,用户开始支付,支付走三方(如支付宝、微信)支付成功后需要及时修改订单的状态和同步Redis中订单的状态。

总结:

(1)秒杀系统设计中采用LVS+Nginx方式来提高系统的并发能力

(2)采用将商品信息、商品库存缓存Redis的方式来提高系统的响应和拦截无效的请求

(3)通过异步下单(MQ)的方式来给流量消峰处理,维持系统的稳定

(4)采用WebSocket的方式将下单成功的消息推送给客户端

(5)超卖问题采用乐观锁的机制做兜底处理

(6)针对用户超时未支付或者多次下单同一个商品导致商品少卖的问题,采用真实库存回补的方式来处理预库存(Redis的中的库存)

相关实践学习
快速体验阿里云云消息队列RocketMQ版
本实验将带您快速体验使用云消息队列RocketMQ版Serverless系列实例进行获取接入点、创建Topic、创建订阅组、收发消息、查看消息轨迹和仪表盘。
消息队列 MNS 入门课程
1、消息队列MNS简介 本节课介绍消息队列的MNS的基础概念 2、消息队列MNS特性 本节课介绍消息队列的MNS的主要特性 3、MNS的最佳实践及场景应用 本节课介绍消息队列的MNS的最佳实践及场景应用案例 4、手把手系列:消息队列MNS实操讲 本节课介绍消息队列的MNS的实际操作演示 5、动手实验:基于MNS,0基础轻松构建 Web Client 本节课带您一起基于MNS,0基础轻松构建 Web Client
相关文章
|
消息中间件 数据库 RocketMQ
分布式事务常见解决方案
分布式事务常见解决方案
3073 0
|
存储 编解码 算法
H.264编码及AAC编码基础 1
H.264编码及AAC编码基础
926 0
|
8月前
|
人工智能 自然语言处理 搜索推荐
想让豆包在答案里提到你的官网?这三个步骤缺一不可
想让豆包引用你的官网?必须做好三步:一是将内容模块化、结构清晰,便于AI理解;二是通过专业资质、数据出处和结构化标记提升权威性;三是持续监测引用效果,优化内容策略。AI搜索时代,被“看见”才能赢得客户。
1417 1
|
消息中间件 缓存 监控
如何设计一个秒杀系统,(高并发高可用分布式集群)
【7月更文挑战第4天】设计一个高并发、高可用的分布式秒杀系统是一个非常具有挑战性的任务,需要从架构、数据库、缓存、并发控制、降级限流等多个维度进行考虑。
804 1
|
消息中间件 中间件 Kafka
RocketMQ源码(二)消息消费的模式到底是Push还是Pull?
RocketMQ源码(二)消息消费的模式到底是Push还是Pull?
643 1
|
设计模式 缓存 前端开发
什么是幂等性?四种接口幂等性方案详解!
本文深入分布式系统中的幂等性问题及其解决方案,涵盖数据库唯一主键、乐观锁、PRG模式和防重Token等方法,关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
什么是幂等性?四种接口幂等性方案详解!
|
缓存 NoSQL 数据库
【高并发】秒杀系统设计思路
【高并发】秒杀系统设计思路
464 0
|
消息中间件 监控 供应链
深度剖析 RocketMQ 事务消息!
本文深入探讨了 RocketMQ 的事务消息原理及其应用场景。通过详细的源码分析,阐述了事务消息的基本流程,包括准备阶段、提交阶段及补偿机制。文章还提供了示例代码,帮助读者更好地理解整个过程。此外,还讨论了事务消息的优缺点、适用场景及注意事项,如确保本地事务的幂等性、合理设置超时时间等。尽管事务消息增加了系统复杂性,但在需要保证消息一致性的场景中,它仍是一种高效的解决方案。
1423 2
|
消息中间件 调度 RocketMQ
【RocketMQ系列六】RocketMQ事务消息
【RocketMQ系列六】RocketMQ事务消息
3726 1