秒杀场景
整体要求
- 保证系统的高可用和正确性
- 一致性:保证秒杀减库存中的数据一致性。
- 高性能:涉及大量并发读写,需要支持高并发,从动静分离、热点发现与隔离、请求削峰与分层过滤、服务端极致优化来介绍。
数据预热(预加载)
- 将秒杀商品提前加入到缓存系统入ES、Redis等,防止商品超卖和缓存穿透甚至雪崩。
限制
- 通过网络代理层、SLB负载均衡层、程序阻流组件与算法(如Guava限流)、前端逻辑过滤等多种手段,防止大流量而造成服务拒绝或阻塞。
削峰
- 通过异步通信的设计与解决方案如RPC、MQ等具体实现
隔离
- 通过部署隔离方案,在网络拓扑将秒杀系统独立部署、即使异常或宕机,至少不会导致主营业务系统受损或瘫痪,库表分区。
业务兜底方案
一般这种,宁可少卖不可多卖,扣减库存的方式:
- 下单减库存
实现:下单操作绑定减库存操作
优点:一定不会出现超卖 - 付款减库存
实现:付款时,再扣减库存
优点:并发高,买家下完单却付不了款 - 预扣库存
实现:下单后,库存保留一段时间,超过时间自动释放,释放后其他买家就可以自己购买了,买家付款前,会校验该订单的库存是否还有保留,如有保留再次尝试预扣。如果预扣失败,则返回失败,预扣成功则付款成功并实际减少库存。
redis 实现:lua脚本实现 检查库存+扣减库存+返回结果
mysql 事务实现:
update 新数量=原数量-购买数量 where 原数量>购买数量 and 商品id = xxx
库存放入缓存中的方法
- 多个redis 分别存储部分商品库存。用户请求随机到其中一个redis上,能获取到就获取,不能就返回失败(哪怕另外几个redis中库存存在)
分布式锁场景
分布式锁的一般要求
- 分布式环境下的一致性
- 可重入锁
- 尽量阻塞锁
- 尽量公平锁
- 高可用
- 快,性能好
redis中遇到过的问题总结
- 主从复制:主从复制的异步复制,从尚未同步完主数据,主挂了,发生主从切换会导致数据的一致性问题。
- setnx 成功 expire 设置失败时,会出现key无限存在的问题。使用redis实现分布式锁中,那么会造成死锁。
解决方案1:set 命令 set key value [EX seconds] [PX milliseconds] [NX|XX}
EX seconds:失效时长 (s)
PX milliseconds:失效时长 (ms)
NX:key不存在时设置value,成功返回OK,失败返回nul
XX:key存在时设置value,成功返回OK,失败返回nil
解决方案2:lua脚本实现setnx 与 expire 原子性 - key的续约机制
业务线程需要定期刷新key的有效期,直到使用完毕key过期