文档章节

解Bug之路-串包Bug

无毁的湖光-Al
 无毁的湖光-Al
发布于 2017/04/27 10:14
字数 1610
阅读 3489
收藏 93

解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

© 著作权归作者所有

无毁的湖光-Al

无毁的湖光-Al

粉丝 430
博文 30
码字总数 51882
作品 0
浦东
后端工程师
私信 提问
加载中

评论(32)

无毁的湖光-Al
无毁的湖光-Al 博主

引用来自“jackdonsen”的评论

好文章

@jackdonsen :)
j
jackdonsen
好文章
无毁的湖光-Al
无毁的湖光-Al 博主

引用来自“Kingviker”的评论

恩 思路不错。 受教了。

@Kingviker :)
Kingviker
Kingviker
恩 思路不错。 受教了。
无毁的湖光-Al
无毁的湖光-Al 博主

引用来自“蓝风970655147”的评论

感谢楼主分享, 这一串记录收货不少啊, hahaha

@蓝风970655147 还有不少干货 敬请期待:)
蓝风970655147
蓝风970655147
感谢楼主分享, 这一串记录收货不少啊, hahaha
无毁的湖光-Al
无毁的湖光-Al 博主

引用来自“工信布”的评论

很好的内容 学习了

@工信布 :)
金贞花
金贞花
很好的内容 学习了
无毁的湖光-Al
无毁的湖光-Al 博主

引用来自“ShawnShoper”的评论

引用来自“无毁的湖光-Al”的评论

引用来自“ShawnShoper”的评论

大哥jedis更新到多少版本了- -你还在用多老的有bug的版本

引用来自“无毁的湖光-Al”的评论

这只是提供解决问题的思路 笔者遇到串包的问题不少 不仅仅是Jedis 很多池写的不好的系统都会出这种现象,大部分都是由类似原因引起的。明白了这个bug的源头,后面的很多类似的坑就容易解决了。

引用来自“ShawnShoper”的评论

文章讲的很清晰了。不过这方面的一些改进,问题是需要找,不过最终的解决方案应该从版本更新入手而不是直接操作redis socket。如果版本实在解决不了,这样做倒也无妨。
当年出这个Bug的时候,Jedis版本尚未解决这个问题

回复@无毁的湖光-Al : 好的,我愤青了,看你是1天前发的- -以为是前几天遇到的问题。思路很清晰你
博客是把以前的经验总结分享出来,所以有的时间上有点久远,目的就是为了让大家少踩点坑。😄
S
ShawnShoper

引用来自“无毁的湖光-Al”的评论

引用来自“ShawnShoper”的评论

大哥jedis更新到多少版本了- -你还在用多老的有bug的版本

引用来自“无毁的湖光-Al”的评论

这只是提供解决问题的思路 笔者遇到串包的问题不少 不仅仅是Jedis 很多池写的不好的系统都会出这种现象,大部分都是由类似原因引起的。明白了这个bug的源头,后面的很多类似的坑就容易解决了。

引用来自“ShawnShoper”的评论

文章讲的很清晰了。不过这方面的一些改进,问题是需要找,不过最终的解决方案应该从版本更新入手而不是直接操作redis socket。如果版本实在解决不了,这样做倒也无妨。
当年出这个Bug的时候,Jedis版本尚未解决这个问题

回复@无毁的湖光-Al : 好的,我愤青了,看你是1天前发的- -以为是前几天遇到的问题。思路很清晰你
解Bug之路-TCP"粘包"Bug

解Bug之路-TCP粘包Bug 前言 关于TCP流 TCP是流的概念,解释如下 详情见笔者另一篇博客https://my.oschina.net/alchemystar/blog/833937 关于"粘包" 由于TCP流的特性,经常发生一个收到多于(...

无毁的湖光-Al
2017/04/17
4.4K
109
【掘金日报】第四期 使用Sublime?怎么能不知道这些 Sublime 插件合集!

掘金日报主打分享优质深度技术内容,技术内容分:前端、后端、Android、iOS、产品设计、工具资源和一些有趣的东西。 前端 深度剖析:如何实现一个 Virtual DOM 算法 本文会在教你怎么用 300~...

膜法小编
2017/04/28
0
0
Math.NET 3.9.0 更新,C# 数学计算包

Math.NET Numerics v3.9.0 更新: ### 3.9.0 - 2015-11-25 * Distributions: Normal.CDF avoids problematic subtraction by using Erfc instead of Erf. * Statistics: geometric and harm......

白豆腐徐长卿
2015/11/26
3.5K
0
如何从一个bug体现自己的专业度

前言 对于测试人员来说,bug的生命周期一般分为:发现bug—>提交bug—>验证bug,那在这三个阶段中如何体现测试的专业度呢? 第一阶段:发现bug 场景: "测试不就是发现bug吗,有什么技术含量...

Mr_zebra
2018/05/16
9
0
jQuery中live()的多次重复绑定

今天在实现某个按钮的'click'事件时用到了jquery 的事件委派live函数,但发现这个live会进行多次的事件绑定,比如我单击某个按钮时应该输出一次串字符,但结果是多次输出该字符串,感觉很奇怪...

Eric_HSBC
2013/09/18
1K
0

没有更多内容

加载失败,请刷新页面

加载更多

官方来源的 Duo Mobile App 解决了我的 Network Difficulties 问题

https://help.duo.com/s/article/2094?language=en_US 我利用百度搜索下载了一个 Duo Mobile App (由于 Google Play)在大陆不可用。 在扫描旧手机上的 Duo Mobile App 的二维码时, 显示出错...

圣洁之子
14分钟前
3
0
Zabbix监控Mysql容器(Docker容器)主从是否存活

1、在Zabbix Web端创建模板 2、为该模板创建监控项 3、创建触发器 4、在zabbix-agent端操作 在/etc/zabbix/zabbix_agentd.d新建customize.confw文件 内容如下 UserParameter=mysql.replicat...

abowu
16分钟前
2
0
基于 RocketMQ 的同城双活架构在美菜网的挑战与实践

本文整理自李样兵在北京站 RocketMQ meetup分享美菜网使用 RocketMQ 过程中的一些心得和经验,偏重于实践。 嘉宾李样兵,现就职于美菜网基础服务平台组,负责 MQ ,配置中心和任务调度等基础...

大涛学长
22分钟前
3
0
设计模式之:外观模式和桥接模式

作者:DevYK 链接:https://juejin.im/post/5d7e01f4f265da03b5747aac 外观模式 介绍 外观模式 (Facade) 在开发过程中的运用评率非常高,尤其是在现阶段,各种第三方 SDK “充斥” 在我们周边...

Java架构Monster
23分钟前
2
0
人证合一核验设备

人脸身份验证机,人证合一设备1:N如我们现在在车站或一些重要的场所如步行街、城中村等人流密集的场所应用的人脸识别布控系统,其特点是动态和非配合。所谓的动态也就是识别的不是照 片,不是...

非思丸智能
25分钟前
3
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部