34.Redis如何支撑秒杀
秒杀是一个典型的活动场景,遍布各种电商促销活动中,Redis也是经常被用来支持秒杀活动
整个秒杀分为了很多环节,包含秒杀前 秒杀中 秒杀后,对于不同的环节,Redis的功能并不相同,某些环境甚至并不起作用
首先需要说明的是秒杀场景下的常见问题
1.瞬时访问量非常高
需要支持大量的并发请求秒杀系统时,需要使用Redis拦截大部分的请求,避免大量请求直接发给数据库,压垮数据库
2.读多写少
秒杀场景下,查验商品商品是否还有库存,有库存的时候,才能进行库存扣减和下单操作
库存检查操作是典型的键值对查询,Redis天然支持
然后是秒杀活动中只有一定数额的商品,只有库存满足之后才能进行下单,这还要求读到的数据和实际数据具有原子性
其次是Redis在秒杀场景的哪些环节可以发挥作用
首先是进入后端前,这一步必然和Redis毫无关系,不过前端可以考虑利用CDN进行减负
其次是进入了后端,参加秒杀,这一步,大量的用户点击秒杀的按钮,产生大量的并发请求查询库存,一旦请求到达的时候还有库存,就触发生成订单的操作
所以查询库存和检查库存的操作,就交给Redis来做,而且需要保证具有原子性
之后就是秒杀之后,秒杀之后,还存在用户刷新商品页,以及抢到的用户支付订单等操作,不过这类操作往往在其他的模块,一般都能支撑
那么我们就主要说一下秒杀中,Redis的主要参与在于查询库存剩余和修改库存剩余
Redis如何原子性支撑上述两个操作呢?
之前说过,Redis一是支持原子性操作,二是支持Lua脚本来原子性执行
对于原子性操作,其实还是使用incr这样的操作执行,我们拿Lua脚本顺便说一下原子性操作如何执行
首先在Redis上,我们先使用一个Hash类型的键值对来保存库存的这两个库存
id:total:N
id:ordered:N
一个hash中两个field,分别表示总库存量和已秒杀量
那么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 retur n 0 |
对于上面的脚本,我们可以在Redis客户端,使用EVAL来执行这个脚本
客户端根据返回值,确定秒杀是成功还是失败了,返回是k,就表示成功了如果是0,就是失败了
上面就时Redis扣减库存的核心流程
除了上面的原子操作,在实际的秒杀之前,还可以考虑使用分布式锁来支持秒杀操作,这样库存查验和扣减的原子性和锁加一起,双重保险
最后给出一个小tip:使用切片集群中不同的实例来分别保存分布式锁和商品库存信息,使用这种方式保存后,秒杀请求就会访问分布式锁的实例,没有拿到锁,就不会查询商品实例,就减轻压力了
总结一下:
Redis在秒杀过程中,负责了对库存查验和库存扣减的操作,这两个操作需要保证原子性,而Redis可以利用Lua和自身的原子操作来支持秒杀需求的
最后一个小问题
如果将一个商品的库存,交给4个实例的集群来保存,每个保存四分之一,秒杀请求分发到不同的实例进行处理,可以吗?
这会造成一些意外的操作,比如长连接导致的请求的都是一个实例,不建议这么做