深入ThreadLocal之二
在使用HttpClient调用后台resetful服务时,“Connection reset”是一个比较常见的问题,有同学跟我私信说被这个问题困扰很久了,今天就来分析下,希望能帮到大家。例如我们线上的网关日志就会抛该错误:

从日志中可以看到是Socket套接字在read数据时抛出了该错误。
导致“Connection reset”的原因是服务器端因为某种原因关闭了Connection,而客户端依然在读写数据,此时服务器会返回复位标志“RST”,然后此时客户端就会提示“java.net.SocketException: Connection reset”。
可能有同学对复位标志“RST”还不太了解,这里简单解释一下:
TCP建立连接时需要三次握手,在释放连接需要四次挥手;例如三次握手的过程如下:
第一次握手:客户端发送syn包(syn=j)到服务器,并进入SYN_SENT状态,等待服务器确认;
第二次握手:服务器收到syn包,并会确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。
可以看到握手时会在客户端和服务器之间传递一些TCP头信息,比如ACK标志、SYN标志以及挥手时的FIN标志等。
除了以上这些常见的标志头信息,还有另外一些标志头信息,比如推标志PSH、复位标志RST等。其中复位标志RST的作用就是“复位相应的TCP连接”。
TCP连接和释放时还有许多细节,比如半连接状态、半关闭状态等。详情请参考这方面的巨著《TCP/IP详解》和《UNIX网络编程》。
前面说到出现“Connection reset”的原因是服务器关闭了Connection[调用了Socket.close()方法]。大家可能有疑问了:服务器关闭了Connection为什么会返回“RST”而不是返回“FIN”标志。原因在于Socket.close()方法的语义和TCP的“FIN”标志语义不一样:发送TCP的“FIN”标志表示我不再发送数据了,而Socket.close()表示我不在发送也不接受数据了。问题就出在“我不接受数据” 上,如果此时客户端还往服务器发送数据,服务器内核接收到数据,但是发现此时Socket已经close了,则会返回“RST”标志给客户端。当然,此时客户端就会提示:“Connection reset”。详细说明可以参考oracle的有关文档:http://docs.oracle.com/javase/1.5.0/docs/guide/net/articles/connection_release.html。
另一个可能导致的“Connection reset”的原因是服务器设置了Socket.setLinger (true, 0)。但我检查过线上的tomcat配置,是没有使用该设置的,而且线上的服务器都使用了nginx进行反向代理,所以并不是该原因导致的。关于该原因上面的oracle文档也谈到了并给出了解释。
此外啰嗦一下,另外还有一种比较常见的错误“Connection reset by peer”,该错误和“Connection reset”是有区别的:
服务器返回了“RST”时,如果此时客户端正在从Socket套接字的输出流中读数据则会提示Connection reset”;
服务器返回了“RST”时,如果此时客户端正在往Socket套接字的输入流中写数据则会提示“Connection reset by peer”。
“Connection reset by peer”如下图所示:

前面谈到了导致“Connection reset”的原因,而具体的解决方案有如下几种:
出错了重试;
客户端和服务器统一使用TCP长连接;
客户端和服务器统一使用TCP短连接。
首先是出错了重试:这种方案可以简单防止“Connection reset”错误,然后如果服务不是“幂等”的则不能使用该方法;比如提交订单操作就不是幂等的,如果使用重试则可能造成重复提单。
然后是客户端和服务器统一使用TCP长连接:客户端使用TCP长连接很容易配置(直接设置HttpClient就好),而服务器配置长连接就比较麻烦了,就拿tomcat来说,需要设置tomcat的maxKeepAliveRequests、connectionTimeout等参数。另外如果使用了nginx进行反向代理或负载均衡,此时也需要配置nginx以支持长连接(nginx默认是对客户端使用长连接,对服务器使用短连接)。
使用长连接可以避免每次建立TCP连接的三次握手而节约一定的时间,但是我这边由于是内网,客户端和服务器的3次握手很快,大约只需1ms。ping一下大约0.93ms(一次往返);三次握手也是一次往返(第三次握手不用返回)。根据80/20原理,1ms可以忽略不计;又考虑到长连接的扩展性不如短连接好、修改nginx和tomcat的配置代价很大(所有后台服务都需要修改);所以这里并没有使用长连接。ping服务器的时间如下图:

