诡异的Connection reset

场景:

开发反馈测试环境A应用通过httpclient连接B应用,偶发java.net.socketException: connection reset报错。

理解 Connection reset:

在我看来 Connection reset分为2种情况:

第一种Connection reset by peer:

      服务器返回了RST时,如果此时客户端正在往Socket套接字的输入流中写数据则会提示"Connection reset by peer"。

第二种java.net.socketException: connection reset:

     服务器返回了RST时,如果此时客户端正在从Socket套接字的输出流中读数据则会提示Connection reset";

那么不管那种情况 其实都是服务器返回了RST标志位,但是客户端还在进行输入输出数据,

通过以上描述,猜测Connection reset的原因是客户端连接保活机制超时回收连接,但是服务端还在使用该连接池导致报错。

排查过程:

首先肯定是自己去模拟错误,但是这个模拟错误代价有点大,毕竟是偶发现象。连续访问几十次都没模拟出问题。之后我只能放弃挣扎去看两个应用的相关日志 看能不能找到一点猫腻,比如在这里我会根据traceid跟踪整条链路,但是几乎没什么收获。之后配合开发将连接地址改成直连,这里说一下我这环境是k8环境。 然后直连模拟了大概一天。并没有出现connection reset。

那么问题肯定是出在连接svc地址的时候出现了connection reset问题。

目前该环境用的模式是ipvs,那么众所周知的是ipvs是有超时时间的,如果超时的话,连接将被回收。那么我们可以看下这个环境的配置

所以说,7200是两个小时,明显大于15分钟。本来预期的2小时的tcp链接,15分钟就被kill掉了。

为了验证这个问题,多次等待15分钟访问该接口,终于功夫不负有心人,15分钟必现,那么就不存在偶然的说法了。

之后围绕这个问题开始了一系列的操作。

为了达到 ipvs timeout > docker 中 net.ipv4.tcp_keep_alive

解决这个问题:

1.增大 ipvs timeout

ipvsadm --set 7500 120 300

2.缩小net.ipv4.tcp_keep_alive = 600

在主机上修改/etc/sysctl.conf 将 tcp_keep_alive 改为600 并且同时需要修改k8s pod的tcp_keep_alive 并且kubelet需要增加--allowed-unsafe-sysctls=net.*
securityContext:
sysctls:
- name: net.ipv4.tcp_keepalive_probes
  value: "10"
- name: net.ipv4.tcp_keepalive_intvl
  value: "30"
- name: net.ipv4.tcp_keepalive_time
  value: "600"

这边解释一下原理,k8s同一个集群的两个pod直接通过svc cluster ip 访问,通过cluster ip 访问的微服务,流量会经过kube-proxy,kube-proxy底层走的是ipvs,ipvs timeout 默认超时时间15分钟。过了15分钟,ipvs就会销毁tcp链接。

在容器A中,通过netstat -altpn --timers 命令检查tcp链接,发现之前的tcp通道没有关闭,10.215.10.117 是podip, 10.222.222.187:80 是服务B的service_ip:port

在容器A中,用ss -nt 命令和 ipvsadm命令查看到,在请求结束后,服务A容器内该链接未断开,而主机上ipvs通道已经超时销毁。如下图 当保活时间到0的时候宿主机的连接就会消失。

很奇怪的是以上配置结束之后 并没有解决问题。后续通过linux的 watch机制 反复查看宿主机已经客户端的连接池的状态,发现当客户端建立连接的时候 连接池的状态一直处于off状态(没有时间计时)初步怀疑是客户端建立连接的时候没有打开保活机制导致。

如果开启保活连接的话 会处于keepalive状态如图:

 当为 keepalive 时,表明 TCP 协议栈开启了 keep alive timers,后面括号中的值分别是 timer 所剩时长/重传次数/keepalive probe 次数。当为 off 时,表明没有开启 TCP keepalive timer。当然,正如前面所说,这个 timer 是否开启,和 HTTP keep alive 没有必然联系。

宿主机:

httpClient pod:

所以按照httpclient 返回的timer status状态(off)来看并不是keepalived导致的,那么设置keepalived 的系统参数是没有用的。

所以最终解决方案就是:

第一种(4层tcp keepalived): 连接方式增加保活机制 必须搭配net.ipv4.tcp_keepalive_time net.ipv4.tcp_keepalive_probes net.ipv4.tcp_keepalive_intvl 几个配置

SocketConfig socketConfig = SocketConfig.custom().setSoKeepAlive(true).setSoTimeout(3600000).build(); //We need to set socket keep alive
        RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(3600000).build();
        CloseableHttpClient httpClient = HttpClientBuilder.create().setDefaultRequestConfig(requestConfig).
                                           setDefaultSocketConfig(socketConfig).build();
HttpPost post = new HttpPost(url.toString());
HttpResponse response = httpClient.execute(post);

 第二种(7层keepalived,目前我使用的方法):修改客户端的keepalive的策略keepAliveStrategy,当连接超时之后不再复用老连接。

源代码:

修改后的连接方式:

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值