Redis跑lua脚本的两种方式
By:Roy.LiuLast updated:2021-06-09
redis跑lua脚本,可以实现事务,保证数据一致性,还可以节省网络开销,多次操作变成了批量操作。所以在项目中是经常用的。网上最典型的例子就是setnx的 lua 的实现。我自己测试就不用这个了,我用两个用户,假设他们转账的过程用LUA脚本来实现。

第一种方式,假设lua脚本是在代码里面动态生成的:
@GetMapping("/lua_script")
public Map<String, Object> testLuaParm() {
//定义 Lua脚本
String lua =
"local result = {}\n" +
"local fromBalance = tonumber(redis.call('HGET', 'account', KEYS[1]))\n" +
"local toBalance = tonumber(redis.call('HGET', 'account', KEYS[2]))\n" +
"local amount = tonumber(ARGV[1])\n" +
"if fromBalance >= amount\n" +
"then\n" +
" redis.call('HSET', 'account', KEYS[1], fromBalance - amount)\n" +
" redis.call('HSET', 'account', KEYS[2], toBalance + amount)\n" +
" result[1] = \"succ\"\n" +
" return result\n" +
"end\n" +
"result[1] = \"fail\"\n" +
"return result";
DefaultRedisScript redisScript = new DefaultRedisScript();
//设置返回类型,这步必须要设置
redisScript.setResultType(List.class);
//设置脚本
redisScript.setScriptText(lua);
// 初始化数据
redisTemplate.opsForHash().putIfAbsent("account", "user_a", "101");
redisTemplate.opsForHash().putIfAbsent("account", "user_b", "20");
List<String> keys = new ArrayList<>();
keys.add("user_a");
keys.add("user_b");
//执行
Object result = redisTemplate.execute(redisScript, keys, "20");
Map<String, Object> map = new HashMap<>();
map.put("result", result);
map.put("user_a_remain", redisTemplate.opsForHash().get("account", "user_a"));
map.put("user_b_remain", redisTemplate.opsForHash().get("account", "user_b"));
return map;
}第二种方式,假设lua 脚本是一个文件,其实与第一种类似, 文件内容如下:
--transfer.lua
--用REDIS调用LUA脚本操作两个账户,一个减少,一个增加,但在一个事务里面.
local result = {}
local fromBalance = tonumber(redis.call('HGET', 'account', KEYS[1]))
local toBalance = tonumber(redis.call('HGET', 'account', KEYS[2]))
local amount = tonumber(ARGV[1])
if fromBalance >= amount
then
redis.call('HSET', 'account', KEYS[1], fromBalance - amount)
redis.call('HSET', 'account', KEYS[2], toBalance + amount)
result[1] = "succ"
return result
end
result[1] = "fail"
return result文件是放在resources/scripts 目录下的。这种方式的时候,需要我们定义一个BEAN.
@Bean("redisTransferLua")
public RedisScript<List> script() {
Resource scriptSource = new ClassPathResource("scripts/transfer.lua");
return RedisScript.of(scriptSource, List.class);
}这个时候,controller 可以这么写:
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Autowired
@Qualifier("redisTransferLua")
private RedisScript<List> script;
@GetMapping("/run_lua")
public Map<String, Object> runLuaScript() {
// 初始化数据
redisTemplate.opsForHash().putIfAbsent("account", "user_a", "101");
redisTemplate.opsForHash().putIfAbsent("account", "user_b", "20");
List<String> keys = new ArrayList<>();
keys.add("user_a");
keys.add("user_b");
RedisSerializer<String> stringRedisSerializer = redisTemplate.getStringSerializer();
List flag = (List) redisTemplate.execute(script, keys, "20");
Map<String, Object> retMap = new HashMap<>();
retMap.put("result", flag);
retMap.put("user_a_remain", redisTemplate.opsForHash().get("account", "user_a"));
retMap.put("user_b_remain", redisTemplate.opsForHash().get("account", "user_b"));
return retMap;
}这两种方式都实现了redis运行lua脚本, 而且效果都非常不错,支持原子事务. 通过这个例子可以看出,如果今后有批量操作的。都可以用这种方式批量传参数解决,会高效很多。这里用到的pom参考:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
From:一号门
Previous:网上收集的一个java版的雪花算法,可以直接用
Next:java中用不可见字符作为分隔符

COMMENTS