如果写Redis序列化与读Redis序列化不一致,你觉得会发生什么
开心一刻
昨天在我大哥家玩,嫂子在给小侄女泡奶粉
小侄女走到大嫂面前,大声的说了一句:妈妈,我爱你
大哥很期待的看着小侄女,然而小侄女只是一味的跟大嫂互动
大哥从沙发上起身,不开心的走进了卧室
大嫂看出大哥吃醋了,跟小侄女说到:你去把刚才给妈妈说的那句话也给爸爸说一遍
小侄女小跑进了卧室,趴着大哥的腿上说到:爸爸,我爱妈妈

Redis 写读
Spring Boot 接入 Redis 非常简单,2 步搞定
-
POM 添加依赖
常用的方式有两种
- 添加
spring-boot-starter-data-redis依赖 - 添加
redisson-spring-boot-starter依赖
如果用到分布式锁的话,推荐用 redisson-spring-boot-starter
- 添加
-
配置连接
在配置文件(一般是 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 中数据如下

怎么是一串乱码?这是因为我们未配置数据序列化,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

问题不就来了?
明明 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 中数据如下

下游服务从 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 中 qsl:status:callback 已经没了

然后上游开发与下游开发就开启了 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 一样呢,你们可以去试试
总结
-
Redis 的写读序列化要保持一致,推荐用 json 序列化,可读性高
json序列化包括 FastJsonRedisSerializer、Jackson2JsonRedisSerializer 等
-
捋清楚数据从 Redis 到程序,经历了哪些环节,逐个环节分析,而不是武断的省略中间环节
Redis -> Redis 驱动 -> 反序列化器 -> 程序
-
从源码中找原因,有时候不仅治本,还很高效,不要畏惧它,结合 Debug 方式看源码,没那么难

浙公网安备 33010602011771号