Redis跑lua脚本的两种方式

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

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>


上一篇: 网上收集的一个java版的雪花算法,可以直接用
下一篇: java中用不可见字符作为分隔符
 评论 ( What Do You Think )
名称
邮箱
网址
评论
验证
   
 

 


  • 微信公众号

  • 我的微信

站点声明:

1、一号门博客CMS,由Python, MySQL, Nginx, Wsgi 强力驱动

2、部分文章或者资源来源于互联网, 有时候很难判断是否侵权, 若有侵权, 请联系邮箱:summer@yihaomen.com, 同时欢迎大家注册用户,主动发布无版权争议的 文章/资源.

3、鄂ICP备14001754号-3, 鄂公网安备 42280202422812号