记录一次http网络超时的排查过程
微信公众号:内核小王子 觉得可以的话欢迎关注
场景:公司对外网关对很多外部商户开放,运行多年一直正常,昨天某一个客户调用我们接口的时候频繁报connectiontimeout,异常如下:
该异常来自于httpclient,原因是创建连接超时,也就是tcp进行三次握手的时候失败,或者握手报文没有到达服务端。分析可能有如下原因:
- 1.报文发送太频繁,而客户防火墙性能太差,将报文丢弃
- 2.我们服务端性能过载,accept()方法执行太慢,syn队列或者accept队列满了,导致握手报文被丢弃 ,关于tcp三次握手和发送流程刻参考这篇文章
经过排查后,都不是上面两个原因,目前现象ping包是正常的,执行以下nc命令 ,偶尔会失败,大部分时候成功
while true; do nc -w 2 -zv open.xxx.com 443 ;sleep 1;done
为了排查上面问题,我们先回顾一下这些概念,因为我发现很多人并不清楚短连接加上keepalive和长连接的区别。
长连接: tcp三次握手后,操作系统会通过发送tcp心跳包来保证连接不会因为空闲时间限制被回收,基于长连接客户端可以先发送一个请求,不用等服务端返回立即在发送一个请求
短连接: tcp三次握手后,请求一次等到收到回复后就会进行四次挥手断开连接。
http : 目前http都是短连接,并且在1.1版本中采用了keep-alive机制进行重用。短连接加上keep-alive可以让连接保持活性,但是她和长连接的区别是长连接可以不用等第一个请求的回复收到就可以发送第二个请求,因为tcp是一个流,而短连接虽然通过keep-alive保持连接的活性,但他必须等到第一个请求的回复收到后,才能发送第二个请求,一般会用一个连接池进行复用。浏览器请求一个网页,最开始是通过一个连接进行请求,在收到报文回复后解析发现要加载更多的资源,例如图片和js等资源,会在开多个连接,并发的请求服务端,而这些连接会通过keep-alive保持活性放到一个连接池重用,keep-alive机制是在服务端实现的,例如在tomcat的server.xml里面配置,默认一般为1分钟.
<Connector port="8080" maxThread="50" minSpareThreads="25" maxSpareThread="75" enableLookups="false" redirectPort="8443" acceptCount="100" debug="0" connectionTimeout="20000" disableUploadTimeout="true" maxKeepAliveRequest=100 keepAliveTimeout=60000/>
keepalive : 刚刚说到http的keepalive是在服务端实现的,并且是针对短连接的,有了keepalive的短连接我们一般称为持久连接,而tcp的长连接也需要keepalive机制,客户端和服务端会周期的发送探活报文,这个我们可以通过wireshake进行抓包获取到,有了长连接我们就可以针对这个连接发送请求,并且可以不用等服务端返回后在发送后续请求,为什么http没有用长连接,一般请求一个网页后用户会花时间进行浏览,没有必要用长连接长时间占有资源,但是加载网页的时候为了加快速度,浏览器进行了并发,会同时创建多个连接进行请求,为了防止创建多个连接导致服务器压力太大,所以浏览器限制了同一个域名同时请求的连接数,所以服务端想要加快客户端访问速度可以将资源放到不同的域名来规避浏览器的限制,http没有采用长连接的另一个原因是在http1.1以及之前的版本,一个请求报文和回复报文并不能匹配,并没有定义一个序列号让response和request对应,这样当同时发送多个request之后,客户端在收到response之后不知道和那个request对应,而在http2.0中有定义,所以目前的浏览器都是创建多个连接进行并发,但是单个连接内部必须等上一个请求回复后才能发送下一个请求。
而我们经常会在应用层也会实现一层keepalive探活,例如netty里面IdleSateHandler,很多rpc框架例如dubbo也会单独在应用层实现一层探活机制,有两个原因,一个是应用层可以基于此做一些高可用相关的检测,另外由操作系统发出的tcp探活包仅仅针对网络连接,过于底层,不够灵活,并且即使应用层没有资源处理网络请求底层仍然会对探活包进行响应,而基于应用层就更加灵活,例如服务端可以主动管理连接,客户端也可以主动检测服务端是否可用。
好了会到正题
最终我们发现商户是三台服务器一起请求的,而三台服务器应该是经过nat后是同一个ip,那么很可能是触发了tcp中的一个时间戳的限制,也就是如果同一个ip的请求会记录其时间戳并进行比较,下次发送握手报文的时候,如果时间戳比上一次请求时间小,那么会将该握手报文丢弃,如果同一个ip是同一个机器一般不会有问题,然而三台机器相同ip但是时间戳可能不相同,如果在大批量发送请求的时候很可能会触发该规则。我们询问了下运维的千夜,他确实是在两会期间排查网络问题的时候加了如下参数,开启了回收机制:
net.ipv4.tcp_tw_recycle = 1 net.ipv4.tcp_tw_reuse = 1
而系统默认
net.ipv4.tcp_timestamps = 1 表示开启时间戳校验
千夜将配置还原后,执行sysctl -p 后没有在报超时异常。
历史文章:

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
为什么强烈禁止开发人员使用isSuccess作为变量名
在日常开发中,我们会经常要在类中定义布尔类型的变量,比如在给外部系统提供一个RPC接口的时候,我们一般会定义一个字段表示本次请求是否成功的。 关于这个"本次请求是否成功"的字段的定义,其实是有很多种讲究和坑的,稍有不慎就会掉入坑里,作者在很久之前就遇到过类似的问题,本文就来围绕这个简单分析一下。到底该如何定一个布尔类型的成员变量。 一般情况下,我们可以有以下四种方式来定义一个布尔类型的成员变量: boolean success boolean isSuccess Boolean success Boolean isSuccess 以上四种定义形式,你日常开发中最常用的是哪种呢?到底哪一种才是正确的使用姿势呢? 通过观察我们可以发现,前两种和后两种的主要区别是变量的类型不同,前者使用的是boolean,后者使用的是Boolean。 另外,第一种和第三种在定义变量的时候,变量命名是success,而另外两种使用isSuccess来命名的。 首先,我们来分析一下,到底应该是用success来命名,还是使用isSuccess更好一点。 success 还是 isSuccess 到底...
- 下一篇
Discord 公司如何使用 Cassandra 存储上亿条线上数据
Discord 是一款国外的类似 YY 的语音聊天软件。Discord 语音聊天软件及我们的 UGC 内容的增长速度比想象中要快得多。随着越来越多用户的加入,带来了更多聊天消息。2016 年 7 月,每天大约有 4 千万条消息;2016 年 12 月,每天超过亿条。当写这篇文章时(2017 年 1 月),每天已经超过 1.2 亿条了。 我们早期决定永久保存所有用户的聊天历史记录,这样用户可以随时在任何设备查找他们的数据。这是一个持续增长的高并发访问的海量数据,而且需要保持高可用。如何才能搞定这一切?我们的经验是选择 Cassandra 作为数据库! 我们在做什么 Discord 语音聊天软件的最初版本在 2015 年只用了两个月就开发出来。在那个阶段,MongoDB 是支持快速迭代最好的数据库之一。所有 Discord 数据都保存在同一个 MongoDB 集群中,但在设计上我们也支持将所有数据很容易地迁移到一种新的数据库(我们不打算使用 MongoDB 数据库的分片,因为它使用起来复杂以及稳定性不好)。 实际上这是我们企业文化的一部分:快速搭建来验证产品的特性,但也预留方法来支持将它升...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS8安装Docker,最新的服务器搭配容器使用
- Linux系统CentOS6、CentOS7手动修改IP地址
- 设置Eclipse缩进为4个空格,增强代码规范
- CentOS7,CentOS8安装Elasticsearch6.8.6
- CentOS8安装MyCat,轻松搞定数据库的读写分离、垂直分库、水平分库
- Hadoop3单机部署,实现最简伪集群
- CentOS8编译安装MySQL8.0.19
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- MySQL8.0.19开启GTID主从同步CentOS8
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池