背景
我的應用中,使用 jedis 作為連接 redis 的客戶端,一直在用的好好的,后來有一個新的組件,也需要使用 redis,但是組件是內部封裝的,我只能提供一個 StringReidsTempalte,所以我基于應用本身構造的 factory,又重新構建了一個新的 template。
使用版本
springboot: 3.0.6
jedis: 4.3.2
redis 安裝版本: 7.0.11
jdk: 17
現狀
當時的現狀就是這樣的:
有一個統一的factory 構造器,構造一個 factory,并將這個 factory 交由 spring 管理。
應用內部統一使用的緩存客戶端,由自定義構造一個 RedisTemplate,而新的組件,使用 factory 構造一個新的 StringRedisTemplate。
表象
我在本地啟動的時候,沒有任何問題,正常和 redis 交互。
部署到服務器啟動的時候,會在實際和 redis 交互的底層邏輯處,報出異常。
報錯位置1:
org.springframework.data.redis.connection.jedis.JedisStringCommands#set(byte[], byte[], org.springframework.data.redis.core.types.Expiration, org.springframework.data.redis.connection.RedisStringCommands.SetOption)
在這個地址執行的時候,invoke 方法沒問題,執行到 from 的時候,debug 執行,會報上面構建的 SetParams 這個類找不到。(只有 debug 會報,正常執行該堆棧打印不出)
通過解包,找到實際服務器的 jar 包中的 lib,發現對應的jar 包是有的,也存在這個類。
后面我就不管這個,果然就繼續執行了,這個地方并無卡點。
執行到 getOrElse 的時候,就會報另外一個異常,也就是服務器會打印出來的異常,Unknown redis exception.
堆棧信息中,大概得處理邏輯就是從cgLib 中獲取客戶端的時候,會有一些地方有卡點,不過太過深入,沒有繼續追,肯定不是底層邏輯的 bug,只能是我的客戶端構造的有問題。
于是我就開始仔細看我構造出來的客戶端,和我正常的客戶端比較。
然而這個也沒有什么異常的情況。
我的自己構建的客戶端連接池,正常使用,用了很久了,一直沒問題,實際到底層的時候,都是在一個地方使用的,應該是沒啥問題。
通過網上找關于 jedis 客戶端的使用 demo,幾乎和我的沒差別,只是調優參數不一樣。
jedis 報錯:Unknown redis exception 的幾種常見問題
- Redis 服務未啟動或者未連接:在程序連接 Redis 時,如果 Redis 服務未啟動或者程序無法連接到 Redis,就會出現這個異常。確保 Redis 服務已啟動,且連接信息配置正確。
- Redis 服務故障:Redis 服務本身發生故障導致連接斷開,也會引起這個異常。這時可以檢查 Redis 服務是否正常,以及網絡連接是否正常。
- Redis 客戶端版本問題:Spring Data Redis 依賴于 lettuce 和 Jedis 客戶端來訪問 Redis。如果使用了不兼容的客戶端版本,就會出現這個異常。確保 Spring Data Redis、lettuce 和 Jedis 的版本匹配。
- Redis 操作出錯:使用 Redis 進行操作時,可能會遇到一些錯誤情況,如 key 不存在,操作類型不匹配等。這時檢查 Redis 的操作是否合規,可以幫助解決這個異常。
針對這些可能產生該異常的情況,可以分別進行如下處理:
- 確認 Redis 服務已啟動,并檢查網絡連接和連接信息是否正確。
- 檢查 Redis 服務是否正常,以及網絡連接是否正常。
- 選擇適合的 Spring Data Redis、lettuce 和 Jedis 版本,確保版本匹配。
- 確認 Redis 的操作是否合規,以及檢查 Redis key 是否存在。
解決問題
最終發現,根本無法下手,因為我本地是好的,java 版本都沒問題。
最終嘗試使用 lettuce。
問題解決,正常使用。
如果有各位大佬知道具體原因,歡迎交流!不勝感激!
拓展
lettuce 和 jedis比較
spring boot 本身默認使用的就是 lettuce,所以個人覺得直接使用 lettuce 會更好一些。
lettuce 是 jedis 的后起之秀,相較于 jedis 來說,lettuce 有如下優點
- 線程安全(jedis 線程非安全)
- 基于 Netty 框架的事件驅動通信,可以異步(jedis 為同步阻塞 IO,不支持異步)
- 基于異步+線程安全,所以更適合分布式緩存。
- ps:我的應用就是分布式的,考慮到有可能是線程安全導致的問題,所以嘗試使用 lettuce,結果就解決問題了。(只是猜測可能是線程安全問題導致,并沒有實際驗證,雖然解決,可能只是誤打誤撞)
相較于 jedis,lettuce 的缺點也有
- jedis 提供了更為全面的 reidis 操作特性的 api。
- jedis 的 api 基本和 redis 的指令一一對應,使用簡單,更容易理解。而 lettuce 的api 更抽象一些,學習成本會更高一些。
lettuce 使用
和 jedis 相比,lettuce 使用更簡單,因為 lettuce 并不需要配置連接池,因為 lettuce 單鏈接的性能就很好,線程池數量太低,會導致性能降低,太高和單鏈接性能差異不大,但是資源消耗更多。
所以簡單配置即可。
spring boot 3之后,也移除了直接配置連接池的入口。只需要簡單配置 redis 的鏈接方式即可。
@Bean("lettuceConnectionFactory")public RedisConnectionFactory lettuceConnectionFactory(CacheConfiguration configuration) {logger.info("開始構建 redis factory...{}", JedisConnectionFactory.class.getName());
// GenericObjectPoolConfig<RedisProperties.Lettuce> poolConfig = getLettucePoolConfig();
//
// LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder()
// .build();RedisStandaloneConfiguration redisConfig = getRedisConfiguration(configuration);
// LettuceConnectionFactory factory = new LettuceConnectionFactory(redisConfig, clientConfig);LettuceConnectionFactory factory = new LettuceConnectionFactory(redisConfig);factory.afterPropertiesSet();return factory;}
注意:lettuce 的 factory 創建完成之后,一定要調用afterPropertiesSet
方法,否則在實際使用的時候就會報錯。