秒杀系统
[TOC]
问题分析
大流量、流量倾斜,大量流量会集中在少量的几种商品中
高并发、高性能:流量很大,服务要你能抗住
高可用:服务器不因为大流量而崩溃,同时秒杀业务不影响其他业务
一致性:不出现超卖和少卖的问题
总体策略
漏斗型架构设计:由于售卖的数量很少,绝大数请求是应当被过滤掉的。尽量将请求拦截在系统上游,让漏斗最末端的才是有效请求,也减轻下游压力
缓存:典型的读多写少,非常适合缓存,可以不用DB
削峰:可以通过消息队列削峰
架构
1. 接入层
1.1 请求校验、拦截恶意请求
验证码:应对恶意请求和爬虫
网关设黑名单机制,对同一 IP、多次下单不付款的用户加入黑名单
1.2 限流
如限制每秒只允许多少请求通过
前端限流:秒杀前按钮置灰、点击后置灰几秒
网关限制最大流量,超过直接拒掉
后端限流:分布式限流,如 Sentinel、Hystrix 等
2. 逻辑层
需要如下服务:秒杀服务、库存服务、订单服务、支付服务
3. 存储
DB数据表:商品表、秒杀活动表、库存表、订单表
3.1 多级缓存
静态页面 CDN缓存 + 本地缓存 + redis + DB
依次检查本地缓存、redis 是否卖光,尽早提前返回
这样如果卖 100 个,最多只有 100 个请求走到下面的 DB 查询
最终仍以 DB 为准,redis 主挂掉后不能保证从节点的数据是一致的,只使用 redis 的话会导致从节点升主后超卖;或者 redis 只用主
3.2 数据分片
如果数据量还大,可以将数据分片,即商品平均分到多台服务器/缓存/DB里进行售卖
可能导致某个分表库存为零,但其他分表还有库存
不需要解决,让后续别的用户抢到就可以了
通过路由组件记录每个分表的库存情况,将下单请求转发到有库存的分表中
3.3 提前加载做法
单独做一个 job 服务,提前将库存直接写入服务器或缓存,不走 DB。性能更高
.4. 削峰
使用消息队列进行削峰。比如用户发出一个抢请求,把这个请求放入队列异步处理,前端冻结按钮一会儿再来查状态。
4. 客户端
客户端防重复提交:提交后按钮置灰一段时间
高可用
服务分布式、redis 使用集群、DB主从
限流、熔断、降级
服务单一职责:单独服务器,单独数据库,单独网关。就算秒杀服务挂了,也不会影响其他服务
弹性伸缩:秒杀开启前扩容,完成后缩容
京东:客户参加活动前需要先预约。通过预约,可以提早识别热门商品并进行流量预估,提前加载资源、扩容
一致性
预扣减模式,下单就扣;下单后超时未支付 / 取消的,加回配额
在支付成功再扣除库存的话会出现下单请求成功数量大于库存的情况,造成用户可以进入订单填写页,填完支付时却被提示卖光,体验不好
redis 使用 lua 脚本原子性扣减库存
incr / decr
也是原子操作,原子性上没问题。但存在扣减成负数的问题,减成负数后就没法处理加回配额的逻辑了。所以还是得用 lua 脚本
对同一用户在 redis 里计数 / 放入 set 里,防止一个用户抢多个
Last updated