解Bug之路-串包Bug

解Bug之路-串包Bug

笔者很热衷于解决Bug,同时比较擅长(网络/协议)部分,所以经常被唤去解决一些网络IO方面的Bug。现在就挑一个案例出来,写出分析思路,以飨读者,希望读者在以后的工作中能够少踩点坑。

串包Bug现场

前置故障Redis超时

由于某个系统大量的hget、hset操作将Redis拖垮,通过监控发现Redis的CPU和IO有大量的尖刺,CPU示意图下图所示:

CPU达到了100%,导致很多Redis请求处理不及时,其它业务系统都频繁爆出readTimeOut。此时,紧急将这个做大量hget、hset的系统kill,过了一段时间,Redis的CPU恢复平稳。

一波未平,一波又起

就在我们以为事件平息的时候,线上爆出登录后的用户名称不正确。同时错误日志里面也有大量的Redis返回不正确的报错。尤为奇葩的是,系统获取一个已经存在的key,例如get User123456Name,返回的竟然是redis的成功返回OK。示意图如下:

Jedis.sendCommand:get User123456Name
Jedis.return:OK
	or
Jedis.sendCommand:get User123456Name
Jedis.return:user789

我们发现此情况时,联系op将Redis集群的所有Key紧急delete,当时监控示意图:

当重启后,我们再去线上观察的时候,发现错误依然存在,神奇的是,这种错误发生的频率会随着时间的增加而递减。到最后刷个10分钟页面才会出现这种错,示意图如下所示:

既然如此,那只能祭出重启大法,把出错的业务系统全部重启了一遍。 重启之后,线上恢复正常,一切Okay。

Bug复盘

此次Bug是由Redis本身Server负载太高超时引起的。Bug的现象是通过Jedis去取对应的Key值,得不到预期的结果,简而言之包乱了,串包了。

缩小Bug范围

首先:Redis是全球久经考验的系统,这样的串包不应该是Redis的问题。 第二:Redis刷新了key后Bug依然存在,而业务系统重启了之后Okay。 第三:笔者在错误日志中发现一个现象,A系统只可能打印出属于A系统的json串结构(redis存的是json)。 很明显,是业务系统的问题,如果是Redis本身的问题,那么在很大概率上A系统会接收到B系统的json串结构。

业务系统问题定位

业务系统用的是Jedis,这同样也是一个久经考验的库,出现此问题的可能性不大。那么问题肯定是出在运用Jedis的姿势上。 于是笔者找到了下面一段代码:

public Object invoke(Object proxy,Method method,Object[] args) throws Throwable{
	JedisClient jedisClient = jedisPool.getResource();   
	try{
	  return method.invoke(jedisClient,args);  
	} catch(Exception e){
	  logger.error("invoke redis error",e);   
	  throw e;   
	}finally {
		if(jedisClient != null){
			// 问题处在下面这句
			jedisPool.returnResource(jedisClient);
		}
	}
}

当时我就觉得很奇怪,笔者自己写的,阅读过的连接池的代码都没有将抛异常的连接放回池里。就以Druid为例,如果是网络IO等fatal级别的异常,直接抛弃连接。这里把jedisClient连接返回去感觉就是出问题的关键。

Bug推理

笔者意识到,之所以串包可能是由于jedisClient里面可能有残余的数据,导致读取的时候读取到此数据,从而造成串包的现象。

串包原因

正常情况下的redis交互

先上Jedis源码

public String get(final String key) {
	checkIsInMulti();
	client.sendCommand(Protocol.Command.GET, key);
	return client.getBulkReply();
}

Jedis本身用的是Bio,上述源码的过程示意图如下:

出错的业务系统的redis交互

由于Redis本身在高负载状态,导致没能及时相应command请求,从而导致readTimeOut异常。

复用这个出错链接导致出错

在Redis响应了上一个command后,把数据传到了对应command的socket,进而被inputream给buffer起来。而这个command由于超时失败了。

这样,inputStream里面就有个上个命令留下来的数据。 下一次业务操作在此拿到这个连接的时候,就会出现下面的情况。

再下面的命令get user789Key会拿到get user456Key的结果,依次类推,则出现串包的现象。

串包过程图

