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