乐趣区

关于java:秒杀中的常见问题

秒杀

1. 限流

固定窗口算法:工夫节点无奈动静更新,即 set key expire 10s(1-11 秒,2-12 秒)解决不好

滑动窗口算法: zset member id score 10 解决工夫节点问题 score 即工夫戳,能够动静获取(currentTime – score)工夫内的总访问量,借此判断是否达标

漏桶算法:redis 中如同有相似实现

令牌桶算法:Guava RateLimiter

2. 降级(也叫做熔断)

spring cloud sentinel

3. 缓存

3.1 缓存一致性问题

A. 先更新 Db,再 delete redis

两个问题:

a . 更新 Db 胜利,删除缓存失败。导致 redis 中有脏数据 (能够借助 rocketmq 一直重试解决)

b.(1)A 线程查问 Db 数据为 cache1

(2)B 线程更新 Db, 数据为 ca che2

​ (3)B 线程删除缓存

(4)A 线程将 ca che1 同步到 redis 缓存中。脏数据产生了(依据耗时状况能够推算出概率比拟低)

B 先删除缓存,再更新 Db

​ (1)A 线程写操作 data2,删除缓存

​ (2)b 线程查问发现缓存不存在

​ (3)b 线程同步以后 d b 最新数据 da ta1 到 redis

​ (4)a 线程将数据写入 DB . 脏数据产生

3.2 解决方案

A. 延时双删

先删除缓存,再更新 DB, 再异步删除缓存(通过 rocketmq 异步重试机制,确保删除胜利)

B. 订阅 binLog 机制

阿里开源的 canal

3.3 利用 秒杀实现

限流:zset 滑动窗口限流

降级:spring cloud sentinel+open Feign 熔断

缓存:

延时双删策略

1. 库存扣减问题

提交订单时

付款时

提交订单时事后扣减库存,超时回复:定时工作轮训数据库没有付款的订单,超时订单偿还库存

2. 热点数据缓存

延时双删策略

3. 库存避免超卖问题

乐观锁机制 update

update stock
   sale = sale + 1,
   version = version + 1,
  WHERE id = #{id,jdbcType=INTEGER}
  AND version = #{version,jdbcType=INTEGER}

或者
 
  update stock
   sale = sale + 1
  WHERE id = id
  AND sale={#sale}

乐观锁机制

在 Service 层给更新表增加一个事务,这样每个线程更新申请的时候都会先去锁表的这一行(乐观锁),更新完库存后再开释锁。可这样就太慢了,1000 个线程可等不及。

beginTranse(开启事务)

try{

    //quantity 为申请减掉的库存数量
    $dbca->query('update s_store set amount = amount - quantity where amount>=quantity and postID = 12345');

}catch($e Exception){rollBack( 回滚)

}

commit(提交事务)

具体计划

  • 在零碎初始化时,将商品的库存数量加载到 Redis 缓存中;
  • 接管到秒杀申请时,在 Redis 中进行预减库存,当 Redis 中的库存有余时(库存 <0 能够在内存中设置一个标记量 boolean= 曾经卖光,就不须要再去申请 redis 减少网络开销了,分布式状况下通过 zookeeper 取得相应告诉),间接返回秒杀失败,否则持续进行第 3 步;
  • 将申请放入异步队列中,返回正在排队中;
  • 服务端异步队列将申请出队,出队胜利的申请能够生成秒杀订单,缩小数据库库存(失败就会回滚,并且会复原 redis 中的库存数据即加 1),返回秒杀订单详情。
  • 当后盾订单创立胜利之后能够通过 websocket 向用户发送一个秒杀胜利告诉。前端以此来判断是否秒杀胜利,秒杀胜利则进入秒杀订单详情,否则秒杀失败。
退出移动版