主要策略
-
热点隔离
- 业务隔离
- 系统隔离
- 数据隔离
-
将请求尽量拦截在上游
系统越往下游走, 并发能力越差, 锁冲突越严重
数据分层校验
-
动静分离
-
充分利用缓存
考虑在秒杀前后的时间, 其实秒杀也是读多写少的场景
-
削峰
增加验证码, 答题秒杀机制: 拉长并稀释波峰, 同时有防秒杀器作用
采用队列
-
防黄牛, 秒杀器
服务分层
- 客户端/浏览器/app
- 应用层/view层/站点
- 服务层
- 数据层
秒杀过程
-
准备阶段:
秒杀之前, 用户等等秒杀.
-
秒杀阶段:
用户参与秒杀.
分层优化
客户端
-
用户可能反复刷新/点击/查询, 特别是在出现网络慢,并发高, 系统性能下降的场景. 可能增加80%的请求, 然后恶性循环
优化策略: 拦截请求, 限制流量
- 产品层面: 点击按钮后将按钮置灰
- JS层面: 限制在X秒内只能提交一次
- 准备阶段的页面放CDN, 基本全是读请求
-
页面开始前不可购买, 防止黄牛用url直接购买
优化策略:
- url动态化, url需要校验参数, 在秒杀开始后参数由服务端动态生成
- 如何点亮点击按钮: 通过动态js/ajax, 去后端获取开抢状态, 并获得必要的随机校验参数
-
倒计时
时钟请求逻辑简单, 速度快, 问题不大.
需要注意多个服务器的时钟同步问题.
-
动静分离
秒杀的动态数据和普通的详情页面的动态数据相比更少
整页缓存到CDN, 异步刷新动态元素, 如倒计时, 开始抢购按钮等
应用层
-
黄牛写代码刷接口
优化策略: 拦截请求, 限制到服务层的流量
按照一定逻辑做页面缓存, 如userid, 商品id, 用户ip
结合X秒内同一模型返回相同缓存页面
-
高级黄牛刷接口, 持有大量肉鸡, 用户id等
- 检测IP请求频率
- 验证码
- 用户行为分析, 活跃度分析, 设置秒杀门槛
-
时钟同步
对于定时秒杀, 查询接口的后端服务, 要注意时钟同步问题
服务层
-
优化策略: 拦截请求, 限制到数据层的流量
-
对于写请求, 用请求队列. 因为秒杀库存有限, 没必要全部打到数据层
依赖队列写对用户来说是异步的, 需要在产品上进行一定的设计补偿(比如提升稍后在个人中心查看秒杀结果)
-
对于读请求, 采用分布式缓存, 如redis集群, 可以方便的横向读扩展
-
-
异常处理
- 如果发生雪崩, 需要注意预热
- 过载保护, 如果检测到系统满负载状态,拒绝请求也是一种保护措施
数据层
如何解决超卖问题?
-
悲观锁:
高并发时, 资源消耗巨大
-
乐观锁:
有一定cpu开销
update item_stocks set stock = #new_stock# where item_id = #item_id# and stock = #old_stock#
redis WATCH 也是乐观锁的实现
-
尝试减库存 (mysql update满足一致性要求)
update item_stocks set stock = stock-#count# where item_id = #item_id# and stock >= #count#
-
先入先出队列更新
高并发时队列撑爆
业务层优化
-
(热点隔离) 业务隔离, 秒杀商品单独报名, 提前知道热点
-
点击按钮后将按钮置灰
-
分时分段销售
-
数据粒度的优化
对于余票查询这个业务,票剩了58张,还是26张,你真的关注么,其实我们只关心有票和无票?流量大的时候,做一个粗粒度的“有票”“无票”缓存即可
-
秒杀页面设计要更简洁
用户更关心快速的刷新和下单, 页面应该突出秒杀, 减少其他不必要元素, 减少页面大小
下单表单也应该尽量简洁, 地址使用默认地址, 没有甚至可以不填, 下单成功再补
部署
-
系统隔离
独立部署, 避免对现有系统的冲击
域名, 服务器, 独立部署
-
数据隔离
mysql, cache等等需要隔离存放热数据, 目的是不想0.01%的数据影响另外99.99%
-
应对网络流量的增加, 将秒杀页面(读)部署到CDN
其他
-
减库存方式:
-
拍下减库存: 用户体验更好, 但是超卖的概率大
需要超时回仓, 抢票的启示: 开动秒杀后,45分钟之后再试试看,说不定又有票哟~
-
付款减库存
-
-
其他防黄牛, 秒杀器的方案
- 验证码
- 通过购买记录, 访问记录识别
- IP请求频率
-
站点层或者服务层处理后台失败的话,重放还是丢弃
架构设计原则之一是“fail fast”。对用户而言, 有较高失败率的秒杀, 返回失败是合理的