前言
可使用版本:从 Redis 2.6.0 版本开始起;可通过内置的 Lua 解释器,可以使用 EVAL 命令对 Lua 脚本进行执行。
时间复杂度:根据脚本的复杂度而定(脚本尽量简洁)。
看到网上很多文章每次使用lua 脚本 都要把脚本发送到redis 客户端 然后执行, 在高并发环境下 这个io 是很巨大的,所以我们可以提前把脚本内容提前缓存到redis 客户端 通过sha1 值去执行。
流程
spring Boot+redisTemplate 的整合和配置就不在这写了
第一步
先把lua 脚本 放在 resource 目录下
--库存key
local key1 = KEYS[1]
--事务回查key
local key2 = KEYS[2]
--扣减数量
local num = ARGV[1]
--操作redis 获取sku库存数stock
local stock = tonumber(redis.call('GET', key1))
local result = 0
--如果库存数大于等于减去的数量 则执行redis的decrby命令
--在lua中,除了nil和false,其他的值都为true,所以可用作判空
if(stock and tonumber(stock) >= tonumber(num)) then
redis.call('DECRBY', key1, num)
result = 1
end
--用于事务回查
redis.call('SET', key2, result)
return result
第二步
配置项
@Slf4j
@Configuration
public class RedisScriptConfig {
@Resource
private StringRedisTemplate stringRedisTemplate;
@Bean
public DefaultRedisScript<Long> deductStockRedisScript() {
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
//我这里的路径是resource 下的 lua 文件下的 .lua文件
redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("lua/deductStock.lua")));
redisScript.setResultType(Long.class);
loadRedisScript(redisScript, "deductStock.lua");
return redisScript;
}
/**
* 加载lua脚本到redis服务器
* @param redisScript
* @param luaName
*/
private void loadRedisScript(DefaultRedisScript<Long> redisScript, String luaName) {
try {
List<Boolean> results = stringRedisTemplate.getConnectionFactory().getConnection().scriptExists(redisScript.getSha1());
if (Boolean.FALSE.equals(results.get(0))) {
String sha = stringRedisTemplate.getConnectionFactory().getConnection().scriptLoad(scriptBytes(redisScript));
log.info("预加载lua脚本成功:{}, sha=[{}]", luaName, sha);
}
} catch (Exception e) {
log.error("预加载lua脚本异常:{}", luaName, e);
}
}
/**
* 序列化lua脚本
* @param script
* @return
*/
private byte[] scriptBytes(RedisScript<?> script) {
return this.stringRedisTemplate.getStringSerializer().serialize(script.getScriptAsString());
}
}
第三步
这里就是把对象注入进来 然后方法里调用即可
@Resource
private StringRedisTemplate stringRedisTemplate;
@Resource
private RedisScript<Long> deductStockRedisScript;
// 在方法里调用执行
// 第一个参数 对lua 脚本对象的调用
// 第二个参数 是 lua 脚本中 Keys 参数
// 第三个参数 是 lua 脚本中 ARGV 参数
Long result = stringRedisTemplate.execute(deductStockRedisScript, keys, messageVO.getCount());
特此说明一下哈 如果你的redis 是cluster 模式也没有关系。 redisTemplate 会拿到所有的redis 客户端 去缓存脚本。 这个大家也可以自己dubug 一下 源码 看下。