问题
超卖
高并发
恶意请求
方案
数据库锁
行锁
1
update goods set inventory = inventory-1 where id = #{goodsId} and inventory > 0
问题:大量锁竞争时,会影响数据库性能。
行锁 + 乐观锁
1
update goods set inventory = inventory-1 where id = #{goodsId} and inventory > 0 and version = #{version}
问题:库存 100,且同时只有 100 人抢购商品时,实际卖出的商品可能少于 100。同样存在性能问题
分布式锁
redis 分布式锁
问题:不设置锁的过期时间,可能会导致锁一致得不到释放;设置锁的过期时间,又可能因为业务执行时间较长而导致锁提前释放。使用 Lua 脚本或 Redlock 都较为复杂。ZooKeeper 分布式锁
1
2
3
4
5
6
7
8
9
10InterProcessMutex mutex = new InterProcessMutex(zookeeperClient, "/seckill/" + goodsDto.getGoodsId());
mutex.acquire();
try {
// TODO 查库存
// TODO 减库存
// TODO 下单
} finally {
mutex.release();
}
库存预热 & 内存标记
1 |
|
异步下单
1 | OrderEntity order = new OrderEntity(); |
按钮控制
秒杀开始之前,按钮置灰;用户抢购商品之后,按钮再次置灰。
URL 动态化
- 在秒杀之前,前端先请求后端获取商品秒杀地址。在后端生成随机数作为 pathId 存入缓存(缓存过期时间 60s),然后将这个随机数返回给前端。
- 前端获得 pathId 后,将其作为 URL 参数去请求后端秒杀服务。
- 后端接收 pathId 参数后,将其与缓存中的 pathId 比较。
示例代码如下:
1 | public SeckillGoodsDto createSeckillUrl(String goodsId) { |
用户/IP 限流
前端限流
秒杀按钮在活动之前置灰,在用户购买之后再次置灰。后端限流
相同用户/IP,设置请求次数限制。如可以基于 Spring Cloud Gateway 添加以下配置:
application.yml
1 | spring: |
ThrottlingConfiguration.java
1 |
|
资源静态化
JS/CSS 压缩,减少流量
CDN 就近访问
兜底方案
降级
所谓“降级”,就是当系统的容量达到一定程度时,限制或者关闭系统的某些非核心功能,从而把有限的资源保留给更核心的业务。限流
限流就是当系统容量达到瓶颈时,我们需要通过限制一部分流量来保护系统,并做到既可以人工执行开关,也支持自动化保护的措施。拒绝服务
当系统负载达到一定阈值时,例如 CPU 使用率达到 90% 或者系统 load 值达到 2*CPU 核数时,系统直接拒绝所有请求,这种方式是最暴力但也最有效的保护方式。