首頁>技術>

前言

Redis 作為當前最流行的 NoSQL 之一,想必很多人都用過。

Redis 有五種常見的資料型別:string、list、hash、set、zset。講真,我以前只用過 Redis 的 string 型別。

由於業務需求,用到了 Redis 的集合 set。這不,一上來就踩到坑了。

前幾天有個需求提測,測試小哥提了個 bug,並給了我一個日誌截圖:

問題排查

從堆疊資訊定位到了專案的程式碼,大致如下:

public class CityService  private 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 {  @Bean  public 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) {    // ...    // 查詢MySQL    List<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 的引數細節,不能想當然。

漫漫踩坑路,且踩且珍惜。大家一起踩。

作者|WriteOnRead|掘金

8
最新評論
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • Java虛擬機器記憶體區域的劃分以及作用詳解