本文代碼對應的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!!!