Redis遇到的那些坑
前言
Redis 作為當(dāng)前最流行的 NoSQL 之一,想必很多人都用過。
Redis 有五種常見的數(shù)據(jù)類型:string、list、hash、set、zset。講真,我以前只用過 Redis 的 string 類型。
由于業(yè)務(wù)需求,用到了 Redis 的集合 set。這不,一上來就踩到坑了。
前幾天有個需求提測,測試小哥提了個 bug,并給了我一個日志截圖:

問題排查
從堆棧信息定位到了項目的代碼,大致如下:
public class CityService
private void setStatus(CityRequest request) {
// 根據(jù)城市碼查詢城市信息
Set<String> cityList = cityService.findByCityCode(request.getCityCode());
if (CollectionUtils.isEmpty(cityList)) {
return;
}
// 遍歷,做一些操作(報錯就在這這一行)
for (String city : cityList) {
// ...
}
}
// 一些無關(guān)的代碼...
}
報錯的代碼就在 for 循環(huán)那一行。
這一行看起來似乎沒什么錯誤,跟 HashSet 和 String 轉(zhuǎn)換有什么關(guān)系呢?往前翻一翻 cityList 是怎么來的。
cityList 會根據(jù)城市碼查詢城市信息,這個方法有如下三步:
從本地緩存查詢,若存在則直接返回;否則進行第二步。
從 Redis 查詢,若存在,存入本地緩存并返回;否則進行第三步。
從 MySQL 查詢,若存在,存入本地緩存和 Redis(set 類型)并返回;若不存在返回空。
聯(lián)系報錯信息,再看這幾步的代碼,1、3 可能性較小;第二步因為之前沒有直接用過 set 這種數(shù)據(jù)結(jié)構(gòu),嫌疑較大。
于是想先通過 Redis 客戶端看下緩存信息。
這一看不當(dāng)緊,更疑惑了:Redis 的 key/value 前面有類似\xAC\xED\x00\x05t\x00\x1B 的字符串(可能略有不同),而且還有亂碼。如圖:

亂碼問題處理
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;
}
}
@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 替換默認(rèn)序列化
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);
// 設(shè)置 key/value 的序列化規(guī)則
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
類型轉(zhuǎn)換問題

public class CityService {
public Set<String> findCityByCode(String cityCode) {
// ...
// 查詢MySQL
List<CityDO> cityDoList = cityRepository.findByCityCode(cityCode);
// 封裝數(shù)據(jù)
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;
}
}
public class RedisService {
// ...
public <T> void add2Set(String key, T... values) {
redisTemplate.opsForSet().add(key, values);
}
}
PS: 可變長度類型參數(shù)是 Java 中的一種語法糖,其實它本質(zhì)上是一個數(shù)組。

public class CityService {
public Set<String> findCityByCode(String cityCode) {
// ...
// 【問題出在這里】轉(zhuǎn)成數(shù)組,即 toArray 方法
redisService.add2Set(cacheKey, cityList.toArray());
return cityList;
}
}


Redis key/value 亂碼問題。原因是 RedisTemplate 的序列化問題,注意配置。 HashSet 和 String 類型轉(zhuǎn)換問題。主要是在操作 Redis 的 set 時(其他類型亦然),注意 API 的參數(shù)細(xì)節(jié),不能想當(dāng)然。
有道無術(shù),術(shù)可成;有術(shù)無道,止于術(shù)
歡迎大家關(guān)注Java之道公眾號
好文章,我在看??
