设计秒杀系统从哪些方面考虑
当我们面试的时候遇到最多的问题就是如何设计一个秒杀系统呢?这个问题能够很好的检验程序员对于整体架构的设计以及对于高并发的掌握情况。在问到这个问题的时候我们往往会不知道从哪里回答,下面我将从整个系统的架构层次上一点点的解析对于秒杀系统应该如何设计。
访问层:
设计秒杀系统的前端部分至关重要,它直接面对海量用户的高并发请求,需要采用特殊策略来缓解后端压力。对于前端的访问层我们有以下的设计方式
首先静态资源的优化
所有静态资源(JS/CSS/图片)部署CDN
使用版本号实现永久缓存(
main.v123.css
)秒杀页面本身做成静态页(预渲染)
静态页+CDN的方式能够将前端的页面部署到CDN服务器中,当用户访问的时候直接从CDN服务器获取即可,极大的减少了业务服务器的压力。(CDN:全球分布的缓存服务器网络(像连锁便利店))
方案 100万用户冲击 服务器负载 用户加载延迟 纯动态页面 直接崩溃 100% CPU >5s 静态页+CDN 平稳承载 <20% CPU 300-800ms
其次当用户点击秒杀按钮提交订单时候需要将按钮置为灰色,防止用户反复点击按钮请求后端造成后端资源的浪费。并且还需要添加验证码用来防止黄牛进行机器刷单。
负载层:
上图则是一个简单的负载层的架构图,当用户的后端请求到来之后我们采用了nginx集群和api网关集群的方式来进行部署,那么在nginx外层我们可以使用DNS的轮询访问机制来进行nginx集群轮询,这种方式的性能往往不是最优的。我们可以在nginx外层添加一个LVS集群来进行物理上的负载均衡。
LVS(Linux Virtual Server)
LVS 是一个开源、高性能、基于 Linux 内核的四层(传输层)负载均衡器。它的目标是在一组真实的服务器(称为服务器池或服务器群)前面构建一个高性能、高可用性的虚拟服务器。客户端访问的是这个虚拟服务器的 IP 地址(VIP),LVS 则根据配置的算法和规则,将客户端的请求透明地分发到后端真实的服务器上,从而实现负载均衡、可扩展性和高可用性。
本质:Linux内核级负载均衡器(由章文嵩博士1998年开发)
定位:OSI第4层(传输层)负载均衡
特点:
高性能:单机可处理百万并发(C10M问题解决方案)
零拷贝技术:数据转发不经过用户空间
支持多种调度算法:RR/WRR/LC/WLC等
✅ LVS 不是一台服务器,而是一套 基于 Linux 内核的负载均衡技术(以 IPVS 模块为核心)。
✅ 运行 LVS 的 Linux 物理/虚拟机 被称为 调度器(Director),它是 LVS 架构的载体。
✅ LVS 的核心价值是 高效调度流量(四层转发),而非直接提供应用服务(如 Nginx 或 Tomcat)。
而LVS也是一般与keepalived进行搭配使用
Keepalived 是一个基于 VRRP 协议(Virtual Router Redundancy Protocol,虚拟路由冗余协议)的开源软件,核心目标是实现 Linux 系统的高可用性(HA) 和 负载均衡器的故障转移。它常与 LVS(Linux Virtual Server)结合使用,构建高可用的负载均衡集群。
Keepalived 是什么?
一个基于 VRRP 协议的高可用解决方案,核心功能是:实现 IP 地址漂移(VIP),消除单点故障。
为 LVS 提供 后端服务器健康检查 能力。
守护关键进程,确保服务持续运行。
为什么 LVS 需要 Keepalived?
LVS 本身只是一个负载均衡器,若调度器宕机会导致整个服务不可用。Keepalived 为 LVS 提供了“双机热备”能力,确保即使一台调度器故障,备份节点能立即接管流量,实现服务零中断。
方案 | 最大连接数 | 吞吐量 | 延迟 | 资源消耗 |
---|---|---|---|---|
Nginx单节点 | 50,000 | 30Gbps | 0.5ms | 高 |
LVS+Keepalived | 500,000 | 100Gbps | 0.1ms | 极低 |
商业负载均衡(F5) | 2,000,000 | 200Gbps | 0.05ms | 天价 |
✅ 简单说:
Keepalived 是 LVS 的“保镖”——它确保负载均衡器永不宕机,并时刻监控后端服务器的健康状态。
通过LVS+Keepalived方式来对最外层实现负载均衡的保障,这样可以负载均衡到Nginx集群的各个节点中,而对于nginx集群来说则是需要实现全局限流的操作,进一步进行流量削减,同时本身也是采用负载均衡的方式来将数据打到各个的api网关中,从而打散了流量。对于api网关来说也是要进行限流处理的,同时还需要结合sentinel来实现服务的熔断机制的处理。并且对于api网关则是可以结合K8s来实现容器的动态扩容和缩减,当是在服务的颠峰的时候则可以进行扩容随后如果服务低谷则可以进行缩减。并且还可以通过granfa和普罗米修斯来实现整个请求的时间监控。来对是否扩容有个指标处理。
服务层:
对于服务层来说则是重中之重,整个秒杀的核心逻辑都是在服务层的。那么如何实现整个秒杀流程呢?
首先对于秒杀系统来说要解决的问题就是两个一个是短时间内的高并发请求,另一个就是少量的库存如何保证库存一致性不会出现多扣和少扣。
那么我们知道了这两个关键点业务围绕这两点设计即可:
首先对于高并发的请求我们外层已经使用LVS,Nginx,API网关等组件来进行负载均衡处理将流量分散到不同的服务器上了,其次对于本身服务的业务层还需要做进一步的处理。
上图我列出来了业务层主要做的三个模块,分别是流量,库存,后续订单的管理这三个。
对于流量控制则可以采用流量桶算法的方式来对当个服务进行保护,对于库存则相对复杂一点
对于库存的扣减首先是不能直接操作mysql数据库的(数据库的连接池是有限的,这么大的并发量直接操作数据库很容易导致服务雪崩)
因此我们将库存信息提前录入redis中,扣减的时候直接操作redis即可。同时也要保证整个操作的原子性。当我们扣减操作的时候往往是需要查询库存大于0后然后再进行扣减的操作,这个过程我们使用redis的lua脚本来进行扣减。同时还要添加一个分布式锁,这个分布式锁是防止一个用户多次进行秒杀活动,我们以用户id+活动id+商品id当作唯一key进行分布式上锁来保证秒杀活动参与次数。
redis库存扣减完毕之后最后是肯定要将库存同步到mysql数据库的因此我们可以使用mq的方式来交给消费端慢慢从mysql扣减库存,保证库存的最终一致性。那么库存扣减完毕之后是要生成订单的,这个订单的生成我们往往采用异步的方式来生成订单。而生成订单的方式则是依然可以通过mq的方式进行生成订单,最后呢则是需要给用户返回一个秒杀成功的消息。
其实还有一个最关键的步骤支付模块的管理,整个支付模块是与订单模块和库存模块紧密相关联的
下面我们来说一说支付模块的设计
当扣减完库存之后以及生成订单之后,我们则会让客户进行支付操作,同时这个支付操作会携带订单id用作幂等校验,当用户支付完成之后则会更改订单状态整个流程执行完毕,同时支付后也要留痕保证整个流程链的每个操作都要留痕,当进行追溯的时候能准确的找到整个操作的流程。如果在规定时间内没有执行,后台会有定时任务不断扫描过期订单对其进行关闭并且恢复库存。