如果写Redis序列化与读Redis序列化不一致,你觉得会发生什么

开心一刻

昨天在我大哥家玩,嫂子在给小侄女泡奶粉

小侄女走到大嫂面前,大声的说了一句:妈妈,我爱你

大哥很期待的看着小侄女,然而小侄女只是一味的跟大嫂互动

大哥从沙发上起身,不开心的走进了卧室

大嫂看出大哥吃醋了,跟小侄女说到:你去把刚才给妈妈说的那句话也给爸爸说一遍

小侄女小跑进了卧室,趴着大哥的腿上说到:爸爸,我爱妈妈

杀人还要诛心

Redis 写读

Spring Boot 接入 Redis 非常简单,2 步搞定

  1. POM 添加依赖

    常用的方式有两种

    • 添加 spring-boot-starter-data-redis 依赖
    • 添加 redisson-spring-boot-starter 依赖

    如果用到分布式锁的话,推荐用 redisson-spring-boot-starter

    Redisson 分布式锁实现之源码篇 → 为什么推荐用 Redisson 客户端

  2. 配置连接

    在配置文件(一般是 application.yml 或 application.properties)中配置 Redis 的连接信息

    spring:
      redis:
        database: 0
        host: 192.168.1.225
        password: redis@qsl
        timeout: 1000
    

接入就算完成了;我们写读 Redis 试试

假设我们有 class

@Data
public class CallbackStatus implements Serializable {

    private static final long serialVersionUID = -1L;

    private String taskId;
    private String bizLogId;
    private String status;

    public CallbackStatus() {
    }

    public CallbackStatus(String taskId, String bizLogId, String status) {
        this.taskId = taskId;
        this.bizLogId = bizLogId;
        this.status = status;
    }
    
    @Override
    public String toString() {
        return "CallbackStatus{taskId='" + taskId + "', bizLogId='" + bizLogId + "', status='" + status + "'}";
    }
}

将该类对象写入 Redis

@Test
public void testSet() {
    CallbackStatus callbackStatus = new CallbackStatus(
        "1",
        "10000",
        "SUCCESS"
    );
    redisTemplate.opsForValue().set("qsl:callback-demo", callbackStatus);
}

写入成功后,Redis 中数据如下

写入Redis_demo

怎么是一串乱码?这是因为我们未配置数据序列化,RedisTemplate 默认用的 JdkSerializationRedisSerializer,所以可读性差

然后我们从 Redis 中读取该对象

@Test
public void testGet() {
    CallbackStatus callbackStatus = (CallbackStatus) redisTemplate.opsForValue().get("qsl:callback-demo");
    System.out.println(callbackStatus);
}

输出结果:CallbackStatus{taskId='1', bizLogId='10000', status='SUCCESS'}

是不是没问题?

写读序列化不一致

如果通过 Redis 实现服务间通信,上游服务将数据写入 Redis 时用的时默认序列化 JdkSerializationRedisSerializer

@Bean("jdkObjRedisTemplate")
public RedisTemplate<String, Object> jdkObjRedisTemplate(RedisConnectionFactory factory) {
    RedisTemplate<String, Object> template = new RedisTemplate<>();
    template.setConnectionFactory(factory);
    template.setKeySerializer(new StringRedisSerializer());
    template.setValueSerializer(new JdkSerializationRedisSerializer());
    template.setHashKeySerializer(new StringRedisSerializer());
    template.setHashValueSerializer(new JdkSerializationRedisSerializer());
    return template;
}

而下游服务读取的时候用的序列化是 FastJsonRedisSerializer

@Bean("fastJsonRedisTemplate")
public RedisTemplate<String, Object> fastJsonRedisTemplate(RedisConnectionFactory factory) {
    RedisTemplate<String, Object> template = new RedisTemplate<>();
    template.setConnectionFactory(factory);

    // 添加连接验证
    template.setEnableTransactionSupport(false);

    StringRedisSerializer stringRedisSerializer = new
        StringRedisSerializer();

    // 配置 FastJson 序列化器
    FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class);

    // 设置 FastJson 的配置(如果有需要)
    ParserConfig.getGlobalInstance().setAutoTypeSupport(true);

    // key采用String的序列化方式
    template.setKeySerializer(stringRedisSerializer);
    // hash的key也采用String的序列化方式
    template.setHashKeySerializer(stringRedisSerializer);
    // value序列化方式采用jackson
    template.setValueSerializer(fastJsonRedisSerializer);
    // hash的value序列化方式采用jackson
    template.setHashValueSerializer(fastJsonRedisSerializer);
    template.afterPropertiesSet();
    return template;
}

毋庸置疑,通信会失败,但具体失败形式是什么?读取失败并抛出异常,还是其他形式?

我们试下就知道了,直接用 fastJsonRedisTemplate 去读 qsl:callback-demo

@Test
public void testFastGet() {
    CallbackStatus callbackStatus = (CallbackStatus) fastJsonRedisTemplate.opsForValue().get("qsl:callback-demo");
    System.out.println(callbackStatus);
}