最后的解决方案是客户端和服务器统一使用TCP短连接:我这边正是这么干的,而使用短连接既不用改nginx配置,也不用改tomcat配置,只需在使用HttpClient时使用http1.0协议并增加http请求的header信息(Connection: Close),源码如下:
httpGet.setProtocolVersion(HttpVersion.HTTP_1_0);
httpGet.addHeader(HTTP.CONN_DIRECTIVE, HTTP.CONN_CLOSE);
最后再补充几句,虽然对于每次请求TCP长连接只能节约大约1ms的时间,但是具体是使用长连接还是短连接还是要衡量下,比如你的服务每天的pv是1亿,那么使用长连接节约的总时间为:
1亿*1ms=10^8*1ms=10^5*1s=10^5*1h/3600≈27.78h
神奇的是,亿万级pv的服务使用长连接一天内节约的总时间为27.78小时(竟然大于一天)。
所以使用长连接还是短连接大家需要根据自己的服务访问量、扩展性等因素衡量下。但是一定要注意:服务器和客户端的连接一定要保持一致,要么都是长连接,要么都是短连接。
深入ThreadLocal之二的更多相关文章
- 理解ThreadLocal(之二)
想必很多朋友对ThreadLocal并不陌生,今天我们就来一起探讨下ThreadLocal的使用方法和实现原理.首先,本文先谈一下对ThreadLocal的理解,然后根据ThreadLocal类的源码 ...
- 深入理解ThreadLocal(二)
3 InheritableThreadLocal的使用 通过上面的分析知道通过ThreadLocal保存的值是线程隔离的.其实在Thread对象中,还有一个ThreadLocal.ThreadLoca ...
- ThreadLocal (二):什么时候使用 InheritableThreadLocal
一.ThreadLocal 在父子线程传递的问题 public class InheritableThreadLocalDemo { // 全局变量 // static ThreadLocal< ...
- 切换数据库+ThreadLocal+AbstractRoutingDataSource 一
最近项目用的数据库要整合成一个,所以把多源数据库切换的写法要清除掉.所以以下记载了多远数据库切换的用法及个人对源码的理解. 框架:Spring+mybatis+vertx,(多源数据库切换的用法不涉及 ...
- 五、线程本地ThreadLocal
一.线程私有 在多线程情况下,对于一个共享的数据可能会产生线程安全问题.最简单的解决办法就是堆访问共享数据的时候加锁,但我们知道加锁是很影响效率的,尤其是像数据库连接这样耗费资源较多的情况下,加锁就意 ...
- ThreadLocal (三):为何TransmittableThreadLocal
一.示例 线程池内的线程并没有父子关系,所以不适合InheritableThreadLocal的使用场景 public class ThreadPoolInheritableThreadLocalDe ...
- 从零开发分布式数据库中间件 二、构建MyBatis的读写分离数据库中间件
在上一节 从零开发分布式数据库中间件 一.读写分离的数据库中间件 中,我们讲了如何通过ThreadLocal来指定每次访问的数据源,并通过jdbc的连接方式来切换数据源,那么这一节我们使用我们常用的数 ...
- ThreadLocal的使用场景分析
目录 一.ThreadLocal介绍 二.使用场景1——数据库事务问题 2.1 问题背景 2.2 方案1-修改接口传参 2.3 方案2-使用ThreadLocal 三.使用场景2——日志追踪问题 四. ...
- 转载 三、并行编程 - Task同步机制。TreadLocal类、Lock、Interlocked、Synchronization、ConcurrentQueue以及Barrier等
随笔 - 353, 文章 - 1, 评论 - 5, 引用 - 0 三.并行编程 - Task同步机制.TreadLocal类.Lock.Interlocked.Synchronization.Conc ...
随机推荐
- .bss 段 block started symbol
一个英文缩写 cvs = concurrent version system cli = common language infrastructure
- Java theory and practice
This content is part of the series: Java theory and practice A brief history of garbage collection A ...
- 免费web api接口大全
下面的接口来自互联网,部分功能需要付费 查询手机 http://www.yodao.com/smartresult-xml/search.s?type=mobile&q=手机号码 查询IP h ...
- Unity3D研究院之Inspector面板枚举的别名与排序
虽然mono是支持unicode的.可以在枚举里写中文,但是我还是觉得写英文好一些.可是在编辑器上策划是希望看到的是中文的,还有就是枚举的展示排序功能,策划在编辑的时候为了方便希望把常用的枚举排上前面 ...
- 怎么保护PDF文档和扫描文件里的机密信息
从事商务工作的人,必然要处理带有机密信息的文档,需要分享这些文档的时候,如何谨慎小心地对待那些机密信息,说到底还是取决于自己.分享文档的目的不同,对文档的保护类型和级别也不一样.例如,只有授权的读者才 ...
- C#分部方法
C#分部方法必须是私有的,不能返回值.分部方法主要用内部信息处理中. 下面的例子,有一个分部类People,其中一个定义一个分部方法SetDefaultValue,另外一个类中实现了其中的逻辑处理. ...
- SqlParameter设定的value值为0时、调用的存储过程获取到的值却为null解决方法
原C#代码如下: if (query != null) { switch (query.MethodFlag) { //进出口退补税额统计表 case (int)EnumClassifyCorrect ...
- python compile
compile(source, filename, mode[, flags[, dont_inherit]]) 参数source:字符串或者AST(Abstract Syntax Trees)对象. ...
- Intent的详细解析以及用法
Intent的详细解析以及用法 Android的四大组件分别为Activity .Service.BroadcastReceiver(广播接收器).ContentProvider(内容提供者 ...
- 解压jar
unzip EtnetChinaApplication.jar -d app进入临时目录app,编辑文件