硬核讲解:秒杀设计( 二 )


update 库存表 set
已售数=已售数+1,版本号=版本号+1
where 秒杀id =#{id} and 版本号 = #{version}
需注意:乐观锁状态下 , 由于是随机性的秒杀失败 , 所以可能活动结束后还会有几个没售出去的!
5 第4版-限流最核心的超卖问题已经解决了 , 接下来就是各种优化手段了 。在高并发请求中如果不对接口限流会对后台服务器造成极大压力 , 所以一般秒杀系统为了不影响其他业务会单独部署到个某个服务器上 , 同时还会设置好限流 。
常用的限流方法有我们在 redis 中曾经说过 , 主要有漏桶算法、令牌桶算法 。而google开源项目Guava中RateLimiter使用的就是令牌桶控制算法 。在开发高并发系统时有三把利器用来保护系统:缓存、降级、限流

  1. 缓存:缓存的目的是提升系统访问速度和增大系统处理容量 。
  2. 降级:降级是当服务器压力剧增的情况下 , 根据当前业务情况及流量对一些服务和页面有策略的降级 , 以此释放服务器资源以保证核心任务的正常运行 。
  3. 限流:限流的目的是通过对并发访问/请求进行限速 , 或者对一个时间窗口内的请求进行限速来保护系统 , 一旦达到限制速率则可以拒绝服务、排队或等待、降级等处理 。
5.1 漏桶算法漏桶算法思路:把水比作是请求 , 漏桶比作是系统处理能力极限 , 水先进入到漏桶里 , 漏桶里的水按一定速率流出 , 当流出的速率小于流入的速率时 , 由于漏桶容量有限 , 后续进入的水直接溢出(拒绝请求) , 以此实现限流 。
硬核讲解:秒杀设计

文章插图
 
5.2 令牌桶算法令牌桶算法原理:可以理解成医院的挂号看病 , 只有拿到号以后才可以进行诊病 。
硬核讲解:秒杀设计

文章插图
 
流程大致:
  1. 所有的请求在处理之前都需要拿到一个可用的令牌才会被处理 。
  2. 根据限流大小 , 设置按照一定的速率往桶里添加令牌 。
  3. 设置桶最大可容纳值 , 当桶满时新添加的令牌就被丢弃或者拒绝 。
  4. 请求达到后首先要获取令牌桶中的令牌 , 拿着令牌才可以进行其他的业务逻辑 , 处理完业务逻辑之后 , 将令牌直接删除 。
  5. 如果用户无法获得令牌可以选择一直阻塞等待 , 也可以选择设置好timeout机制 。
  6. 令牌桶有最低限额 , 当桶中的令牌达到最低限额的时候 , 请求处理完之后将不会删除令牌 , 以此保证足够的限流 。
工程中一般用令牌桶算法为多 , 一般用Google的Guava 中 RateLimiter 即可 。
//创建令牌桶实例
private RateLimiter rateLimiter = RateLimiter.create(20);
// 阻塞式获得令牌才继续往下执行
rateLimiter.acquire();
// 就等3秒看是否可以获得令牌 , 返回Boolean值 。
rateLimiter.tryAcquire(3, TimeUnit.SECONDS)
6 第5版- 细节优化有了乐观锁跟限流 , 接下来再思考写细节问题 。
  1. 秒杀要有时间范围限制的 , 不能再任意时刻都可以接受秒杀请求 , 要实行限时抢购 。
  2. 如果有懂IT人员通过抓包获取了秒杀接口地址 , 在秒杀开始时 , 不通过按钮 , 直接通过脚本秒杀咋办?要实行秒杀接口隐藏 。
  3. 每个用户单位时间内访问次数要做频率限制 。
6.1 限时抢购很简单 , 将秒杀商品放入Redis并设置超时 , 比如我们以kill + 商品id作为key , 以商品id作为value , 设置180秒超时 。
127.0.0.1:6379> set kill1 1 EX 180
OK
加入时间校验:
public Integer createOrder(Integer id) {
//redis校验抢购时间
if(!
stringRedisTemplate.hasKey("kill" + id)){
throw new RuntimeException("秒杀超时,活动已经结束啦!!!");
}
//校验库存
Stock stock = checkStock(id);
//扣库存
updateSale(stock);
//下订单
return createOrder(stock);
}
6.2 秒杀接口隐藏


推荐阅读