文章目錄
- 執行Lua腳本后一直查詢不到Redis中的數據(附帶詳細問題排查過程,一波三折)
- 問題背景
- 問題1:Lua腳本無法切庫
- 問題2:RedisTemlate切庫報錯
- 問題3:序列化導致數據不一致
- 問題4:Lua腳本中單引號無法拼接字符串
- 總結
執行Lua腳本后一直查詢不到Redis中的數據(附帶詳細問題排查過程,一波三折)
這個問題坑慘我了,估計耗費了我兩個小時😫,中間走了不少彎路,好在我靈光一閃+GPT給我的靈感否則就栽在這上面了
問題背景
-
問題背景
在使用 Redis 實現接口調用次數扣減操作時,發現響應結果一直返回的是 -1,也就是查詢不到數據
問題1:Lua腳本無法切庫
-
問題排查過程1:
經過一段排查,加上GPT的提示信息,我快速定位到了是可能是參數的問題,但是通過 Debug 斷點調試,我可以百分百肯定這個參數肯定沒有問題;然后我又到 redis-cli 執行原生的 Redis 指令,發現也查不到數據!(○′・д・)ノ,懵逼了,搗鼓了半天突然想起來有沒有可能是這個數據庫中壓根沒有數據,于是我使用
keys *
指令查看數據庫中是否有數據,結果發現有數據,但是壓根就沒有我預熱的數據!!!這是什么原因呢?一看 REP 就恍然大悟了,我操作的數據庫1,但是由于我配置文件中使用的是數據庫 0,導致我預熱的數據 都在 數據庫1中,而 Lua腳本默認操作的數據庫0,那么問題就簡單了,直接使用redis('select',1)
這條指令切換一下數據庫不久OK了嗎(我真聰明🤭)然后我先在redis-cli上實驗,發現的確是這個的原因,嘿嘿問題成功解決了?(你不會以為這就完了吧🤣) -
問題原因:Lua腳本默認使用的數據庫0,我的數據在數據庫1中
-
問題解決
在執行Lua腳本前,先切換一下數據庫
redis('SELECT', 1)
==結果發現在 Lua 腳本中添加了
redis('SELECT', 1)
之后壓根就沒有用!==┭┮﹏┭┮只能繼續排查問題,
-
問題排查過程2
在lua腳本中查詢數據前,加上
redis('select',1)
,結果發現嘿嘿還是返回-1!這下徹底懵逼了,結果經過詢問ChartGPT,發現:Lua腳本不能切換數據庫,還是太天真了既然Lua腳本無法完成切庫,那么就需要使用 RedisTemplate 進行切庫
-
問題解決:
RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();connection.select(1);
你不會以為這么快就解決了這個問題吧!
問題2:RedisTemlate切庫報錯
-
問題排查過程2:
redisTemplate
切庫引發的新問題:在添加了上面代碼后,結果又報錯Selecting a new database not supported due to shared connection. Use separate ConnectionFactorys to work with multiple databases
.在此詢問GPT,發現是由于 Redis 的共享連接限制,無法直接在同一個連接上切換到不同的數據庫。如果你需要在執行 Lua 腳本之前切換 Redis 數據庫,可以嘗試使用不同的連接工廠來處理多個數據庫
-
問題解決
-
解決方案一:直接關閉RedisTemplate的共享連接(不推薦)
詳情請參考這篇文章:Springboot整合redis切庫問題
在使用 Spring Data Redis 時,默認情況下是共享連接的,因為這可以提高性能和效率。然而,如果你確實需要關閉共享連接,并為每個數據庫創建一個獨立的連接,也是可以的。但是需要注意一些潛在的問題:
- 性能影響:共享連接可以減少連接的開銷,而獨立連接則需要更多的資源來維護和管理。因此,關閉共享連接可能會對系統的整體性能產生一定的影響。
- 連接池限制:Redis 連接池有著最大連接數的限制,每個連接都占用一部分連接資源。如果為每個數據庫都創建獨立的連接,那么連接池中可用的連接數量將被均分,這可能導致每個數據庫可用連接數的減少。
- 連接管理復雜性:對于每個獨立的連接,你需要額外管理和維護連接的生命周期,包括創建、銷毀、異常處理等。這可能增加代碼的復雜性和維護成本。
總之,關閉共享連接并為每個數據庫創建獨立的連接可能會帶來性能和連接管理方面的一些負面影響。因此,在進行決策時,請權衡利弊,并根據具體需求和系統規模做出選擇。
-
解決方案二:新建一個RedisTemplate
為了提高代碼的復用性,我就打算利用 單例模式+原型模式 對IOC容器中的RedisTemplate進行一個拷貝,然后將這個新的RedisTemplate來切庫,相對于方案一要更加方便
下面是代碼:
package com.ghp.admin.redis;import com.ghp.common.utils.BeanConvertorUtils; import org.springframework.data.redis.connection.RedisConnection; import org.springframework.data.redis.core.RedisTemplate;/*** @author ghp* @title* @description*/ public class RedisUtils {private RedisTemplate redisTemplate;private static RedisUtils instance;static {instance = new RedisUtils();}public RedisUtils getInstance(RedisTemplate redisTemplate){this.redisTemplate = redisTemplate;return instance;}/*** 創建一個新的RedisTemplate,并且切換數據庫* @param databaseIndex* @return*/public RedisTemplate<String, String> createRedisTemplate(int databaseIndex) {// 進行拷貝(這里是是使用自己封裝的工具類,你可以選擇使用Spring框架自帶的拷貝工具類)RedisTemplate newRedisTemplate = BeanConvertorUtils.copyBean(redisTemplate,RedisTemplate.class);RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();connection.select(databaseIndex);return newRedisTemplate;} }
看上去好像已經成功解決了,但是還沒完,經過測試發現執行 Lua 腳本后仍然無法從Redis中查詢到數據庫w(゚Д゚)w
這些我又陷入了懵逼中
-
問題3:序列化導致數據不一致
-
問題排查過程3:
我開始陷入懷疑當中,因為我在 redis-cli 中經過切庫后,發現執行指令是可以查詢到數據的,難道是切庫失敗了?經過檢驗發現切庫是成功的,因為我在 Lua 腳本中執行硬編碼
local leftNum = redis.call('HGET', 'cache:interface:pathMethod:', '/api/name/user-POST')
是的的確確查詢到了數據的!我開始懷疑是不是我參數傳遞錯了,但是經過
return leftNum
發現是有數據的,而且數據是真確的,這是什么原因呢?雖然返回結果是一樣的,可能不可能Lua腳本中的值不一樣呢?我繼續抱著試一試的態度,執行下面的代碼
local key1 = KEYS[1] if key1 == '/api/name/user' thenreturn 1 end return 2
結果意料之外的居然返回了 2,也就是說我 傳入了 ’/api/name/user‘,然后從 Lua 中 返回的是 ’/api/name/user‘,但是 Lua 中兩個值竟然不相等!
我開始懷疑難道是編碼的問題嗎?因為之前在看微信公眾號上的一篇文章,有個大佬就是因為編碼(全角和半角)的問題導致查詢不到數據,于是我抱著試一試的心態,測試了一下編碼,有看了一下編譯器使用的編碼格式,發現是全局
UTF-8
所以可以排除編碼問題了,那會是什么原因呢,百思不得其解,突然靈光一閃會不會是序列化的問題,我全局為RedisTemplate配置了序列化轉換器,但是我還是不敢相信,因為我在Lua腳本中返回的
retrun ARGV[1]
和我查詢使用的 key也就是/api/name/user
是一摸一樣的發現仍然照樣沒有查詢到數據,在 Lua 腳本中返回 pathJson,發現是
\"/api/name/user\"
,這一下又給了靈感,會不會是傳入的JSON多了有個\"
,于是我做出如下實驗local key1 = KEYS[1] if key1 == '\"/api/name/user\"' thenreturn 1 end return 2
驚奇的發現這回終于返回 1,也就是說傳入的 path 是 竟然是
\"/api/name/user\"
(結果很震驚,因為之前我在Lua腳本中返回path時,結果就是/api/name/user
,返回的結果根本沒有"
),我猜測應該是RedisTemplate在處理Lua的返回結果時會多做一層序列化,登錄參數的傳遞過程中需要經過一層序列化!至此問題終于解決了(你不會以為這就成功解決了吧) -
解決方案
在接收參數后進行預處理,也就是去掉傳入參數中多余的
”
local key1 = string.gsub(KEYS[1], "\"", "") -- cache:interface:pathMethod: local key2 = string.gsub(KEYS[2], "\"", "") -- cache:interface:leftNum: -- 獲取hashKey local path = string.gsub(ARGV[1], "\"", "") local method = string.gsub(ARGV[2], "\"", "") local userId = string.gsub(ARGV[3], "\"", "")
問題4:Lua腳本中單引號無法拼接字符串
-
問題排查4
后面我改造了代碼,發現仍然不成功!!!😫 ┭┮﹏┭┮
經過測試,我發現
local key1 = path .. '-' .. method return key1
發現只會返回
-
前面的字符串 也就是 path,如果調換path和method的未知,那么只會返回method,我又陷入了沉思。后面我突然想起來了,是Lua中雙引號和單引號的區別,使用
..
運算符可以用來拼接字符串。這個運算符可以連接兩個字符串(或者將其他數據類型轉換為字符串后連接)。然而,..
運算符只能用于連接雙引號字符串。單引號字符串在 Lua 中表示字符,而不是字符串。如果你試圖使用..
運算符連接兩個單引號字符串,會得到一個語法錯誤。 -
解決方法
local key1 = tostring(path) .. "-" .. tostring(method) return key1
成功解決了。如果你對Lua感興趣的可以參考這篇文章:Lua快速入門筆記_知識汲取者的博客-CSDN博客
總結
終于解決了這個問題,感覺好舒爽😄
總的來說,我遇到的問題是 Java 代碼使用 RedisTemplate.execute
執行 Lua腳本的時候,由于我配置了全局序列化,所以導致 傳入Lua腳本中的參數 會先被序列化,而Lua腳本的參數被傳出來時同樣會被反序列化,這個序列化和反序列化對我而言是透明的,所以導致我沒有想到居然序列化了一遍,從而導致我走了好多彎路
這里提一嘴:前面那個Lua腳本默認使用數據庫 0不完全正確,如果我們在配置文件中配置了使用哪一個數據庫,那么在Lua腳本中就會使用哪一個數據庫,所以我們可以不用切庫,也能保障 Lua腳本能夠操作數據庫1,所以前面的 問題1 和 問題2 不需要,根本原因在于序列化和反序列化問題
參考資料
- ChartGPT3.5
- Springboot整合redis切庫問題