<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Redis遇到的那些坑

          共 4103字,需瀏覽 9分鐘

           ·

          2021-02-13 17:17

          前言

          Redis 作為當(dāng)前最流行的 NoSQL 之一,想必很多人都用過(guò)。

          Redis 有五種常見的數(shù)據(jù)類型:string、list、hash、set、zset。講真,我以前只用過(guò) Redis 的 string 類型。

          由于業(yè)務(wù)需求,用到了 Redis 的集合 set。這不,一上來(lái)就踩到坑了。

          前幾天有個(gè)需求提測(cè),測(cè)試小哥提了個(gè) bug,并給了我一個(gè)日志截圖:

          問(wèn)題排查

          從堆棧信息定位到了項(xiàng)目的代碼,大致如下:

          public?class?CityService
          ??private?void?setStatus(CityRequest?request)?
          {
          ????//?根據(jù)城市碼查詢城市信息
          ????Set?cityList?=?cityService.findByCityCode(request.getCityCode());
          ????if?(CollectionUtils.isEmpty(cityList))?{
          ??????return;
          ????}

          ????//?遍歷,做一些操作(報(bào)錯(cuò)就在這這一行)
          ????for?(String?city?:?cityList)?{
          ??????//?...
          ????}
          ??}

          ??//?一些無(wú)關(guān)的代碼...
          }

          報(bào)錯(cuò)的代碼就在 for 循環(huán)那一行。

          這一行看起來(lái)似乎沒(méi)什么錯(cuò)誤,跟 HashSet 和 String 轉(zhuǎn)換有什么關(guān)系呢?往前翻一翻 cityList 是怎么來(lái)的。

          cityList 會(huì)根據(jù)城市碼查詢城市信息,這個(gè)方法有如下三步:

          1. 從本地緩存查詢,若存在則直接返回;否則進(jìn)行第二步。
          2. 從 Redis 查詢,若存在,存入本地緩存并返回;否則進(jìn)行第三步。
          3. 從 MySQL 查詢,若存在,存入本地緩存和 Redis(set 類型)并返回;若不存在返回空。

          聯(lián)系報(bào)錯(cuò)信息,再看這幾步的代碼,1、3 可能性較小;第二步因?yàn)橹皼](méi)有直接用過(guò) set 這種數(shù)據(jù)結(jié)構(gòu),嫌疑較大。

          于是想先通過(guò) Redis 客戶端看下緩存信息。

          這一看不當(dāng)緊,更疑惑了:Redis 的 key/value 前面有類似\xAC\xED\x00\x05t\x00\x1B 的字符串(可能略有不同),而且還有亂碼。如圖:


          亂碼問(wèn)題處理

          網(wǎng)上查了一番,原來(lái)是 spring-data-redis 的 RedisTemplate 序列化的問(wèn)題。

          RedisTemplate 的默認(rèn)配置如下:

          public?class?RedisAutoConfiguration?{

          ?@Bean
          ?@ConditionalOnMissingBean(name?=?"redisTemplate")
          ?public?RedisTemplate?redisTemplate(RedisConnectionFactory?redisConnectionFactory)
          ???throws?UnknownHostException?
          {
          ??RedisTemplate?template?=?new?RedisTemplate<>();
          ??template.setConnectionFactory(redisConnectionFactory);
          ??return?template;
          ?}
          }

          RedisTemplate 在操作 Redis 時(shí)默認(rèn)使用 JdkSerializationRedisSerializer 來(lái)進(jìn)行序列化的。

          對(duì)于這個(gè)問(wèn)題,修改下配置就可以了,示例代碼如下:

          @Configuration
          @AutoConfigureAfter(RedisAutoConfiguration.class)
          public?class?RedisConfig?
          {
          ??@Bean
          ??public?RedisTemplate?redisTemplate(RedisConnectionFactory?redisConnectionFactory)?{
          ????RedisTemplate?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;
          ??}
          }

          這個(gè)配置改過(guò)之后,亂碼的情況就沒(méi)了。

          類型轉(zhuǎn)換問(wèn)題

          繼續(xù)跟進(jìn)前面的類型轉(zhuǎn)換問(wèn)題。

          通過(guò)客戶端查看 Redis 的值,如下:


          這是什么鬼?明顯不對(duì)勁兒啊!

          我們想存儲(chǔ)的是 set 類型,正常應(yīng)該是三條數(shù)據(jù),這里怎么只有一條?

          想了想應(yīng)該是向 Redis 存儲(chǔ)值的時(shí)候有什么問(wèn)題,于是翻到代碼看了看怎么存的:

          public?class?CityService?{
          ??public?Set?findCityByCode(String?cityCode)?{
          ????//?...

          ????//?查詢MySQL
          ????List?cityDoList?=?cityRepository.findByCityCode(cityCode);

          ????//?封裝數(shù)據(jù)
          ????Set?cityList?=?new?HashSet<>();
          ????cityDoList.forEach(record?->?{
          ??????String?city?=?String.format("%s-%s",?record.getType(),?record.getCity());
          ??????cityList.add(city);
          ????});

          ????//?【問(wèn)題出在這里】
          ????redisService.add2Set(cacheKey,?cityList);
          ????return?cityList;
          ??}
          }

          RedisService#add2Set 方法:

          public?class?RedisService?{
          ??//?...
          ??public??void?add2Set(String?key,?T...?values)?{
          ????redisTemplate.opsForSet().add(key,?values);
          ??}
          }

          乍一看好像沒(méi)什么問(wèn)題。

          但是再一看,RedisService#add2Set 方法中,values 是可變長(zhǎng)度類型的參數(shù),如果把整個(gè) cityList(java.util.Set 類型)作為一個(gè)參數(shù)傳給可變長(zhǎng)度類型的參數(shù)會(huì)怎么樣呢?

          PS: 可變長(zhǎng)度類型參數(shù)是 Java 中的一種語(yǔ)法糖,其實(shí)它本質(zhì)上是一個(gè)數(shù)組。

          打個(gè)斷點(diǎn)看下:


          可以看到這里的 Set 類型,也就是傳入的 cityList 被當(dāng)成了數(shù)組中的一個(gè)元素,怪不得會(huì)報(bào)錯(cuò)。

          那這種情況該怎么處理呢?

          其實(shí)也很簡(jiǎn)單,把 cityList 轉(zhuǎn)成數(shù)組就可以了:

          public?class?CityService?{
          ??public?Set?findCityByCode(String?cityCode)?{
          ????//?...

          ????//?【問(wèn)題出在這里】轉(zhuǎn)成數(shù)組,即?toArray?方法
          ????redisService.add2Set(cacheKey,?cityList.toArray());
          ????return?cityList;
          ??}
          }

          這樣入?yún)⒕桶凑障胍姆绞絹?lái)了:


          再觀察 Redis 的緩存值,可以看到也是想要的結(jié)果:


          到這里,問(wèn)題算是搞定了。

          結(jié)語(yǔ)

          本文主要復(fù)盤了 Redis 使用過(guò)程中遇到的兩個(gè)問(wèn)題:

          1. Redis key/value 亂碼問(wèn)題。原因是 RedisTemplate 的序列化問(wèn)題,注意配置。
          2. HashSet 和 String 類型轉(zhuǎn)換問(wèn)題。主要是在操作 Redis 的 set 時(shí)(其他類型亦然),注意 API 的參數(shù)細(xì)節(jié),不能想當(dāng)然。

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

          瀏覽 39
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  三级网站在线观看免费 | 国产一区二区激情小说片 | 男女激情综合福利网站在线观看 | 免费看黄色的网站 | 操碰在线视频 |