redis的lua脚本使用

183 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第10天,点击查看活动详情

lua 是一种轻量小巧的脚本语言,用标准C语言编写并以源代码形式开放, 其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。

一、在redis使用lua脚本的好处

  1. 减少网络开销。可以将多个请求通过脚本的形式一次发送,减少网络时延。
  2. 原子操作。Redis会将整个脚本作为一个整体执行,中间不会被其他请求插入。因此在脚本运行过程中无需担心会出现竞态条件,无需使用事务。
  3. 复用。客户端发送的脚本会永久存在redis中,这样其他客户端可以复用这一脚本,而不需要使用代码完成相同的逻辑。

二、redis使用Lua脚本

//返回redis中KEYS[1] 的值 
retrun redis.call('GET',KEYS[1])

lua 的常见命令

命令不多,就下面这几个:

  • EVAL
  • EVALSHA
  • SCRIPT LOAD - SCRIPT EXISTS
  • SCRIPT FLUSH
  • SCRIPT KILL

命令格式:EVAL script numkeys key [key …] arg [arg …]

  1. script参数是一段 Lua5.1 脚本程序。脚本不必(也不应该[^1])定义为一个 Lua 函数\
  2. numkeys指定后续参数有几个key,即:key [key …]中key的个数。如没有key,则为0\
  3. key [key …] 从 EVAL 的第三个参数开始算起,表示在脚本中所用到的那些 Redis 键(key)。在Lua脚本中通过KEYS[1], KEYS[2]获取。\
  4. arg [arg …] 附加参数。在Lua脚本中通过ARGV[1],ARGV[2]获取。

eg:


// 例:numkeys=2,keys数组有两个元素key1和key2,arg数组元素中有两个元素first和second 
//      其实{KEYS[1],KEYS[2],ARGV[1],ARGV[2]}表示的是Lua语法中“使用默认索引”的table[数组],
//      相当于java中的map中存放四条数据。Key分别为:1234,而对应的value才是:KEYS[1]、KEYS[2]、ARGV[1]、ARGV[2]
//      举此例子仅为说明eval命令中参数的如何使用。项目中编写Lua脚本最好遵从key、arg的规范。
127.0.0.1:6379> eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 first second 
1) "key1"
2) "key2"
3) "first"
4) "second"
// 例:使用了redis为lua内置的redis.call函数
//      脚本内容为:先执行SET命令,在执行EXPIRE命令(相当于执行SETEX key1 60 10)
//      numkeys=1,keys数组有一个元素userAge(代表redis的key)
//      arg数组元素中有两个元素:10(代表userAge对应的value)和60(代表redis的存活时间)
127.0.0.1:6379> EVAL "redis.call('SET', KEYS[1], ARGV[1]);redis.call('EXPIRE', KEYS[1], ARGV[2]); return 1;" 1 userAge 10 60
(integer) 1
127.0.0.1:6379> get userAge
"10"
127.0.0.1:6379> ttl userAge
(integer) 50

三、Redis执行Lua脚本文件

这里编写了一个限流limit.lua的脚本文件:

local key = KEYS[1]
local count = tonumber(ARGV[1])
local time = tonumber(ARGV[2])
local current = redis.call('get', key)
if current and tonumber(current) > count then
	return tonumber(current)
end
current = redis.call('incr',key)
if tonumber(current) == 1 then
	redis.call('expire', key, time)
end

return tonumber(current)
执行Lua脚本文件

执行命令: redis-cli -a 密码 --eval Lua脚本路径 key [key …] , arg [arg …]

如:redis-cli -a 123456 --eval ./limit.lua userName , zhangsan lisi

四、redisTemplate执行脚本的方法封装

redis配置文件创建beanDefaultRedisScript

/**
 * 读取限流脚本
 */
@Bean
public DefaultRedisScript<Long> redisluaScript() {
    DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
    //这里脚本的路径为path for source root 路径
    redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("lua/limit.lua")));
    redisScript.setResultType(Long.class);
    return redisScript;
}

/**
 * 用于执行限流的脚本
 */
@Bean
public RedisTemplate<String, Serializable> limitRedisTemplate(LettuceConnectionFactory redisConnectionFactory) {
    RedisTemplate<String, Serializable> template = new RedisTemplate<String, Serializable>();
    template.setKeySerializer(new StringRedisSerializer());
    template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
    template.setConnectionFactory(redisConnectionFactory);
    return template;
}
@Autowired
private RedisTemplate limitRedisTemplate;

@Autowired
private DefaultRedisScript redisluaScript;


public R limit(){
    Object l =limitRedisTemplate.execute(redisluaScript, Arrays.asList("test_limit_rate_1"),time,30000L);
    log.info("l = {}",l);
    Long done = Long.parseLong(l.toString());
    System.out.println(done);
    if(done>time){
        return R.ok("超限");
    }
    return R.ok();
}

参考文章: Redis的lua脚本:blog.csdn.net/weixin_4285… Redis 使用lua脚本最全教程:blog.csdn.net/le_17_4_6/a…