秒杀系统设计

Riicarus大约 4 分钟架构设计高并发秒杀系统

秒杀系统设计

秒杀系统主要从前后端两个方面进行设计.
前端限制用户操作, 后端进行高并发设计.

秒杀系统关注服务器的高并发能力以及逻辑完整性, 既要保证能够支持短时间大流量输入, 又要确保商品库存正确扣减, 一定不能超卖.

后端设计

后端设计的思路包括:

  • 如何提高并发量.
  • 并发量支撑不住怎么办.

我们要尽可能支撑高并发, 但是也要做好支撑不住的保护方案, 下面进行一一阐述.

Redis 集群

单机的 Redis 可能顶不住很高的并发量, 这时候我们需要使用到 Redis 集群进行处理. 同时, 秒杀本来是一个读多写少的业务, 可以进行 Redis 读写分离.
那么 Redis 的策略我们就设计出来了: 使用 Redis 集群, 主从同步 & 读写分离, 配置哨兵, 开启持久化策略.

库存预热

由于秒杀实际上会对数据库的库存进行扣减, 如果用户都去数据库查询/校验/扣减库存, 那么数据库压力很大, 而且逻辑繁琐. 我们可以提前把商品的库存缓存到 Redis 中, 整个秒杀流程都在 Redis 中进行处理, 等秒杀结束后, 再异步修改数据库的库存即可.

Lua

刚刚 Redis 集群中的主从结构可能会出现超卖问题(如果主节点库存只剩下一个了, 但是从节点都读取到了, 就都去消费数据, 那么这时候会超卖). 这里的问题主要出现在读取和消费不是一个原子性的步骤, 而是两个步骤, 需要先从 Redis 中读取数据, 然后再去 Redis 消费数据, 这对 Redis 来说是两步. 解决方案也很简单, 就是使用 LUA 脚本.
Redis 在 2.6 之后的版本内置了 LUA 环境支持, 解决了高效处理 CAS(check-and-set) 命令的缺点, 并且可以组合多个 Redis 命令来实现比较复杂的逻辑(如 BloomFilter). LUA 命令类似于 Redis 的事务, 不会被其他命令插队, 有一定的原子性, 可以完成一些 Redis 事务性的操作.

对于超卖问题, 只需要 LUA 指令扣减前检查库存数量, 库存不足就不扣减即可.

Web 服务器

Tomcat 服务器并发能力很弱, 但是 Nginx 是高性能的 Web 服务器, 能轻松承载几万的并发. 那么我们就使用 Nginx + 多 Tomcat 的负载均衡策略, 在需要秒杀时, 可以多租一点流量机.

MQ

当秒杀的商品多起来时, 需要写入的数据也就多起来了(比如秒杀 10w 件商品). 这时候就需要进行流量削峰, 使用 MQ 来对峰值流量进行缓冲, 但是要注意消费顺序问题(参考 MQ 中的如何保证消息消费顺序).

资源静态化

虽然前后端都分离了, 但是前端依然需要服务器来支持它的资源, 当访问量上升时, 任然会使用很多的服务器资源. 我们可以把静态的资源都放入到 CDN 服务器中, 减轻自身服务器的压力.

恶意请求拦截

使用 Nginx 对恶意的请求进行拦截, 防止用户使用脚本进行大量请求.

动态 URL

后端为前端提供动态加密的秒杀连接, 防止用户使用脚本或者提前预知链接.

限流/熔断/降级

... 等待施工, 需要链接到一个专门的文档

单一职责

将秒杀设计成一个微服务单元, 如果秒杀挂了, 不至于影响到其他的服务, 与其相连的数据库同理.

前端设计

前端主要需要限制用户操作.

按钮置灰

秒杀开始前按钮置灰, 开始后, 点击按钮后置灰按钮, 等待一段时间再启用, 防止用户重复多次点击.