10、Redis在秒杀场景的应用

一、秒杀场景的特性

1、顺时并发流量非常高

        当有大量并发请求涌入秒杀系统时,可以使用Redis的高性能、高并发特性,先拦截掉大部分请求,避免大量请求直接发送给数据库,把数据库给压跨。

2、读多写少,读操作是简单的查询操作

        在秒杀场景下,用户需要先检查商品是否有库存,只有库存有余量时,秒杀系统才能进行库存扣减和下单操作。库存查验操作是典型的键值对查询,Redis对键值对查询的高效支持,正好和 这个操作要求相匹配。

二、基于原子操作支撑秒杀场景

        在秒杀场景中,一个商品的库存对应了两个信息,分别是总库存量和已秒杀量。这种数据模型正好是一个key(商品ID)对应两个属性关系,所以我们可以使用一个HASH键值类型来保存:

key:itemID
value:{total:N, ordered:M}

        库存查验和库存扣减这两个扣作要保证原子性,可以直接使用Redis原子操作。因为有查询、扣减两步操作,无法用一条命令完成,所以我们需要使用Lua脚本原子性的执行这两个操作。如下所示:

#获取商品库存信息            
local counts = redis.call("HMGET", KEYS[1], "total", "ordered");
#将总库存转换为数值
local total = tonumber(counts[1])
#将已被秒杀的库存转换为数值
local ordered = tonumber(counts[2])
#将当前秒杀数量转换为数值
local k = tonumber(ARGV[1])
#如果当前请求的库存量加上已被秒杀的库存量仍然小于总库存量,就可以更新库存         
if ordered + k <= total then
    #更新已秒杀的库存量,减去k个库存
    redis.call("HINCRBY",KEYS[1],"ordered", k)                              
    return k
end               
return 0

使用Redis 客户端,EVAL命令来执行这个脚本,根据返回值确定是否秒杀成功,返回0表示失败,返回k表示秒杀成功。应用端根据返回值进行下一步操作。

三、基于分布式锁支撑秒杀场景

        使用分布式锁的具体做法是,先让客户端向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只是实现了对库存查验和扣减这个环节的支撑,除此之外,还有其它几个环节需要处理好。

1、前端静态页面设计,秒杀页面上能静态化处理的页面元素,都需要尽量静态化,这样可以充分利用CDN或浏览器缓存服务秒杀开始前的请求。

2、请求拦截和流控,在秒杀系统接入层,对恶意请求进行拦截,避免对系统的恶意攻击,例如使用黑名单禁止恶意IP进行访问。如果Redis实例访问压力过大,需要在接入层进行限流,控制进入秒杀系统的请求数量。

3、库存信息过期时间处理,Redis中保存的库存信息,为了避免缓存击穿问题,不要给库存信息设置过期时间。为了避免缓存穿透,未命中缓存的商品key可以直接拒绝服务。

4、数据库订单异常处理,如果数据库没能成功处理订单,可以增加订单重试功能,保证订单最终能被成功处理。

你可能感兴趣的:(Redis核心技术,redis,数据库)