时间戳无效怎么解决(秒杀中的常见问题汇总)
爱跨境 跨境电商 2022-06-10 15:39:05 · 热度999

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)缓存刚好失效

(2)请求A查询数据库,得一个旧值

(3)请求B将新值写入数据库

(4)请求B删除缓存

(5)请求A将查到的旧值写入缓存(根据耗时情况可以推算出概率比较低)

B 先删除缓存,再更新Db

(1)A线程删除缓存,写db更新为data2

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

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

​ (4)a线程将数据data2写入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向用户发送一个秒杀成功通知。前端以此来判断是否秒杀成功,秒杀成功则进入秒杀订单详情,否则秒杀失败。