Redis实现原子操做的两种方式与商品入库出库解决方案

在单机的Redis集群下,想要实现针对多个key的复杂原子操做有两种方法。一种是Watch+Multi,即监视器加事务方式,另外一种即是经过执行lua脚本实现。redis

这里所说的复杂原子性操做好比,扣减某商品的5个库存,须要先判断当前商品的剩余库存是否足够扣减。可是避免不了在判断足够的状况下,再去执行扣减操做时,这个期间库存没有被别人修改的状况。数据库

Watch+Multi

Watch能够监控多个key,被监听的多个key,只要被监听的key其中有一个key被修改,那么全部操做都不会被执行,不只仅是对被监听的key的操做。Watch并不能单独使用,而是要结合事务使用。缓存

接着咱们看下,在Watch以后,事务执行以前,若是key被修改,会是什么状况。安全

这很好理解,即使事务中各命令的顺序不同,由于单线程执行命令的缘由,exec执行时就已经知道被监听的哪些key被修改过了,它只是不执行事务了。服务器

每一个Redis数据库中都保存着一个watched_keys的字典,这个字典的键是某个被Watch命令监视的键,如上面例子中的s1s2,而字典的值则是一个链表,链表中记录全部监视该键的客户端,即一个链接。多线程

全部写命令,在执行以后都会对watched_keys字典进行检查,查看是否有客户端正在监视刚刚写的键,若是有,将监视该键的客户端的REDIS_DIRTY_CAS标识打开,表示该客户端的事务安全性已经被破坏。在客户端(一个链接)提交(exec)事务执行时,先检测该标识是否被打开,若是是,则会拒绝当前客户端执行提交的事务。分布式

假设在Cluster集群下执行,会是什么结果呢?我在个人服务器上部署了一个cluster集群,这是很早以前就部署了的。来看下在Cluster集群下执行Watch监听多个key会怎样。性能

能够看到,在Watch的时候就出错了,请求不容许key在不一样的slot。即使slot不一样但都在同一个节点也是不行的,必须是同一个slot。下图中,s4s5这两个key是在同一个节点上的。ui

Watch是真的严格,不一样slot都不行。有没有想过为何监听多个落在不一样节点上的key,会不被容许?在单节点下,Redis单线程执行,可以保证原子性,但在不一样节点下,就是多进程多线程的问题,Watch天然就不能用。lua

Lua脚本

Redis执行lua脚本是原子性操做,与执行Redis命令同样对待。原子性操做得益于Redis执行命令是单线程的,lua脚本也会放在命令执行的等待队列中排队执行,所以也须要特别注意,lua脚本中不要执行太多代码,最好不要写for循环语句,不然会阻塞住,严重致使redis节点假死。

在主从Redis集群与读写分离Redis集群下,lua脚本的编写不须要考虑太多问题,只需考虑脚本耗时问题。但在分槽位的cluster集群下,咱们想要经过lua脚本实现原子性操做,就必需要确保脚本所要操做的key都在同一个Redis节点下,即全部key计算出来的槽位都落到同一个Redis节点(小集群中的master)下,才能保证命令是原子性的。

事实上,若是要操做的key不在同一节点上,命令执行也会保错。下图是Lua脚本试图访问群集中的非本地节点抛出的错误。

即使是在一个事务中执行多个lua脚本,只要有一个lua脚本操做的key落在不一样的节点,结果都会执行失败。

并不仅是lua脚本,由于事务也不支持multiexec之间的命令,操做的key落在不一样节点的状况。

不一样slot也不行。

商品入库出库问题

关于商品库存的问题,不少视频教程都在讲使用分布式锁,你可能也会想到使用分布式锁,可是用过度布式锁的都知道性能是个什么状况。

若是商品库存只使用redis缓存,在不须要修改数据库中库存的状况下。对商品的库存实现入库和出库,咱们能够借助lua脚本实现原子性修改库存。在lua脚本中判断库存是否足够减库存,足够扣减状况下再更新库存,这一系列操做是原子的。

lua脚本不难学,我看了一下条件选择、分支语句以及判断语句以后就能本身写出脚本了。由于咱们也并不须要用lua作多复杂的事情。下面给出一个原子性修改库存的lua脚本例子:

local kc=tonumber(redis.call('GET',KEYS[1])); 
if kc==nil 
then 
    return -1; 
end 
local newKc=kc-ARGV[1]; 
if newKc<0 
then 
    return 0; 
else 
    redis.call('SET',KEYS[1],newKc); 
    return 1; 
end
复制代码
相关文章
相关标签/搜索