Redis遇到的那些坑
前言
Redis 作為當前最流行的 NoSQL 之一,想必很多人都用過。
Redis 有五種常見的數據類型:string、list、hash、set、zset。講真,我以前只用過 Redis 的 string 類型。
由于業務需求,用到了 Redis 的集合 set。這不,一上來就踩到坑了。
前幾天有個需求提測,測試小哥提了個 bug,并給了我一個日志截圖:
?
問題排查
從堆棧信息定位到了項目的代碼,大致如下:
public?class?CityServiceprivate?void?setStatus(CityRequest?request)?{//?根據城市碼查詢城市信息Set<String>?cityList?=?cityService.findByCityCode(request.getCityCode());if?(CollectionUtils.isEmpty(cityList))?{return;}//?遍歷,做一些操作(報錯就在這這一行)for?(String?city?:?cityList)?{//?...}}//?一些無關的代碼... }報錯的代碼就在 for 循環那一行。
這一行看起來似乎沒什么錯誤,跟 HashSet 和 String 轉換有什么關系呢?往前翻一翻 cityList 是怎么來的。
cityList 會根據城市碼查詢城市信息,這個方法有如下三步:
從本地緩存查詢,若存在則直接返回;否則進行第二步。
從 Redis 查詢,若存在,存入本地緩存并返回;否則進行第三步。
從 MySQL 查詢,若存在,存入本地緩存和 Redis(set 類型)并返回;若不存在返回空。
聯系報錯信息,再看這幾步的代碼,1、3 可能性較小;第二步因為之前沒有直接用過 set 這種數據結構,嫌疑較大。
于是想先通過 Redis 客戶端看下緩存信息。
這一看不當緊,更疑惑了:Redis 的 key/value 前面有類似\xAC\xED\x00\x05t\x00\x1B 的字符串(可能略有不同),而且還有亂碼。如圖:
亂碼問題處理
網上查了一番,原來是 spring-data-redis 的 RedisTemplate 序列化的問題。
RedisTemplate 的默認配置如下:
public?class?RedisAutoConfiguration?{@Bean@ConditionalOnMissingBean(name?=?"redisTemplate")public?RedisTemplate<Object,?Object>?redisTemplate(RedisConnectionFactory?redisConnectionFactory)throws?UnknownHostException?{RedisTemplate<Object,?Object>?template?=?new?RedisTemplate<>();template.setConnectionFactory(redisConnectionFactory);return?template;} }RedisTemplate 在操作 Redis 時默認使用 JdkSerializationRedisSerializer 來進行序列化的。
對于這個問題,修改下配置就可以了,示例代碼如下:
@Configuration @AutoConfigureAfter(RedisAutoConfiguration.class) public?class?RedisConfig?{@Beanpublic?RedisTemplate<String,?Object>?redisTemplate(RedisConnectionFactory?redisConnectionFactory)?{RedisTemplate<String,?Object>?redisTemplate?=?new?RedisTemplate<>();redisTemplate.setConnectionFactory(redisConnectionFactory);//?使用?Jackson2JsonRedisSerialize?替換默認序列化Jackson2JsonRedisSerializer?jackson2JsonRedisSerializer?=?new?Jackson2JsonRedisSerializer<>(Object.class);ObjectMapper?objectMapper?=?new?ObjectMapper();objectMapper.setVisibility(PropertyAccessor.ALL,?JsonAutoDetect.Visibility.ANY);objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,?false);jackson2JsonRedisSerializer.setObjectMapper(objectMapper);//?設置?key/value?的序列化規則redisTemplate.setKeySerializer(new?StringRedisSerializer());redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);redisTemplate.setHashKeySerializer(new?StringRedisSerializer());redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);redisTemplate.afterPropertiesSet();return?redisTemplate;} }這個配置改過之后,亂碼的情況就沒了。
類型轉換問題
繼續跟進前面的類型轉換問題。
通過客戶端查看 Redis 的值,如下:
這是什么鬼?明顯不對勁兒啊!
我們想存儲的是 set 類型,正常應該是三條數據,這里怎么只有一條?
想了想應該是向 Redis 存儲值的時候有什么問題,于是翻到代碼看了看怎么存的:
public?class?CityService?{public?Set<String>?findCityByCode(String?cityCode)?{//?...//?查詢MySQLList<CityDO>?cityDoList?=?cityRepository.findByCityCode(cityCode);//?封裝數據Set<String>?cityList?=?new?HashSet<>();cityDoList.forEach(record?->?{String?city?=?String.format("%s-%s",?record.getType(),?record.getCity());cityList.add(city);});//?【問題出在這里】redisService.add2Set(cacheKey,?cityList);return?cityList;} }RedisService#add2Set 方法:
public?class?RedisService?{//?...public?<T>?void?add2Set(String?key,?T...?values)?{redisTemplate.opsForSet().add(key,?values);} }乍一看好像沒什么問題。
但是再一看,RedisService#add2Set 方法中,values 是可變長度類型的參數,如果把整個 cityList(java.util.Set 類型)作為一個參數傳給可變長度類型的參數會怎么樣呢?
PS: 可變長度類型參數是 Java 中的一種語法糖,其實它本質上是一個數組。
打個斷點看下:
可以看到這里的 Set 類型,也就是傳入的 cityList 被當成了數組中的一個元素,怪不得會報錯。那這種情況該怎么處理呢?
其實也很簡單,把 cityList 轉成數組就可以了:
public?class?CityService?{public?Set<String>?findCityByCode(String?cityCode)?{//?...//?【問題出在這里】轉成數組,即?toArray?方法redisService.add2Set(cacheKey,?cityList.toArray());return?cityList;} }這樣入參就按照想要的方式來了:
再觀察 Redis 的緩存值,可以看到也是想要的結果:
到這里,問題算是搞定了。
?
結語
本文主要復盤了 Redis 使用過程中遇到的兩個問題:
Redis key/value 亂碼問題。原因是 RedisTemplate 的序列化問題,注意配置。
HashSet 和 String 類型轉換問題。主要是在操作 Redis 的 set 時(其他類型亦然),注意 API 的參數細節,不能想當然。
漫漫踩坑路,且踩且珍惜。大家一起踩。
有道無術,術可成;有術無道,止于術
歡迎大家關注Java之道公眾號
好文章,我在看??
總結
以上是生活随笔為你收集整理的Redis遇到的那些坑的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Spring事务传播机制与隔离机制
- 下一篇: python上的数据库sqlite3——