秒杀是一个非常典型的常见,比如像双11,618这样的网络购物节,一些平台会提供限时限量的是商品,而且这些商品也是物美价廉,吸引大量的用户,redis经常用于这种秒杀场景
我们先来谈谈秒杀场景的特点对支持系统的要求
秒杀场景的特点及redis对秒杀场景的支持
- 瞬时并发访问量非常高
秒杀最明显的特哥特点就是瞬时访问量巨大,对于数据来说,所能够支撑的访问量也就是每秒千级别,而redis本身的特点就是高性能的读写,可以处理每秒万级别的请求,甚至更高。所以我们可以使用redis拦截绝大部分的请求,这样数据库的压力就大大减轻。
- 读多写少,而且都是简单的查询
在秒杀场景中,正常操作是先通过商品id查询库存,判断库存是否有余,然后是扣减库存。商品的数量是有限的,所以真正扣减库存的操作也是有限,更多是查询库存信息,用户每次点击抢购都会有查询库存操作。查询库存操作是典型的键值操作,redis对键值对查询的搞笑支持,刚好满足这一点。
redis在秒杀场景中的哪些环节发生作用
秒杀的整个过程,我们可以分为三个部分:
- 秒杀活动前
- 秒杀活动中
- 秒杀活动后
下面我们就详细极少一下各个环节redis的作用
秒杀活动前
在这个阶段,用户会不断的刷新商品详情页,这就会给服务端造成巨大的压力。我们一般采用的方法就是商品详情的信息尽量静态化,先缓存到cdn或者利用浏览器的缓存,这样就不会有大量的请求到服务端,减少了服务端的压力。在这一阶段不需要用到redis。
秒杀活动中
在这一阶段,需要做一些操作:查询库存,校验库存是否充足,扣减库存。因为每一个请求都需要查询库存,所以查询库存的压力就会很大,库存校验成功之后才会有扣减库存操作。此时我们就需要使用redis来缓存库存信息,来支持大量的查询库存请求。
扣减库存是否可以在数据库端完成?
答案是否定的不能,原因如下:
1:如果在数据库中扣减库存,那么数据库中库存扣减成功之后,还需要更新redis中的库存,增加了操作的复杂度
2:有可能会出现超卖的情况,比如数据库中库存扣减成功,但是还未来得及更新redis中的库存,那么就会读取到redis中的旧库存,导致超卖。
综上扣减库存操作还是需要放在redis中。
需要注意的是查询库存,校验库存是否充足,扣减库存,这三个步骤我们要保证原子性操作,不然查询可能会查到旧库存,导致超卖。保证原子性我们我们有三种做法:
1:使用redis自带的操作命令。目前来看没有相关命令,所以pass掉
2:使用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
3:使用分布式锁,用户需要先获取锁才能进行库存查询,及以后操作。伪代码如下:
//使用商品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
秒杀活动后
活动结束后可能还有有部门用户刷新商品页面,看是否有其他客户退单。已经成功的用户查看订单信息,跟踪订单的进度,但这些用户的请求量已经小很多了,服务器可以支持。