怎么保证执行的原子性?
Redis使用同一个Lua解释器来执行所有命令,同时,Redis保证以一种原子性的方式来执行脚本:当lua脚本在执行的时候,不会有其他脚本和命令同时执行,这种语义类似于 MULTI/EXEC。从别的客户端的视角来看,一个lua脚本要么不可见,要么已经执行完。
然而这也意味着,执行一个较慢的lua脚本是不建议的,由于脚本的开销非常低,构造一个快速执行的脚本并非难事。但是你要注意到,当你正在执行一个比较慢的脚本时,所以其他的客户端都无法执行命令。
redis+lua实现高并发库存扣减和回流过程:
用的是redis list的方式,但是在库存量较大时会占用大量的redis资源。正好现在又有一个新的需求,牵涉到库存控制,由于时间上已不允许完全重写,所以只是在原有的代码基础上简单增加了几行代码,使用redis+lua进行库存管理,其他代码完全使用现有代码不改变
核心思路如下:
1、存储时,把库存存储在数据库的基础上冗余一份到redis。由于本次需求是一个活动3个字,每个字库存独立,所以采取了hash的方式来存,尽量减少key的数量是redis使用的基本原则。
key:"butterfly:collectword:"+activityId
field:word.getId()
value:word.getWordNum()
2、抽奖时,先走原有代码,根据几率计算是否中奖。如果中奖,根据活动id,中奖wordid, 进行库存扣减,然后判断扣减后是否库存小于0,如果扣减后库存已小于0,并把库存再次+1,避免库存一直减下去,并返回-1,否则返回剩余库存。
如果是在java里面写这个流程,功能实现是OK的,但是在大并发下,库存就会超卖。因为java环境下,整个流程并不是一个原子操作。
利用redis+lua的原子操作特性,可以简单的避免这个问题。
代码如下:
String script="local stock= redis.call('HINCRBY',KEYS[1],ARGV[1],ARGV[2]);if stock<0 and stock>-1000 then redis.call('HINCRBY',KEYS[1],ARGV[1],ARGV[3]); return -1 else return stock end" ;
String stock=JedisClusterUtil.eval_r(script, 1, "butterfly:collectword:"+activity,word.getId(),"-1","1");
顺便对eval方法参数做一个简单的说明
第一个参数,lua脚本。
第二个参数 后面的数组里包含的key的个数
第三个参数 String ...pars 就是多个string
redis在执行lua脚本时,会把 pars分割为2个数组,一个是keys 一个是argv
分割依据是 第二个参数,把前N个参数作为keys 其他的作为argv
需要注意的是,LUA脚本里访问数组元素角标从1开始。
具体关于lua脚本的内容使用请移步至 redis命令参考–Script脚本 : http://doc.redisfans.com/script/index.html