1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        Redis遇到的那些坑

        共 6033字,需瀏覽 13分鐘

         ·

        2021-04-25 09:35

        前言


        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ù)城市碼查詢城市信息,這個方法有如下三步:

        1. 從本地緩存查詢,若存在則直接返回;否則進行第二步。

        2. 從 Redis 查詢,若存在,存入本地緩存并返回;否則進行第三步。

        3. 從 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 的字符串(可能略有不同),而且還有亂碼。如圖:

        亂碼問題處理

        網(wǎng)上查了一番,原來是 spring-data-redis 的 RedisTemplate 序列化的問題。
        RedisTemplate 的默認(rèn)配置如下:

        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 時默認(rèn)使用 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 替換默認(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)換問題

        繼續(xù)跟進前面的類型轉(zhuǎn)換問題。
        通過客戶端查看 Redis 的值,如下:

        這是什么鬼?明顯不對勁兒??!
        我們想存儲的是 set 類型,正常應(yīng)該是三條數(shù)據(jù),這里怎么只有一條?
        想了想應(yīng)該是向 Redis 存儲值的時候有什么問題,于是翻到代碼看了看怎么存的:

        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;
          }
        }

        RedisService#add2Set 方法:

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

        乍一看好像沒什么問題。
        但是再一看,RedisService#add2Set 方法中,values 是可變長度類型的參數(shù),如果把整個 cityList(java.util.Set 類型)作為一個參數(shù)傳給可變長度類型的參數(shù)會怎么樣呢?
        PS: 可變長度類型參數(shù)是 Java 中的一種語法糖,其實它本質(zhì)上是一個數(shù)組。
        打個斷點看下:

        可以看到這里的 Set 類型,也就是傳入的 cityList 被當(dāng)成了數(shù)組中的一個元素,怪不得會報錯。
        那這種情況該怎么處理呢?
        其實也很簡單,把 cityList 轉(zhuǎn)成數(shù)組就可以了:

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

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

        這樣入?yún)⒕桶凑障胍姆绞絹砹耍?/span>


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


        到這里,問題算是搞定了。
         
        結(jié)語
        本文主要復(fù)盤了 Redis 使用過程中遇到的兩個問題:
        1. Redis key/value 亂碼問題。原因是 RedisTemplate 的序列化問題,注意配置。
        2. HashSet 和 String 類型轉(zhuǎn)換問題。主要是在操作 Redis 的 set 時(其他類型亦然),注意 API 的參數(shù)細(xì)節(jié),不能想當(dāng)然。
        漫漫踩坑路,且踩且珍惜。大家一起踩。

        有道無術(shù),術(shù)可成;有術(shù)無道,止于術(shù)

        歡迎大家關(guān)注Java之道公眾號


        好文章,我在看??

        瀏覽 21
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            免费在线观看成人网站 | 日逼逼网| 欧美日韩电影一区二区三区 | 中国乱论片 | 午夜性爱福利视频 | 高清无码免费在线观看 | 瘦精品无码一区二区三区四区五区六区七区八区 | 北条麻妃的69XX在线 | 大香蕉色网 | 粉嫩99精品99久久久久久特污兔 |