Redis使用Lua腳本的兩個小問題
- 最近在項目中使用redisTemplate 執行Lua 腳本發現兩個比較坑的地方,發現之後其實也很簡單,過程很容易讓人抓狂,
一、整型轉換
1.1 場景
- 邏輯是通過一段 Lua 腳本去給redis中的一個值加上參數1的值,如果結果大於參數2,那麼就把值歸零並返回1,如果小於參數二就不歸零且返回0,
static String SCRIPT =
"local sum = redis.call(\"GET\", KEYS[1]) + ARGV[1];\n" +
"if sum >= ARGV[2] then\n" +
"\t redis.call(\"set\",KEYS[1], 0)\n" +
" return 1\n" +
" else\n" +
" redis.call(\"INCRBY\",KEYS[1],ARGV[1])\n" +
"\t return 0\n" +
" end";
- 測試代碼:
@Test
public void test3() {
DefaultRedisScript<Integer> redisScript = new DefaultRedisScript<>(SCRIPT,Integer.class);
List<String> keys = Lists.newArrayList("dynamic_car.car__statistical") ;
Object execResult = (Object) redisTemplate.execute(redisScript, keys, 3, 10);
if (execResult != null) {
System.out.println(execResult);
} else {
System.out.println("End ... ");
}
}
1.2 問題
- 錯誤1:執行的時候報錯,首先報的是String無法轉換爲Integer,這裏需要將Lua中的 ARGV[2] 改爲: tomumber(ARGV[2]),將參數2轉換爲數字,即使傳進去就是整型也要轉,日誌如下,提示第二行嘗試將String轉換爲number,這裏也感覺很奇怪,明顯傳參是Integer類型。
@user_script:2: user_script:2: attempt to compare string with number
- 錯誤2:在 Lua 中根據條件返回1和0,因此初始化 DefaultRedisScript的泛型是 Integer 類型,執行的時候報錯:拋出異常,但是查看redis Lua的執行卻起了效果,redis中數據已經修改了,由此推測這個異常是和返回值相關,因此把DefaultRedisScript的Integer改成 Long 就好了。
Caused by: io.lettuce.core.RedisException: java.lang.IllegalStateException
- 錯誤3:這個不算錯誤,算是一個bug,在如下Lua中第一行call中如果get的key不存在,則返回的是false,那麼加運算就會拋出異常提示Boolean類型不能運算,這裏需要校驗
"local present = redis.call(\"GET\", KEYS[1]);"
1.3 修復
- 最後修改後的Lua和代碼如下:
public void test3() {
DefaultRedisScript<Long> getRedisScript = new DefaultRedisScript<>(SCRIPT, Long.class);
String redisKet = "dynamic_car.car__statistical1";
List<String> keys = Lists.newArrayList(redisKet);
Object execResult = (Object) redisTemplate.execute(getRedisScript, keys, 3, 10);
System.out.println(execResult + " -- "+ execResult.getClass());
}
- lua
public static String SCRIPT =
"local present = redis.call(\"GET\", KEYS[1]);\n" +
"if present == false then \n" +
"\t redis.call(\"set\",KEYS[1], ARGV[1])\n" +
"\t return 0 \n" +
"\t end" +
"\t local sum = redis.call(\"GET\", KEYS[1]) + ARGV[1];\n" +
"\t if sum >= tonumber(ARGV[2]) then\n" +
"\t redis.call(\"set\",KEYS[1],0)\n" +
" return 1\n" +
" else\n" +
" redis.call(\"INCRBY\",KEYS[1],ARGV[1])\n" +
"\t return 0\n" +
" end";
二、獲取Set元素
2.1 場景
- 通過 Lua 腳本嘗試去獲取一個 Set 集合的全部元素,如果集合存在,就獲取元素並刪除集合,如果不存在,就什麼都不做;
public static String SCRIPT = "\n" +
"\t if redis.call(\"EXISTS\",KEYS[1]) > 0 then\n" +
"\t local datas = redis.call(\"SMEMBERS\",KEYS[1])\n" +
"\t redis.call(\"DEL\",KEYS[1])\n" +
"\treturn datas\n" +
"end\n" +
"\t";
- java代碼
@Test
public void test11() {
DefaultRedisScript<Set> getRedisScript = new DefaultRedisScript<>(SCRIPT1,Set.class);
List<String> keys = Lists.newArrayList("setSchemaTableIds-1");
Object execute = redisTemplate.execute(getRedisScript, keys);
System.out.println(execute);
}
2.2 問題
- 執行後拋出異常:而且發現 execute 返回的總是Set裏面的一個元素,很奇怪,改了很多遍 Lua 腳本,定義Set等都不奏效
java.lang.ClassCastException: java.lang.String cannot be cast to java.util.List
2.3 修復
-
最後將DefaultRedisScript 改成 DefaultRedisScript,就解決問題了,明顯是獲取Set,接收是List,也算一個小坑了。不過網上看到說原始的 Lua 獲取到 Set 裏面的元素順序是不固定的,猜測可能是 redisTemplate 做了封裝,使用List 固定了返回的順序
-
注意我使用的版本是
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.0.2.RELEASE</version>
</dependency>