上图中相同颜色的矩形对应的数据是一致的。但是由于Redis超时,导致数据串了。

为什么get操作返回OK

上图很明显的解释了为什么一个get操作会返回OK的现象。因为其上一个操作是set操作,它返回的OK被get操作读取到,于是就有了这种现象。

为什么会随着时间的收敛而频率降低

因为在调用Redis出错后,业务系统有一层拦截器会拦截到业务层的出错,同时给这个JedisClient的错误个数+1,当错误个数>3的时候,会将其从池中踢掉。这样这种串了的连接会越来越少,导致Bug原来越难以出现。

在每次调用之前清理下inputstream可行否

不行,因为Redis可能在你清理inputstream后,你下次读取前把数据给传回来。

怎么避免这种现象?

抛出这种IO异常的连接直接给扔掉,不要放到池子里面。

怎么从协议层面避免这种现象

对每次发送的命令都加一个随机的packetId,然后结果返回回来的时候将这个packetId带回来。在客户端每次接收到数据的时候,获取包中的packetId和之前发出的packetId相比较,如下代码所示:

if(oldPacketId != packetIdFromData){
     throw new RuntimeException("串包");
}

总结

至少在笔者遇到的场景中,出现IO异常的连接都必须被抛掉废弃,因为你永远不知道在你复用的那一刻,socket或者inputstream的buffer中到底有没有上一次命令遗留的数据。 当然如果刻意的去构造协议,能够通过packetId之类的手段把收发状态重新调整为一致也是可以的,这无疑增加了很高的复杂度。所以废弃连接重建是最简单有效的方法。

原文链接

https://my.oschina.net/alchemystar/blog/888086

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏睿哥杂货铺

Linux 性能诊断:负载评估

从load avgerage等总括性的数据着手,参考CPU使用率和I/O等待时间等具体的数字,从而自顶向下快速排查各进程状态。

28810
来自专栏惨绿少年

MongoDB 分片集群技术

---- 在了解分片集群之前,务必要先了解复制集技术! ----  1.1 MongoDB复制集简介   一组Mongodb复制集,就是一组mongod进程,...

5059
来自专栏FreeBuf

聊一聊万恶的锁首

当手持8倍镜的98K都不能在使用程序时干掉万恶的锁首时,内心是十万头羊驼奔跑的场景,那我们就来聊一聊市面上常见的锁首方式。 ① :设置OpenHomePage、...

2187
来自专栏IT技术精选文摘

解Bug之路-记一次JVM堆外内存泄露Bug的查找

1403
来自专栏CSDN技术头条

Kubernetes推1.2版本,可管理1000节点,运行3万Pods

赶在Google云端产品用户大会之前,Kubernetes也释出了1.2新版。超过680位开源开发者参与。新版最大特色是扩充能力大幅提高了400%,单个集群可管...

2068
来自专栏FreeBuf

文件监控程序V2.0改进版(网站防挂马好手)

前言 技多不压身,多掌握几种不同的编程语言,不但有助于开拓我们的视野,还可以让我们在工作时,自己写一些小程序来帮我们减少工作量。 前段时间给一个客户维护服务器,...

5745
来自专栏编程之旅

Liunx的文件权限

之前讲过为了统一开发环境生产环境以及更换开发机器的情况,我把环境统一由Vagrant部署在Linux的虚拟机中,但是由于我对Linux系统没有系统的学习过,对于...

1121
来自专栏刺客博客

Linux使用指定用户运行程序

其实关于降权运行,也就这么一句: su - user -c "xxxxxx",也没必要写一篇文章来解释,但是出于安全考虑,以及对之前出现的aria2权限过高的问...

7063
来自专栏美团技术团队

Redis高负载下的中断优化

Redis 服务端的总体请求量从年初最开始日访问量百亿次级别上涨到高峰时段的万亿次级别,给运维和架构团队都带来了极大的挑战。

1.2K11
来自专栏北京马哥教育

Linux之HA高可用集群的基础概念总结

HA(High Availability)高可用集群,其特点为根据实际需求为前端Diretor,后端RS-server,数据库服务器,共享存储等集群节点做一个...

4286

扫码关注云+社区

领取腾讯云代金券