在单机的Redis
集群下,想要实现针对多个key
的复杂原子操做有两种方法。一种是Watch+Multi
,即监视器加事务方式,另外一种即是经过执行lua
脚本实现。redis
这里所说的复杂原子性操做好比,扣减某商品的5
个库存,须要先判断当前商品的剩余库存是否足够扣减。可是避免不了在判断足够的状况下,再去执行扣减操做时,这个期间库存没有被别人修改的状况。数据库
Watch
能够监控多个key
,被监听的多个key
,只要被监听的key
其中有一个key
被修改,那么全部操做都不会被执行,不只仅是对被监听的key
的操做。Watch
并不能单独使用,而是要结合事务使用。缓存
接着咱们看下,在Watch
以后,事务执行以前,若是key
被修改,会是什么状况。安全
这很好理解,即使事务中各命令的顺序不同,由于单线程执行命令的缘由,exec
执行时就已经知道被监听的哪些key
被修改过了,它只是不执行事务了。服务器
每一个Redis
数据库中都保存着一个watched_keys
的字典,这个字典的键是某个被Watch
命令监视的键,如上面例子中的s1
、 s2
,而字典的值则是一个链表,链表中记录全部监视该键的客户端,即一个链接。多线程
全部写命令,在执行以后都会对watched_keys
字典进行检查,查看是否有客户端正在监视刚刚写的键,若是有,将监视该键的客户端的REDIS_DIRTY_CAS
标识打开,表示该客户端的事务安全性已经被破坏。在客户端(一个链接)提交(exec
)事务执行时,先检测该标识是否被打开,若是是,则会拒绝当前客户端执行提交的事务。分布式
假设在Cluster
集群下执行,会是什么结果呢?我在个人服务器上部署了一个cluster
集群,这是很早以前就部署了的。来看下在Cluster
集群下执行Watch
监听多个key
会怎样。性能
能够看到,在Watch
的时候就出错了,请求不容许key
在不一样的slot
。即使slot
不一样但都在同一个节点也是不行的,必须是同一个slot
。下图中,s4
和s5
这两个key
是在同一个节点上的。ui
Watch
是真的严格,不一样slot
都不行。有没有想过为何监听多个落在不一样节点上的key
,会不被容许?在单节点下,Redis
单线程执行,可以保证原子性,但在不一样节点下,就是多进程多线程的问题,Watch
天然就不能用。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
脚本,由于事务也不支持multi
与exec
之间的命令,操做的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
复制代码