这是我参与11月更文挑战的第9天,活动详情查看:2021最后一次更文挑战
秒杀场景下,需要注意几个问题:
- Redis在秒杀场景中需要缓存哪些数据?
- 缓存的数据做更新时,如何保证原子性?
秒杀场景下不同阶段下的分析
在秒杀场景中,有着高并发,特点是读多写少。面对瞬间的读请求流量,单数据库层是顶不住压力的,从磁盘上拿数据比较慢,连接数很快就满了,这很快就可以把数据库给搞崩了。
秒杀场景的过程可以分为秒杀前、秒杀开始、秒杀后。在每一个阶段,Redis所发挥的作用也不一样。
秒杀活动前
在这个阶段,用户会不断刷新商品详情页,这会导致详情页的瞬时请求量剧增。这个阶段的应对方案,一般是尽量把商品详情页的页面元素静态化,然后使用CDN或是浏览器把这些静态化的元素缓存起来。这样一来,秒杀前的大量请求可以直接由CDN或是浏览器缓存服务,不会达到服务端了,这就减轻了服务器端的压力。
在这个阶段,有CDN和浏览器缓存服务请求就足够了,还不需要使用Redis。
秒杀活动开始
秒杀开始时,大量用户点击秒杀按钮,会产生大量的并发请求查询库存。一旦某个请求查询到有库存,系统就会进行库存扣减,然后系统会生成实际订单,并进行后续处理,例如订单支付、物流服务等。
这个阶段的操作共分为三个部分:库存查验、库存扣减和订单处理。
因为只有当库存查验发现有库存后,才会进行库存扣减和订单处理的流程,所以最大的并发压力都在库存查验操作上。
为了支撑大量高并发的库存查验请求,需要在这个环节中使用Redis保存库存量。这样一来,请求可以直接从Redis中读取库存并进行查验。
库存扣减这个操作也需要在缓存中处理。如果将该操作放到数据库中执行,会带来两个问题:
- 额外的开销。数据库层的数据更新后,还需要和Redis进行同步,带来了额外的操作逻辑;
- 下单量超过实际库存量,出现超售。由于数据库的处理速度较慢,不能及时更新库存余量,有可能在更新完数据库层后再更新Redis缓存时,其它并发请求就会读取到旧的库存值,就会出现下单数量大于实际的库存量,导致出现超售。
所以需要在Redis中进行库存扣减。当库存查验完成后,一旦库存有余量,就立即在Redis中进行库存扣减。而且为了避免请求查询到旧的库存值,库存查验和库存扣减这两个操作需要保证原子性。
秒杀活动后
在秒杀活动后,请求会大大降低。可能会有用户尝试退单,但量不会很多了。这时候不需要使用Redis即可完成请求。
Redis 支撑秒杀场景的方法
在对Redis进行库存查验和库存扣减操作时,需要使用原子操作或者分布式锁来实现。
库存查验和库存扣减两个操作不能使用Redis提供的原子命令来完成,需要使用Lua脚本原子性执行这两个操作。
Lua脚本
#获取商品库存信息
local counts = redis.call("HMGET", KEYS[1], "total", "ordered");
#将总库存转换为数值
local total = tonumber(counts[1])
#将已被秒杀的库存转换为数值
local ordered = tonumber(counts[2])
#如果当前请求的库存量加上已被秒杀的库存量仍然小于总库存量,就可以更新库存
if ordered + k <= total then
#更新已秒杀的库存量
redis.call("HINCRBY",KEYS[1],"ordered",k) return k;
end
return 0
使用分布式锁支撑秒杀场景时,需要先让客户端向Redis申请分布式锁,只有拿到锁的客户端才能进行库存查验和库存扣减。这样一来,大部分请求都会在申请锁时堵塞住,后面的库存查验和库存扣操作就不需要使用原子操作了。因为多个客户端只有一个客户端才能拿到锁,这已经保证了互斥性了。
分布式锁的伪代码
//使用商品ID作为key
key = itemID
//使用客户端唯一标识作为value
val = clientUniqueID
//申请分布式锁,Timeout是超时时间
lock =acquireLock(key, val, Timeout)
//当拿到锁后,才能进行库存查验和扣减
if(lock == True) {
//库存查验和扣减
availStock = DECR(key, k)
//库存已经扣减完了,释放锁,返回秒杀失败
if (availStock < 0) {
releaseLock(key, val)
return error
}
//库存扣减成功,释放锁
else{
releaseLock(key, val)
//订单处理
}
}
//没有拿到锁,直接返回
else
return
秒杀场景下Redis只是实现了后端对库存查验和库存扣减的支撑。而其它环节是需要别的支撑的。主要目的是为了减少大量请求进入后端系统,降低后端系统服务的压力,常见的手段有页面静态化(CDN)、请求拦截和流控、缓存校验和扣减库存、消息队列处理订单。
另外,为了不影响其他业务系统,秒杀系统最好和业务系统隔离,主要包括应用隔离、部署隔离、数据存储隔离。