之前同事反馈说线上遇到Redis反序列化异常问题,异常如下:

XxxClass1 cannot be cast to XxxClass2

已知信息如下:

  • 该异常不是必现的,偶尔才会出现;
  • 出现该异常后重启应用或者过一会就好了;
  • 序列化协议使用了hessian。

因为偶尔出现,首先看了报异常那块业务逻辑是不是有问题,看了一遍也发现什么问题。看了下对应日志,发现是在Redis读超时之后才出现的该异常,因此怀疑redis client操作逻辑那块导致的(公司架构组对redis做了一层封装),发现获取/释放redis连接如下代码:

 try {
jedis = jedisPool.getResource();
// jedis业务读写操作
} catch (Exception e) {
// 异常处理
} finally {
if (jedis != null) {
// 归还给连接池
jedisPool.returnResourceObject(jedis);
}
}

初步认定原因为:发生了读写超时的连接,直接归还给连接池,下次使用该连接时读取到了上一次Redis返回的数据。因此本地验证下,示例代码如下:

 @Data
@NoArgsConstructor
@AllArgsConstructor
static class Person implements Serializable {
private String name;
private int age;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
static class Dog implements Serializable {
private String name;
} public static void main(String[] args) throws Exception {
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(1);
JedisPool jedisPool = new JedisPool(config, "192.168.193.133", 6379, 2000, "123456"); Jedis jedis = jedisPool.getResource();
jedis.set("key1".getBytes(), serialize(new Person("luoxn28", 26)));
jedis.set("key2".getBytes(), serialize(new Dog("tom")));
jedisPool.returnResourceObject(jedis); try {
jedis = jedisPool.getResource();
Person person = deserialize(jedis.get("key1".getBytes()), Person.class);
System.out.println(person);
} catch (Exception e) {
// 发生了异常之后,未对该连接做任何处理
System.out.println(e.getMessage());
} finally {
if (jedis != null) {
jedisPool.returnResourceObject(jedis);
}
} try {
jedis = jedisPool.getResource();
Dog dog = deserialize(jedis.get("key2".getBytes()), Dog.class);
System.out.println(dog);
} catch (Exception e) {
System.out.println(e.getMessage());
} finally {
if (jedis != null) {
jedisPool.returnResourceObject(jedis);
}
}
}

连接超时时间设置2000ms,为了方便测试,可以在redis服务器上使用gdb命令断住redis进程(如果redis部署在Linux系统上的话,还可以使用iptable命令在防火墙禁止某个回包),比如在执行 jedis.get("key1".getBytes() 代码前,对redis进程使用gdb命令断住,那么就会导致读取超时,然后就会触发如下异常:

Person cannot be cast to Dog

既然已经知道了该问题原因并且本地复现了该问题,对应解决方案是,在发生异常时归还给连接池时关闭该连接即可(jedis.close内部已经做了判断),代码如下:

 try {
jedis = jedisPool.getResource();
// jedis业务读写操作
} catch (Exception e) {
// 异常处理
} finally {
if (jedis != null) {
// 归还给连接池
jedis.close();
}
}

至此,该问题解决。注意,因为使用了hessian序列化(其包含了类型信息,类似的有Java本身序列化机制),所有会报类转换异常;如果使用了json序列化(其只包含对象属性信息),反序列化时不会报异常,只不过因为不同类的属性不同,会导致反序列化后的对象属性为空或者属性值混乱,使用时会导致问题,并且这种问题因为没有报异常所以更不容易发现。

既然说到了Redis的连接,要知道的是,Redis基于RESP(Redis Serialization Protocol)协议来通信,并且通信方式是停等方式,也就说一次通信独占一个连接直到client读取到返回结果之后才能释放该连接让其他线程使用。小伙伴们可以思考一下,Redis通信能否像dubbo那样使用单连接+序列号(标识单次通信)通信方式呢?理论上是可以的,不过由于RESP协议中并没有一个"序列号"的字段,所以直接靠原生的通信方法来实现是不现实的。不过我们可以通过echo命令传递并返回"序列号"+正常的读写方式来实现,这里要保证二者执行的原子性,可以通过lua脚本或者事务来实现,事务方式如下:

MULTI
ECHO "唯一序列号"
GET key1
EXEC

然后客户端收到的结果是一个 [ "唯一序列号", "value1" ]的列表,你可以根据前一项识别出这是你发送的哪个请求。

为什么Redis通信方式并没有采用类似于dubbo这种通信方式呢,个人认为有以下几点:

  • 使用停等这种通信方式实现简单,并且协议字段尽可能紧凑;
  • Redis都是内存操作,处理性能较强,停等协议不会造成客户端等待时间较长;
  • 目前来看,通信方式这块不是Redis使用上的性能瓶颈,这一点很重要。

推荐阅读:

欢迎小伙伴扫描以下二维码阅读更多精彩好文。

 

一次线上Redis类转换异常排查引发的思考的更多相关文章

  1. 线上Redis偶发性链接失败排查记

    问题过程 输入法业务于12月12日上线了词库接受业务,对部分用户根据用户uuid判断进行回传,在12月17日早上8点多开始出现大量的php报错(Redis went away),报错导致了大量的链接积 ...

  2. 线上redis服务内存异常分析。

    项目中,新增了一个统计功能,用来统计不同手机型号的每天访问pv,看了下redis2.6有个setbit的功能,于是打算尝尝鲜把 redis从2.4更新到了2.6 因为是租了vps.服务器的内存只有4g ...

  3. 线上Kafka突发rebalance异常,如何快速解决?

    文章首发于[陈树义的博客],点击跳转到原文<线上Kafka突发rebalance异常,如何快速解决?> Kafka 是我们最常用的消息队列,它那几万.甚至几十万的处理速度让我们为之欣喜若狂 ...

  4. Linux(2)---记录一次线上服务 CPU 100%的排查过程

    Linux(2)---记录一次线上服务 CPU 100%的排查过程 当时产生CPU飙升接近100%的原因是因为项目中的websocket时时断开又重连导致CPU飙升接近100% .如何排查的呢 是通过 ...

  5. 一次线上CPU高的问题排查实践

    一次线上CPU高的问题排查实践 前言 近期某一天上班一开电脑,就收到了运维警报,有两台服务CPU负载很高,同时收到一线同事反馈 系统访问速度非常慢,几乎无响应. 一个美好的早晨,最怕什么就来什么.只好 ...

  6. 线上CPU飙升100%问题排查

    本文转载自线上CPU飙升100%问题排查 引子 对于互联网公司,线上CPU飙升的问题很常见(例如某个活动开始,流量突然飙升时),按照本文的步骤排查,基本1分钟即可搞定!特此整理排查方法一篇,供大家参考 ...

  7. 线上redis问题修复:JedisConnectionException: Unexpected end of stream.

    经过: 项目上线后经常报 Unexpected end of stream.; nested exception is redis.clients.jedis.exceptions.JedisConn ...

  8. 线上Redis高并发性能调优实践

    项目背景 最近,做一个按优先级和时间先后排队的需求.用 Redis 的 sorted set 做排队队列. 主要使用的 Redis 命令有, zadd, zcount, zscore, zrange ...

  9. 一次性搞清楚线上CPU100%,频繁FullGC排查套路

    “ 处理过线上问题的同学基本上都会遇到系统突然运行缓慢,CPU 100%,以及 Full GC 次数过多的问题. 当然,这些问题最终导致的直观现象就是系统运行缓慢,并且有大量的报警. 本文主要针对系统 ...

随机推荐

  1. 你必须知道的EF知识和经验(转)

    注意:以下内容如果没有特别申明,默认使用的EF6.0版本,code first模式. 推荐MiniProfiler插件 工欲善其事,必先利其器. 我们使用EF和在很大程度提高了开发速度,不过随之带来的 ...

  2. logical函数

    logical函数(逻辑函数) logical(x):x ~=0时,logical(x)=1:x = 0时,logical(x)=0

  3. 在IIS配置时没有启用目录浏览功能 :HTTP 错误 403.14

    在IIS配置时没有启用目录浏览功能,浏览网站时,会出现“HTTP 错误 403.14–Forbidden,Web服务器被配置为不列出此目录内容”的提示,怎么解决这个问题呢? 01 02 03 04 0 ...

  4. 如何查询正在运行的SQL Server agent job

    运行"msdb"系统数据库下的存储过程"dbo.sp_help_job",可以得知现在SQL Server中有多少个正在运行的agent job: USE [m ...

  5. Eureka服务下线源码解析

    我们知道,在Eureka中,可以使用如下方法使Eureka主动下线,那么本篇文章就来分析一下子这个下线的流程 public synchronized void shutdown() { if (isS ...

  6. Qt压缩和解压 zip

    zlib编译详见 https://blog.csdn.net/zhangxuechao_/article/details/85049711 下载quazip https://github.com/st ...

  7. element实现vue级联多选

    已经有大神完成element的改造github:https://github.com/webCoderJ/ele-multi-cascader#Attributes 已实践可用

  8. Redis 使用过程中遇到的具体问题

    1.缓存雪崩和缓存穿透问题 1.1缓存雪崩 简介:缓存同一时间大面积的失效,所以,后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉. 解决办法:  事前:尽量保证整个 redis 集 ...

  9. Oracle 11g R2 Sample Schemas 安装

    最近准备对之前学习SQL*Loader的笔记进行整理,希望通过官方文档中的示例学习(Case Studies)来进行,但是官方文档中示例学习相关的脚本文件在数据库软件安装完成之后默认并没有提供,而是整 ...

  10. linux date 设置系统时间

    设置 系统时间 注意时间格式 date  -s "date" [root@localhost c]# date -s "2019-05-29 10:58:00" ...