持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第10天,点击查看活动详情
lua 是一种轻量小巧的脚本语言,用标准C语言编写并以源代码形式开放, 其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。
一、在redis使用lua脚本的好处
- 减少网络开销。可以将多个请求通过脚本的形式一次发送,减少网络时延。
- 原子操作。Redis会将整个脚本作为一个整体执行,中间不会被其他请求插入。因此在脚本运行过程中无需担心会出现竞态条件,无需使用事务。
- 复用。客户端发送的脚本会永久存在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 …]
- script参数是一段 Lua5.1 脚本程序。脚本不必(也不应该[^1])定义为一个 Lua 函数\
- numkeys指定后续参数有几个key,即:key [key …]中key的个数。如没有key,则为0\
- key [key …] 从 EVAL 的第三个参数开始算起,表示在脚本中所用到的那些 Redis 键(key)。在Lua脚本中通过KEYS[1], KEYS[2]获取。\
- 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分别为:1、2、3、4,而对应的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…