01、使用Lua脚本来执行Redis命令的好处
- 一次发送多个命令,减少网络开销。
- Redis会将整个脚本作为一个整体执行,不会被其他请求打断,保持原子性。
- 对于复杂的组合命令,我们可以放在文件中,可以实现程序之间的命令集复用。
02、Redis中调用Lua脚本
使用eval方法,语法格式如下:
- eval代表执行Lua语言的命令。
- lua-script代表Lua语言脚本内容。
- key-num表示参数中有多少个key,需要注意的是Redis中key是从1开始的,如果没有key的参数,那么写0。
- [key1key2key3...]是key作为参数传递给Lua语言,也可以不填,但是需要和key-num的个数对应起来。
- [value1value2value3....]这些参数传递给Lua语言,它们是可填可不填的。
示例,返回一个字符串,0个参数:
03、在Lua脚本中调用Redis命令
使用redis.call(command,key[param1,param2...])进行操作。语法格式如下:
- command是命令,包括set、get、del等。
- key是被操作的键。
- param1,param2...代表给key的参数。
【1】、在Redis中调用Lua脚本执行Redis命令
以上命令等价于setgupao2673。
【2】、Redis中调用Lua脚本文件中的命令,操作Redis
创建Lua脚本文件:
Lua脚本内容,先设置,再取值:
在Redis客户端中调用Lua脚本
得到返回值:
【3】、案例 对IP进行限流
需求:在X秒内只能访问Y次。
设计思路:用key记录IP,用value记录访问次数。
拿到IP以后,对IP+1。如果是第一次访问,对key设置过期时间(参数1)。否则判断次数,超过限定的次数(参数2),返回0。如果没有超过次数则返回1。超过时间,key过期之后,可以再次访问。
KEY[1]是IP,ARGV[1]是过期时间X,ARGV[2]是限制访问的次数Y。
6秒钟内限制访问10次,调用测试(连续调用10次):
app:ip:limit:192.168.8.111是key值,后面是参数值,中间要加上一个空格和一个逗号,再加上一个空格。
即:./redis-cli–eval [lua脚本] [key...] 空格,空格[args...]
多个参数之间用一个空格分割。
03、缓存Lua脚本
【1】、为什么要缓存
在脚本比较长的情况下,如果每次调用脚本都需要把整个脚本传给Redis服务端,会产生比较大的网络开销。
【2】、如何缓存
Redis提供了EVALSHA命令,允许开发者通过脚本内容的SHA1摘要来执行脚本。
Redis在执行scriptload命令时会计算脚本的SHA1摘要并记录在脚本缓存中,执行EVALSHA命令时Redis会根据提供的摘要从脚本缓存中查找对应的脚本内容,如果找到了则执行脚本,否则会返回错误:"NOSCRIPT No matching script.Please use EVAL."
【3】、案例 自乘案例
自乘的运算,让它乘以后面的参数:
把这个脚本变成单行,语句之间使用分号隔开
script load '命令'
返回
"be4f93d8a5379e5e5b768a74e77c8a4eb0434441"
调用:
127.0.0.1:6379>set num 2
OK
127.0.0.1:6379>evalsha be4f93d8a5379e5e5b768a74e77c8a4eb0434441 1 num 6
(integer)12
04、脚本超时
为了防止某个脚本执行时间过长导致Redis无法提供服务,Redis提供了lua-time-limit参数限制脚本的最长运行时间,默认为5秒钟。
lua-time-limit 5000(redis.conf配置文件中)
Redis提供了一个script kill的命令来中止脚本的执行。新开一个客户端:
如果当前执行的Lua脚本对Redis的数据进行了修改(SET、DEL等),那么通过scriptkill命令是不能终止脚本运行的。
因为要保证脚本运行的原子性,如果脚本执行了一部分终止,那就违背了脚本原子性的要求。最终要保证脚本要么都执行,要么都不执行。
127.0.0.1:6379>script kill
(error)UNKILLABLE Sorry the script already executed write commands against the dataset.You can either wait the script terminationor kill the server in a hard way using the SHUTDOWN NOSAVE command.
遇到这种情况,只能通过shutdown nosave命令来强行终止redis。
05、java调用案例
public class LuaTest { public static void main(String[] args) { Jedis jedis = getJedisUtil(); jedis.eval("return redis.call('set',KEYS[1],ARGV[1])", 1,"test:lua:key","qingshan2673lua"); System.out.println(jedis.get("test:lua:key")); for(int i=0; i<10; i++){ limit(); } }
/** * 10秒内限制访问5次 */ public static void limit(){ Jedis jedis = getJedisUtil(); // 只在第一次对key设置过期时间 String lua = "local num = redis.call('incr', KEYS[1])\n" + "if tonumber(num) == 1 then\n" + "\tredis.call('expire', KEYS[1], ARGV[1])\n" + "\treturn 1\n" + "elseif tonumber(num) > tonumber(ARGV[2]) then\n" + "\treturn 0\n" + "else \n" + "\treturn 1\n" + "end\n"; Object result = jedis.evalsha(jedis.scriptLoad(lua), Arrays.asList("localhost"), Arrays.asList("10", "5")); System.out.println(result); }
private static Jedis getJedisUtil() { String ip = ResourceUtil.getKey("redis.host"); int port = Integer.valueOf(ResourceUtil.getKey("redis.port")); String password = ResourceUtil.getKey("redis.password"); JedisPoolConfig jedisPoolConfig = new JedisPoolConfig(); JedisPool pool = new JedisPool(jedisPoolConfig, ip, port, 10000, password); return pool.getResource(); }
}
|
请发表评论