秒杀系统设计

秒杀系统

秒杀系统的设计

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

图片

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

下面我们具体的做秒杀系统的设计:

1 整体设计:

图片

​ 秒杀系统中我们使用了LVS和Nginx来扛住第一波流量,并且在Nginx做一波限流控制

​ 然后Nginx将请求转发到网关上,由网关将请求分发到处理服务器上(秒杀服务)

​ 接下来由处理服务器做相关的操作

​ 下单中使用的是异步下单加WebSocket推送下单成功的消息到客户端上

​ 使用Redis缓存来缓存商品信息、预库存信息和用户下单成功之后的订单信息

1.1 设计的知识点:
1 LVS :
  • Linux Virtual Server(LVS)是一个开源的负载均衡解决方案,它通常用于构建高性能、可扩展的网络服务架构。LVS 主要由 Linux 内核的功能和一些用户空间的工具组成,通过这些工具和技术可以实现请求的负载均衡和高可用性
  • LVS 的核心是在 Linux 内核中实现的四种负载均衡调度算法:
    • Round-Robin(轮询): 将客户端请求依次分配给各个后端服务器,按照服务器列表的顺序循环分发请求,适用于服务器性能相近的场景。
    • Weighted Round-Robin(加权轮询) :在轮询的基础上增加了权重的概念,可以根据服务器的性能或者负载情况,动态调整每个服务器的权重,使得性能较好的服务器能够处理更多的请求。
    • Least-Connection(最小连接数) : 将客户端请求分配给当前连接数最少的后端服务器,以达到负载均衡的效果,适用于服务器性能不均衡或者负载不均匀的场景。
    • Weighted Least-Connection(加权最小连接数) :在最小连接数的基础上增加了权重的概念,可以根据服务器的性能或者负载情况,动态调整每个服务器的权重,使得性能较好的服务器能够处理更多的请求。
  • 除了以上的负载均衡算法,LVS 还提供了高可用性的功能,通常通过心跳检测和故障转移来实现。常见的心跳检测工具包括 Keepalived 和 heartbeat,它们可以监测后端服务器的健康状态,并在服务器出现故障时将流量转移到其他正常的服务器上,确保服务的连续性和可靠性。
2 Nginx :

​ Nginx 通常被用作反向代理服务器,用于承担大量并发请求的负载和流量分发。Nginx 具有高性能、高并发处理能力和低资源消耗的特点,使其成为了很多系统架构中的首选组件之一。以下是使用 Nginx 扛住第一波流量的一些优势和常见做法

  • **高性能和高并发处理能力:**Nginx 是一个轻量级的、事件驱动的服务器, 是异步非阻塞的处理方式,能够高效的处理大量的并发连接。

  • 反向代理和负载均衡 :能够将客户端的请求转发到后端的多台服务器上进行处理。通过负载均衡算法,Nginx 可以将请求均匀地分发到多台服务器上,从而提高系统的整体性能和可用性

  • 静态文件服务:Nginx 也被广泛用作静态文件服务器,能够快速地响应静态资源(如图片、CSS、JavaScript 文件等)的请求。这可以减轻动态请求的压力,提高系统的整体性能。

  • 缓存机制: Nginx 提供了丰富的缓存机制,可以将动态页面内容缓存起来,减少对后端服务的请求压力,提高响应速度。

  • 安全性和可靠性: 可以进行访问控制、反向代理、SSL 加密等安全操作,保护系统免受恶意攻击。同时,Nginx 的稳定性和可靠性也使其成为了大规模系统架构中的重要组成部分。

3 请求转发到网关,网关将请求分发到处理服务器
  • Nginx转发到网关
    • 当用户发起请求时,请求首先 被 Nginx接收到
    • Nginx作为方向代理服务器,根据配置的规则将请求转发的给网关服务
  • 网关服务接受请求
    • 网关服务作为系统的入口,接收来自Nignx的请求
    • 网关服务可能会进行一些预处理,如验证请求参数、鉴权等操作,以确保请求的合法性和安全性。
  • 请求分发到处理服务器上:
    • 经过预处理后,网关将请求分发到后端处理服务器上,即秒杀服务
    • 这些处理服务器可能部署在集群中,每台服务器都能处理请求,并且具备相同的秒杀服务功能。
    • 网关可能会使用负载均衡算法,如轮询、加权轮询、最小连接数等,将请求均匀地分发到各个处理服务器上,以实现负载均衡,提高系统的整体性能和可用性。
  • 处理服务器执行秒杀服务:
    • 处理服务器收到请求后,执行相应的秒杀服务逻辑。
    • 这包括验证用户的秒杀资格、检查商品库存、生成订单等操作。
    • 如果秒杀成功,处理服务器会返回相应的成功响应给网关。
  • 网关将响应返回给用户:
    • 网关接收到处理服务器的响应后,将响应返回给用户。
    • 如果秒杀成功,网关可能还会触发相应的后续操作,如向用户推送秒杀成功的消息等。
