秒杀系统 Flash Sale System
秒杀设计
避免服务器页面瞬时加载请求
- 静态页面
- CDN Content Delivery Network
- 秒杀按钮如何到点变化(请求页面耗时久)
- 使用JS脚本控制,在页面中引用一个JS文件(文件极小),但是该文件不要被缓存。
- 该JS的作用是,包含秒杀开始标志,修改样式,生成下单页面的URL及随机参数。
- 当秒杀活动开始时推送该文件。
- 为了避免用户直接访问下单URL,需要将URL动态化,用随机数作为参数,只能秒杀开始的时候才生成。
如何限流
- 针对用户id请求计数,最多每秒请求数阈值
- 针对请求IP请求限流,但容易误伤同IP的用户
- 对接口限流,限制接口请求总数
- 随机验证码
- 滑动滑块、google的验证是否为真人按钮
- 提高业务门槛:比如b站 的会员注册需要答题、会员的等级
下单
- 查询、预扣库存,避免数据库压力
- 缓存cache:redis
- 缓存查询和预扣不一致?
- 用LUA保证原子性
- 缓存击穿(请求的id在第一次大量的请求中还没有被cache
- redis分布式锁,保证只有部分请求在查询产品数据
- 分布式锁设计细节见下面
- 预热缓存,precache
- redis分布式锁,保证只有部分请求在查询产品数据
- 缓存穿透(接口传来不存在的id)
- 提前缓存不存在的id
分布式锁
-
redis中如何实现?
- 用SET lockKey requestID NX PX expireTime
- NX:只在键不存在时,才对键进行设置操作。
- PX:设置键的过期时间为 millisecond 毫秒。
- 用SET lockKey requestID NX PX expireTime
-
如何保证一个请求获得锁和释放锁是原子的?
- redis的lua脚本
- 把request_id放到key中
-
如何保证秒杀效果是先来后到而不是均匀的?
- 问题详述:如果都是同步请求锁一次就返回,那么一万次请求中只有一次成功,下一个一万次也有一次成功?怎么变成先来后到呢?
- 解决方案:自旋锁
- 即lua脚本中,失败后等待一段时间再重试
-
其他的方案:
-
除了上面的问题之外,使用redis分布式锁,还有锁竞争问题、续期问题、锁重入问题、多个redis实例加锁问题等。
这 些问题使用redisson可以解决,由于篇幅的原因,后面有机会考虑出一个专题介绍分布式锁。
-
下单、支付消息队列
- 真实场景:秒杀->下单->支付
- 为什么需要?
- 因为下单和付款的量、同步要求远小于秒杀
- 消息丢失问题?
- 即:如何避免生产消息前服务出错导致的生产消息失败?
- 生产消息表
- 重试生产失败的消息?
- 定时的cronjob
- 重复消费问题:
- 即:如何避免重复消费消息?
- 消费消息表
- 在消费前查询表
- 如果未消费过则消费执行,否则return error
- 注意上面两个操作要放到一个事务中
- 垃圾消费问题?
- 即,如何避免重试太多次仍旧生产失败的消息?
- 记录重试的次数,多于阈值直接返回
如何定时取消已经下单但未付款的单?
- 借助消息队列(比如mq)的延时队列
The UML Diagrams
@startuml
title 避免服务器页面瞬时加载请求
start
:用户访问静态页面;
:CDN提供静态资源;
if (秒杀按钮到点变化?) then (是)
:使用JS脚本控制;
:引用一个不缓存的JS文件;
note right
包含秒杀开始标志
修改样式
生成下单页面的URL及随机参 数
end note
:推送JS文件;
else (否)
:等待秒杀开始;
endif
if (用户点击秒杀按钮?) then (是)
:生成随机参数的URL;
note right
生成随机参数的URL
只能秒杀开始时生成
end note
:用户请求下单URL;
else (否)
:等待用户操作;
endif
stop
@enduml