最近线上频发 HttpServerErrorException: 500 异常,但规律性不强,通过查询服务器日志发现有类型错误:

org.apache.http.NoHttpResponseException: 21.153.143.183:8080 failed to respond

通过查询资料发现这个异常与Http Header的一个参数 Connection: Keep-Alive 有关,我们使用的是Apache的httpclient。

  • 先给出一个解决方法,很简单,在初始化httpclient时,使用如下配置,主要是NoConnectionReuseStrategy.INSTANCE 参数:
httpclient = HttpClientBuilder.create() 
.setMaxConnPerRoute(20) 
.setConnectionReuseStrategy(NoConnectionReuseStrategy.INSTANCE) //解决NoHttpResponseException问题 
.setMaxConnTotal(200) 
.build();

这样做的目的就是每次请求不再链接复用,而是创建一个新的链接。

  • 下面分析下出现这个异常的原因,以及为什么上面方法可以解决这个问题

HTTP1.1 默认请求都使用Connection: Keep-Alive 参数,表示这个链接要进行保持,服务端接收到这个请求后会保持链接,等待下次请求。而这个keep-alive是有有效期的,客户端和服务端有效时间配置方式,对系统的影响是非常大的。

服务端使用的是springboot(1.5.9)自带tomcat容器,使用默认的keepAliveTimeout,一般说法是这个默认值是20秒,走读下AbstractEndpoint的代码查看,默认使用soTimeout的值, 如下:

keepalived 网卡故障会触发vip切换么_客户端

 soTimeout默认20秒。

keepalived 网卡故障会触发vip切换么_服务端_02

 实际上并非如此,通过在启动springboot服务时debug进去,可以到达如下代码位置:

keepalived 网卡故障会触发vip切换么_服务端_03

keepalived 网卡故障会触发vip切换么_服务端_04

也就是soTimeout被改成了60秒。OK,服务端keepaliveTimeout为60秒。

客户端的默认keepaliveTimeout,通过翻代码,发现竟然有15分钟 。

客户端15分钟超时,而服务端60秒超时,差距不是一点点,通过抓包可以了解下,tcp交互的过程,服务端口8090:

tcp通过3次握手建立连接,4次握手关闭连接,可以看第三个箭头指明的位置,50657端口连接了8090端口。建立连接过程是正常的,而关闭链接时只是服务端一厢情愿的发了个Fin包,客户端没有回应Fin包(此时连接已不可用),如果httpclient使用这个不可用的连接发送请求就会反生not response异常。等过了15分钟,客户端Fin,服务端说,这个连接不存在啊,Reset吧。

keepalived 网卡故障会触发vip切换么_服务端_05

 

上面提到的解决方法,避免了重复使用过期连接,也就不会有NoHttpResponseException这个异常。

keepalived 网卡故障会触发vip切换么_服务端_06

keepalived 网卡故障会触发vip切换么_服务端_07

调用releaseConnection方法,reusable参数为false,则直接关闭链接