Lua脚本
Lua/ˈluə/是一种轻量级脚本语言,它是用 C 语言编写的,跟数据的存储过程有点类 似。 使用 Lua 脚本来执行 Redis 命令的好处:
- 一次发送多个命令,减少网络开销。
- Redis 会将整个脚本作为一个整体执行,不会被其他请求打断,保持原子性。
- 对于复杂的组合命令,我们可以放在文件中,可以实现程序之间的命令集复
用。
在Redis中调用Lua脚本
redis> eval lua-script key-num [key1 key2 key3 ....] [value1 value2 value3 ....]
eval代表执行Lua语言的命令。
- lua-script代表Lua语言脚本内容。
- key-num表示参数中有多少个key,需要注意的是Redis中key是从1开始的,如果没有key的参数,那么写0。 [key1key2key3...]是key作为参数传递给Lua语言,也可以不填,但是需要和key-num的个数对应起来。
- [value1 value2 value3 ....]这些参数传递给 Lua 语言,它们是可填可不填的。
示例:
127.0.0.1:6379> eval "return KEYS[1]" 1 'Hello World'
"Hello World"
在Lua脚本中调用Redis命令
使用 redis.call(command, key [param1, param2...])进行操作。语法格式:
redis> eval "redis.call('set',KEYS[1],ARGV[1])" 1 lua-key lua-value
- command是命令,包括set、get、del等。
- key是被操作的键。
- param1,param2...代表给key的参数。
Lua 是在调用时用 key 表示形参,argv 表示参数值(实参)。
示例:
127.0.0.1:6379> eval "return redis.call('set',KEYS[1],ARGV[1])" 1 edg meiko
127.0.0.1:6379> get edg
"meiko"
在Java中的操作:
在这里是以spring-redis 2.0.8.RELEASE为例,实现一个限流的功能。
@Component
public class RedisManager {
@Resource
private RedisTemplate<String, Object> redisTemplate;
private final static DefaultRedisScript<Boolean> RATE_LIMIT = new DefaultRedisScript<>(
"local times = tonumber(ARGV[3])" +
"local result_1 = redis.call('SETNX',KEYS[1],ARGV[3])" +
"if result_1 == 1 then redis.call('expire',KEYS[1], ARGV[1]) else times = redis.call('incrby',KEYS[1],ARGV[3]) end " +
"if times > tonumber(ARGV[2]) then return 0 end return 1 " , Boolean.class);
public Boolean limit(String key, long rate, long time, final TimeUnit unit, int value) throws Exception {
return this.count(key, value, time, unit, rate);
}
public Boolean count(String key, long value, long time, final TimeUnit unit, long rate) throws Exception {
List<String> redisKeys = Arrays.asList(key);
return redisTemplate.execute(RATE_LIMIT, redisKeys, TimeoutUtils.toSeconds(time, unit), rate, value);
}
}
在redis中缓存lua脚本:
在脚本比较长的情况下,如果每次调用脚本都需要把整个脚本传给 Redis 服务端, 会产生比较大的网络开销。为了解决这个问题,Redis 提供了 EVALSHA 命令,允许开发者通过脚本内容的 SHA1 摘要来执行脚本。
如何缓存:
Redis 在执行 script load 命令时会计算脚本的 SHA1 摘要并记录在脚本缓存中,执行 evalsha 命令时 Redis 会根据提供的摘要从脚本缓存中查找对应的脚本内容,如果找到了则执行脚本,否则会返回错误:"NOSCRIPT No matching script. Please use EVAL."。
127.0.0.1:6379> script load "return 'Hello World'"
"470877a599ac74fbfda41caa908de682c5fc7d4b"
27.0.0.1:6379> evalsha 470877a599ac74fbfda41caa908de682c5fc7d4b 0
"Hello World"