SpringBoot使用Redis脚本

  1. 脚本如下:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16

local key = KEYS[1]
local value = ARGV[1]
local expire = ARGV[2]

if redis.call("get", key) == false then
    if redis.call("set", key, value) then
        if tonumber(expire) > 0 then
            redis.call("expire", key, expire)
        end
        return true
    end
end

return false

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12

local key = KEYS[1]
local current_value = ARGV[1]
local expect_value = ARGV[2]

if redis.call('GET', key) == current_value then
    redis.call('SET', key, expect_value)
    return true
end

return false

  1. 配置如下:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17

@Configuration
public class LuaConfiguration {
    @Bean
    public RedisScript<Boolean> lua02() {
        return RedisScript.of(new ClassPathResource("scripts/lua02.lua"), Boolean.class);
    }

    @Bean
    public DefaultRedisScript<Boolean> lua01() {
        DefaultRedisScript<Boolean> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("scripts/lua01.lua")));
        redisScript.setResultType(Boolean.class);
        return redisScript;
    }
}

  1. 测试代码如下:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

@SpringBootTest
public class Test05_RedisScripting {
    @Autowired
    private RedisScript<Boolean> lua02;
    @Autowired
    private DefaultRedisScript<Boolean> lua01;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Test
    public void lua01() {
        Boolean execute = stringRedisTemplate.execute(lua01,
                Collections.singletonList("lua01_key"),
                "lua01_value", "1000");
        System.out.println("");
    }

    @Test
    public void lua02() {
        stringRedisTemplate.opsForValue().set("lua02_key", "2");

        Object execute = stringRedisTemplate.execute(lua02,
                Collections.singletonList("lua02_key"),
                "2", "3");
        System.out.println("");
    }
}

注意事项

  1. Lua脚本的bug特别可怕,由于Redis的单线程特点,一旦Lua脚本出现不会返回(不是返回值)得问题,那么这个脚本就会阻塞整个redis实例。

  2. Lua脚本应该尽量短小实现关键步骤即可。(原因同上)

  3. Lua脚本中不应该出现常量Key,这样会导致每次执行时都会在脚本字典中新建一个条目,应该使用全局变量数组KEYS和ARGV, KEYS和ARGV的索引都从1开始(这个提醒非常好,因为我习惯去定义变量)

  4. 传递给lua脚本的的键和参数:传递给lua脚本的键列表应该包括可能会读取或者写入的所有键。传入全部的键使得在使用各种分片或者集群技术时,其他软件可以在应用层检查所有的数据是不是都在同一个分片里面。另外集群版redis也会对将要访问的key进行检查,如果不在同一个服务器里面,那么redis将会返回一个错误。(决定使用集群版之前应该考虑业务拆分),参数列表无所谓。(感觉Redis集群服务还是有很多技术点需要研究的,我们现在的应用还停留在表皮)

  5. lua脚本跟单个redis命令和事务段一样都是原子的已经进行了数据写入的lua脚本将无法中断,只能使用SHUTDOWN NOSAVE杀死Redis服务器,所以lua脚本一定要测试好。

参考资料

  1. SpringBoot使用Lua脚本操作Redis