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遇到的那些坑

        共 4104字,需瀏覽 9分鐘

         ·

        2021-02-06 18:37

        前言

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

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

        由于業(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ù)城市碼查詢(xún)城市信息
        ????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ù)城市碼查詢(xún)城市信息,這個(gè)方法有如下三步:

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

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

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

        這一看不當(dāng)緊,更疑惑了:Redis 的 key/value 前面有類(lèi)似\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)了。

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

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

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


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

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

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

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

        ????//?查詢(xún)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)度類(lèi)型的參數(shù),如果把整個(gè) cityList(java.util.Set 類(lèi)型)作為一個(gè)參數(shù)傳給可變長(zhǎng)度類(lèi)型的參數(shù)會(huì)怎么樣呢?

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

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


        可以看到這里的 Set 類(lèi)型,也就是傳入的 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ù)盤(pán)了 Redis 使用過(guò)程中遇到的兩個(gè)問(wèn)題:

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

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

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

        手機(jī)掃一掃分享

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

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        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>
            神尾舞BEST神尾舞 | 国产成人一区二区啪在线观看 | 总裁受高h奶水双性吃奶男男 | 做爰短篇高潮乱小说 | 天天干人人摸 | 国产丝袜一区二区三区免费视频 | 天天黄色片 | 91精品日产乱码一二三区别 | 东京热一区二区三区精品无码 | 败火老妇露脸视频 |