执行正常,并未抛出异常,只是读取的结果是 null

jdk写fast读

问题不就来了?

明明 Redis 中 qsl:callback-demo 有值,为什么读取的是 null

我再往真实业务上靠一靠,用 Redis 实现消息队列,简化版是不是直接用 Redis 的 List 来实现?

上游服务从 List 右边写入数据

/**
 * 从 list 右边写入数据
 */
@Test
public void testWriteList() {
    CallbackStatus data = new CallbackStatus(
            "2",
            "20000",
            "failed"
    );
    jdkObjRedisTemplate.opsForList().rightPush("qsl:status:callback", data);
}

数据写入成功后,Redis 中数据如下

写入Redis_List数据

下游服务从 List 左边读取数据

/**
 * 从 list 的左边读取数据
 */
@Test
public void testReadList() {
    Object o = fastJsonRedisTemplate.opsForList().leftPop("qsl:status:callback");
    CallbackStatus callbackStatus = convertToCallbackStatus(o);
    
    // 实际项目代码中没有 System 这一行
    System.out.println(callbackStatus);
    
    if (callbackStatus != null) {
        // TODO 业务处理,并打印相关日志
    }
}

private CallbackStatus convertToCallbackStatus(Object obj) {
    if (obj == null) {
        return null;
    }

    try {
        if (obj instanceof CallbackStatus) {
            return (CallbackStatus) obj;
        }

        if (obj instanceof JSONObject) {
            return ((JSONObject) obj).toJavaObject(CallbackStatus.class);
        }

        if (obj instanceof Map) {
            String jsonStr = JSON.toJSONString(obj);
            return JSON.parseObject(jsonStr, CallbackStatus.class);
        }

        String jsonStr = JSON.toJSONString(obj);
        return JSON.parseObject(jsonStr, CallbackStatus.class);

    } catch (Exception e) {
        LOGGER.error("【工作流任务回调】===》对象转换失败: class={}", obj.getClass().getName(), e);
        return null;
    }
}

读取并未异常,但结果却是 null

读取Redis_List数据

同时,Redis 中 qsl:status:callback 已经没了

Redi中List数据已经没了

然后上游开发与下游开发就开启了 battle

下游开发:你没往 Redis 中写入状态数据吗

上游开发:你把你的服务停下,我跑下试试,看 Redis 中有没有状态数据

几分钟后,上游开发给下游开发截了个 Redis 数据图,并气冲冲的跑到他面前,说到:Redis 里面不是有数据吗

下游开发启动服务,并看了看代码,说到:该打的日志都打了,现在一行日志都没有输出,就是从 Redis 没读到数据嘛

上游开发:那就不知道了,反正我是往 Redis 中成功写入数据了

然后下游开发陷入了无尽的猜想中

有别的节点消费了?开发环境的节点我已经停了,应该只有我本地节点了

Redis List 中数据已经没了,说明已经被消费了,本地没有业务日志,也没有异常日志,到底谁消费了?

......

摸不着头脑

从源码中找根因

从 Redis List leftPop 数据,到底是没查到数据,还是查到了反序列化成 null 了,跟下源码就知道了

先执行 testWriteList() 往 List 中插入一条数据

然后打好断点

打断点

以 debug 方式启动 testReadList

具体细节就不给你们展示了,直接给你们看答案

源码寻找答案

堆栈信息从下往上看,从 Redis leftPop 到的数据是 byte[131],说明什么?说明查到数据了!!!

只是 FastJsonRedisSerializer 将其反序列化异常了

反序列化异常

可恶的是

fastjson 把异常吃了,没把异常往外抛,直接 return null

你怎么敢的呀

根因是不是就找到了?

通过 JdkSerializationRedisSerializer 把数据写入 Redis,读取的时候,FastJsonRedisSerializer 反序列化异常,但 fastjson 捕获了异常后直接返回 null

这个 null 并非指从 Redis 未查询到数据,而是读取到数据后反序列化异常,fastjson 返回的 null

不同的 Serializer 反序列化异常时所做的处理肯定是不一样的,FastJsonRedisSerializer 是捕获异常后什么也不做,只是返回 null,那 JdkSerializationRedisSerializer、Jackson2JsonRedisSerializer 等 Serializer 反序列化异常时,是不是也如 FastJsonRedisSerializer 一样呢,你们可以去试试

总结

  1. Redis 的写读序列化要保持一致,推荐用 json 序列化,可读性高

    json序列化包括 FastJsonRedisSerializer、Jackson2JsonRedisSerializer 等

  2. 捋清楚数据从 Redis 到程序,经历了哪些环节,逐个环节分析,而不是武断的省略中间环节

    Redis -> Redis 驱动 -> 反序列化器 -> 程序

  3. 从源码中找原因,有时候不仅治本,还很高效,不要畏惧它,结合 Debug 方式看源码,没那么难

posted @ 2026-07-24 09:56  青石路  阅读(85)  评论(1)    收藏  举报