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