前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Redis遇到的那些坑

Redis遇到的那些坑

作者头像
WriteOnRead
发布2021-02-22 11:26:26
4130
发布2021-02-22 11:26:26
举报
文章被收录于专栏:WriteOnReadWriteOnRead

前言

Redis 作为当前最流行的 NoSQL 之一,想必很多人都用过。

Redis 有五种常见的数据类型:string、list、hash、set、zset。讲真,我以前只用过 Redis 的 string 类型。

由于业务需求,用到了 Redis 的集合 set。这不,一上来就踩到坑了。

前几天有个需求提测,测试小哥提了个 bug,并给了我一个日志截图:

问题排查

从堆栈信息定位到了项目的代码,大致如下:

代码语言:javascript
复制
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 会根据城市码查询城市信息,这个方法有如下三步:

  1. 从本地缓存查询,若存在则直接返回;否则进行第二步。
  2. 从 Redis 查询,若存在,存入本地缓存并返回;否则进行第三步。
  3. 从 MySQL 查询,若存在,存入本地缓存和 Redis(set 类型)并返回;若不存在返回空。

联系报错信息,再看这几步的代码,1、3 可能性较小;第二步因为之前没有直接用过 set 这种数据结构,嫌疑较大。

于是想先通过 Redis 客户端看下缓存信息。

这一看不当紧,更疑惑了:Redis 的 key/value 前面有类似\xAC\xED\x00\x05t\x00\x1B 的字符串(可能略有不同),而且还有乱码。如图:

乱码问题处理

网上查了一番,原来是 spring-data-redis 的 RedisTemplate 序列化的问题。

RedisTemplate 的默认配置如下:

代码语言:javascript
复制
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 来进行序列化的。

对于这个问题,修改下配置就可以了,示例代码如下:

代码语言:javascript
复制
@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 存储值的时候有什么问题,于是翻到代码看了看怎么存的:

代码语言:javascript
复制
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 方法:

代码语言:javascript
复制
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 转成数组就可以了:

代码语言:javascript
复制
public class CityService {
  public Set<String> findCityByCode(String cityCode) {
    // ...

    // 【问题出在这里】转成数组,即 toArray 方法
    redisService.add2Set(cacheKey, cityList.toArray());
    return cityList;
  }
}

这样入参就按照想要的方式来了:

再观察 Redis 的缓存值,可以看到也是想要的结果:

到这里,问题算是搞定了。

结语

本文主要复盘了 Redis 使用过程中遇到的两个问题:

  1. Redis key/value 乱码问题。原因是 RedisTemplate 的序列化问题,注意配置。
  2. HashSet 和 String 类型转换问题。主要是在操作 Redis 的 set 时(其他类型亦然),注意 API 的参数细节,不能想当然。

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

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-02-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 WriteOnRead 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 问题排查
    • 乱码问题处理
      • 类型转换问题
      • 结语
      相关产品与服务
      云数据库 Redis
      腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档