springBoot redis分布式鎖lua腳本釋放異常分析及解決方案
在使用SpringBoot集成redis實現分布式鎖時,運用Lua腳本進行鎖釋放可能會遇到返回值類型不匹配和IllegalStateException異常。本文將通過一個案例分析問題根源并提供解決方案。
問題描述:
開發者使用Lua腳本釋放Redis分布式鎖,代碼片段如下:
public void unlock(String key, Object value) { String script = "if (redis.call('get',KEYS[1]) == ARGV[1]) then return redis.call('del',KEYS[1]) else return 0 end"; DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(script); Object result = redisTemplate.execute(redisScript, Collections.singletonList(key), value); }
該腳本旨在檢查key的值是否與傳入的value匹配,若匹配則刪除key(釋放鎖),否則返回0。運行時出現以下問題:
- 問題一:redisTemplate.execute()返回值類型為Object,而非預期的Long。
- 問題二:單元測試執行unlock方法時拋出org.springframework.data.redis.RedisSystemException: Redis exception; nested exception is io.lettuce.core.RedisException: Java.lang.IllegalStateException異常。
錯誤原因及解決方案:
分析錯誤日志和代碼,問題主要在于兩點:
-
Collections.singletonList(key)類型不匹配:雖然Collections.singletonList(key)返回List
,但redisTemplate在處理keys參數時存在類型轉換問題。 Lua腳本期望KEYS為一個鍵的數組。 使用ArrayList明確指定keys參數類型可以解決此問題。 -
redisTemplate泛型類型不匹配:DefaultRedisScript
期望返回Long類型,但redisTemplate可能無法直接返回Long類型。Lua腳本返回的是數值型字符串,需要進行類型轉換。使用StringRedisTemplate更適合處理字符串類型的key和value,并能更直接地處理Lua腳本返回的數值型字符串。
修改后的代碼:
StringRedisTemplate stringRedisTemplate; // Inject StringRedisTemplate public void unlock(String key, String value) { // Changed Object value to String value String script = "if (redis.call('GET',KEYS[1]) == ARGV[1]) then return redis.call('DEL',KEYS[1]) else return 0 end"; DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(script); redisScript.setResultType(Long.class); // Explicitly set the result type List<String> keys = new ArrayList<>(); keys.add(key); Long result = stringRedisTemplate.execute(redisScript, keys, value); // Use StringRedisTemplate System.out.println(result); }
通過以上修改,解決了返回值類型不匹配和IllegalStateException異常,確保Lua腳本正確執行。 關鍵在于使用StringRedisTemplate并顯式設置redisScript的resultType為Long.class。 同時,將value參數的類型改為String,以匹配Lua腳本中的ARGV[1]。
記住在你的spring boot配置中注入StringRedisTemplate。 例如:
@Bean public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) { StringRedisTemplate template = new StringRedisTemplate(); template.setConnectionFactory(redisConnectionFactory); return template; }