4 异步下单加WebSocket推送下单成功

​ 异步下单加 WebSocket 推送是一种常见的实现方式,用于构建实时性较高的在线交易系统或者其他需要实时更新的应用程序。下面是一种可能的实现流程:

  • 异步下单:
    • 当用户提交订单时,应用程序不立即进行订单处理,而是将订单信息异步发送到后台进行处理。这可以通过消息队列来实现,比如使用 RabbitMQ、Kafka 等消息中间件,将订单消息发送到消息队列中,然后由后台服务进行消费处理。
    • 异步处理订单可以提高系统的并发能力和可伸缩性,因为订单处理不会阻塞用户请求的响应。
  • WebSocket 推送:
    • 当订单状态发生变化时,后台服务可以将订单状态的变化信息推送给客户端,以 WebSocket 的形式实时更新客户端页面
    • WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议,能够实现客户端和服务器之间的实时数据传输。通过 WebSocket,客户端可以订阅订单状态的更新,当订单状态发生变化时,服务器即可主动向客户端推送最新的订单状态信息。
5 Redis缓存来缓存商品信息、预库存信息和用户下单成功之后的订单信息

是为了提高系统的性能、减轻数据库压力,并且提供更快速的响应时间。下面是这些信息如何被缓存的一般流程 :

  1. 商品信息缓存:
    • 当用户访问秒杀页面时,秒杀系统可以首先尝试从 Redis 缓存中获取商品信息,如商品名称、描述、价格等。
    • 如果商品信息在缓存中不存在,系统则会从数据库中获取,并将其存储到 Redis 缓存中,以便下次访问时可以直接从缓存中获取,减少数据库查询次数。
  2. 预库存信息缓存:
    • 在秒杀开始前,系统会将商品的预库存信息存储到 Redis 缓存中。
    • 预库存信息包括每个商品的剩余库存数量,系统可以定时更新 Redis 中的库存信息,确保秒杀活动开始时,缓存中的库存与数据库中的库存保持一致。
  3. 用户下单成功后的订单信息缓存:
    • 当用户成功秒杀到商品并下单成功后,系统会生成相应的订单信息。
    • 系统将订单信息存储到数据库中,并同时将订单信息存储到 Redis 缓存中,以便快速地获取订单状态或者订单详情。
    • 订单信息在缓存中可能会设定一定的过期时间,以确保缓存中的数据与数据库中的数据保持一致性,并且避免缓存过期导致的数据不一致问题。

2 定时上架商品和预库存

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

图片

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

3 秒杀商品展示

图片

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

4 用户下单

图片

4.1 每场每个商品用户只能下一单的处理
  • 用户下单的时候,首先的要去判断一下用户本场是否已经下过单

​ 防止某个用户同时多个请求来到秒杀系统,为此在Redis中增加一个Set数据结构来记录用户本场针对某个商品已经下过单 ,如果Set中存在了本场次当前商品已经下过单就不可以让用户继续下单,反之可以下单。

PS: :唯一性: Set 数据结构中的元素是唯一的,每个用户在本场次活动中针对某个商品只能存在一个元素。这样可以确保每个用户只能购买一件商品,避免重复购买。

Set 数据结构是无序的,这意味着不会有重复的元素存在,并且不会受到元素插入的顺序影响。这对于记录用户下单状态来说是很好的特性,因为我们只关心用户是否已经下单,而不需要考虑订单的顺序。

Set 数据结构提供了丰富的操作方法,如添加元素、删除元素、判断元素是否存在等,这使得管理用户下单状态变得更加简单和灵活 O(1)。

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

图片

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

这样可以确保在同一场次内,同一个用户对同一个商品只能下单一次。

在进行数据库操作时,需要使用唯一键作为表的主键或添加唯一约束。这样当两个线程同时尝试插入相同的唯一键时,其中一个操作会失败,从而防止同一个用户对同一个商品进行多次下单。

如果数据库插入失败,说明已经有其他线程成功地插入了相同的唯一键,此时需要回滚之前减少的库存,并将库存数量增加到 Redis 中,以保证数据的一致性。

于数据库操作和回补库存到 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)超卖问题采用乐观锁的机制做兜底处理

s中订单的状态。

总结:

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

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

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

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

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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值