本文代码对应的github地址:https://github.com/nieandsun/redis-study
文章目录
1 redis脚本 & 事务
我在《【redis知识点整理】 — redis事务简介》那篇文章里翻译过Redis官网( https://redis.io/topics/transactions )的一段话,现再次贴在下面:
这段话啥意思呢?
- (1) redis脚本可以做现在redis提供的事务的任何工作,且脚本方式更简单,更快
- (2)之所以又有脚本方式、又有本文(《【redis知识点整理】 — redis事务简介》)介绍的这些方式,是因为本文介绍的内容很早就有了,脚本方式是Redis2.6才引入的
- (3)或许未来所有使用redis的用户都只用脚本方式进行事务的操作,如果真是那样,redis官网就有可能弃用并删除本文介绍的事务方式。
从上面的翻译可以看出来,使用脚本方式操作redis将满足事务特性。 这里有三点我觉得必须得点出来:
- (1)redis使用的脚本为Lua脚本
- (2)Lua脚本的特性为脚本内的命令要么全部执行成功,要么全部失败
- (3)基于(2)并与《【redis知识点整理】 — redis事务简介》那篇文章介绍的redis提供的事务进行对比可知:
- redis使用MULTI开启的事务为弱事务,它虽然也是原子性的,但是强调的是事务内的命令
要么全部执行,要么全部不执行
- redis使用脚本方式运行的多条命令,将处于一个强事务内:
要么全部执行成功,要么全部失败!!!
- redis使用MULTI开启的事务为弱事务,它虽然也是原子性的,但是强调的是事务内的命令
两者是有本质区别的。
2 redis脚本的玩法 & Linux篇
事实上,我们其实根本不用安装Lua,因为redis本身就可以解析Lua脚本,至于Redis为啥要使用Lua作为脚本语言、以及Lua语言的语法等有兴趣的可以自行百度,比如说如下网站:
Lua官网: http://www.lua.org/
菜鸟教程:https://www.runoob.com/lua/lua-tutorial.html
redis官网关于脚本的介绍:https://redis.io/commands/eval
redis脚本主要有如下两种玩法。
2.1 玩法1 — EVAL
语法如下:
EVAL script numkeys key [key ...] arg [arg ...]
- EVAL:表示redis使用字符串脚本
- script:为具体的脚本
- numkeys :操作的key的数量
- key [key …]:具体要操作的key
- arg [arg …] : 往脚本中传入的参数
举例如下:
上面的命令其实就相当于set name yoyo
,由此其实可以看到,在脚本中:
- KEYS[num] —> 表示接收的redis的key
- ARGV[num] —>表示接收的redis的value
2.2 玩法2 — EVALSHA
语法如下:
EVALSHA sha1 numkeys key [key ...] arg [arg ...]
可以看到该语法除了前两个指令外其他的和EVAL的语法都一样,这里简单介绍一下前两个指令的意思:
- EVALSHA: 表示使用EVALSHA 的方式运行redis脚本(☺☺☺)
- sha1 :其实指的是某个脚本生成的sha值
下面详细介绍某个脚本生成sha值的方法:
(1)首先肯定要开启redis服务器
(2)其次在redis的安装目录下(我的为/usr/local/software/redis/bin)写一个简单的redis脚本,比如说
(3)生成SHA值的语法如下
./redis-cli -h 192.168.65.135 -p 6379 -a 123456 script load "$(cat simple.lua )"
上面的命令就是使用redis客户端在
- 服务器为 192.168.65.135 <—> -h 192.168.65.135
- 端口号 6379 <—> -p 6379
- 密码为123456 <—> -a 123456
的redis服务器上为 simple.lua脚本生成sha值。
运行结果如下:
之后我们就可以直接拿着生成sha值进行操作了:
命令如下:
EVALSHA abdd7885574293da651b0a118b1552e42f334b6a 2 name age yoyo 18
接下来看看执行结果:
3 springboot项目中RedisTemplate如何调用Lua脚本
3.1 RedisTemplate调用Lua脚本方法(execute)简介
我们首先看一下调用execute的构造方法:
从上面的图可以看出来,RedisTemplate调用Lua脚本的构造方法有两个,我把他们再单独拿出来稍微解释一下:
- 第一种构造
@Nullable
//第一个参数,对脚本的一个封装
//第二个参数,key组成的list列表
//第三个参数,value组成的可变参数列表
//将其与上面讲的EVAL 或 EVALSHA方式执行LUA脚本的方式进行对比,应该比较好理解
<T> T execute(RedisScript<T> script, List<K> keys, Object... args);
- 第二种构造
//可以看到多了两个参数,如果看过我上篇文章的话,应该对这两个参数并不陌生
//RedisSerializer<?> argsSerializer -->指定Value的序列化方式
//RedisSerializer<T> resultSerializer --> 指定返回结果的序列化方式
@Nullable
<T> T execute(RedisScript<T> script, RedisSerializer<?> argsSerializer, RedisSerializer<T> resultSerializer,
List<K> keys, Object... args);
3.2 方式1 — 直接用字符串构造RedisScript
- demo
@GetMapping("/lua-demo")
public String LuaDemo2() {
//lua脚本
String script = "local key1 = KEYS[1]\n" +
"local key2 = KEYS[2]\n" +
"local arg1 = ARGV[1]\n" +
"local arg2 = tonumber(ARGV[2])\n" +
"\n" +
"redis.call(\"SET\", key1, arg1)\n" +
"redis.call(\"lpush\",key2,arg2)\n" +
"\n" +
"return 1";
// 构造RedisScript并指定返回类类型
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
// 参数一:redisScript,参数二:key列表,参数三:arg(可多个)
Long result = redisTemplate.execute(redisScript, Arrays.asList("name111", "age111"), "yoyo111", 19);
System.out.println(result);
return "OK";
}
- 测试结果如下:
这里要注意一下
,name111之所以不是一个简单的字符串"yoyo",是因为我指定Value的序列化方式为Jackson2JsonRedisSerializer
3.3 方式2 — 读取lua脚本来构造RedisScript
比如说我将lua脚本放在了下面的目录下:
则可以按照如下的方式进行读取lua脚本、构造RedisScript对象
@GetMapping("/lua-limit")
public String LuaDemo() {
// 构造RedisScript
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
// 指定要使用的lua脚本
redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("redis-lua/ipcount.lua")));
//指定返回类型
redisScript.setResultType(Long.class);
// 参数一:redisScript,参数二:key列表,参数三:arg(可多个)
Long result = redisTemplate.execute(redisScript, Arrays.asList("127.0.0.1"), 5, 2);
log.info("是否获可以访问:{}", result == 1 ? "是" : "否");
return "OK";
}
简单用jmeter测试一下上面代码的限流效果
测试计划如下,即2秒内发送15个请求
测试结果如下,可以看到2秒内仅有5个请求可以访问,说明我们写的限流小demo可用
end!!!