一. 场景
下单后库存校验或者秒杀场景下,有很多利用“锁”的方案来解决问题。但是加锁其实是一件性价比很低的事,所以我们采用用redis+lua的方式来实现这个功能。
二. 思路
阶段一:
在库存加减逻辑中分为2个步骤:STEP1.读取库存,STEP2.读取库存。
利用其他方法例如"锁"等,也就是想控制好STEP2一定要紧跟STEP1,本质上就是确保获取的库存的最新的数据为最新。
阶段二:
在相对较高的并发场景下,redis被常用作库存管理,我们需要通过最小成本的改动来实现库存的限制。但是redis的读取库存+读取库存一般都是有上层应用代码控制,有没有办法在一个函数调用中能串行执行这俩个步骤了?
阶段三:
众所周知,redis是单线程的,并且现在已经支持lua脚本,那是不是可以利用该组合实现我们的场景了?
三. 设计方案
这个方案就很简单了,直接利用redistemplate执行lua脚本
四. 代码
4.1 lua脚本代码样例
private static final String GET_COUPON_CODE =
"local values = redis.call('hmget',KEYS[1],'recvCnt','couponCnt');\n" + //lua返回value的数组
"if tonumber(values[1]) < tonumber(values[2]) then \n" + //lua的数组索引从1开始 values[1] = recvCnt,values[2] = couponCnt
" redis.call('hincrby',KEYS[1],'recvCnt',1);\n" +
" return true;\n" +
"else\n " +
" return false;\n" +
"end\n";
4.2 redis调用lua脚本
//执行调用
execute(GET_COUPON_CODE, keys);
//此处将数值类型转化为Long
public Long execute(String redisScript,List<String> keys){
RedisScript<Long> REDIS_SCRIPT = new DefaultRedisScript<>(redisScript, Long.class);
return redisTemplate.execute(REDIS_SCRIPT,keys);
}
4.3 lua基本用法
redis.call()
redis.pcall()
call与pcall基本上一样。脚本报错时,call会直接报错,pcall不会报错,会把错误信息放到lua table 的err字段中。
五. 说明
1.记得利用“\n”分行,也可以利用string的append拼接
2.values[n]对应有序数组keys,需要控制好各数据顺序
3.lua的数组索引从1开始
六. 总结
我们先分析场景,通过多种方案对比,选用了redis+lua的组合来满足我们的业务需要。利用redis单线程的特点,以及redis2.6版本后开始对lua的支持,我们采用redis执行lua脚本来确保我们查询+修改的串行执行。后面我们展示了code的实现案例,以及介绍了lua脚本的一些注意事项,可以依葫芦画瓢形式自己实现自己的需求。综合而言,我们分析场景应先分析其核心问题,然后利用一些更简洁的方法或小技巧来落地。
网友评论