背景
在上一次优化K8s watch长连接中Okhttp的坑过后,几个月没出现啥问题了
可是后来又开始出现这个异常情况,表现仍然是watch不更新数据了,也不重连了
现象
几次Watch出问题的时候对应master节点指标均有异常,具体表现在io很高、并且伴随有内存分配失败
因此准备在测试环境模拟产生这个主机异常
通过一番查阅后,决定同时将宿主机的io和内存打满
内存打满可以通过stress来模拟
stress --vm 20 --vm-bytes 500M --vm-keep
io打满可以通过dd命令来模拟,可以参考文档
经过多次试验,上述方式组合起来使用的时候有一定概率会出现watch不再更新的现象,具体表现是watch连接会一直处于FIN_WAIT2
状态
tcp6 0 0 10.1.1.2:40890 10.1.1.1:6443 FIN_WAIT2 76179/java off (0.00/0/0)
tcp6 0 0 10.1.1.2:40874 10.1.1.1:6443 FIN_WAIT2 76179/java off (0.00/0/0)
这个状态会一直保持,直到重启进程才会重连
解决
上述链接可以看出是没有开启Tcp-Keepalive的,因此我们尝试给okhttp客户端开启Tcp-Keepalive
SocketFactory factory1 = new SocketFactory() {
@Override
public Socket createSocket() throws SocketException {
Socket socket = new Socket();
socket.setKeepAlive(true);
return socket;
}
@Override
public Socket createSocket(String s, int i) throws IOException, UnknownHostException {
Socket socket = new Socket(s, i);
socket.setKeepAlive(true);
return socket;
}
@Override
public Socket createSocket(String s, int i, InetAddress inetAddress, int i1) throws IOException, UnknownHostException {
Socket socket = new Socket(s, i, inetAddress, i1);
socket.setKeepAlive(true);
return socket;
}
@Override
public Socket createSocket(InetAddress inetAddress, int i) throws IOException {
Socket socket = new Socket(inetAddress, i);
socket.setKeepAlive(true);
return socket;
}
@Override
public Socket createSocket(InetAddress inetAddress, int i, InetAddress inetAddress1, int i1) throws IOException {
Socket socket = new Socket(inetAddress, i, inetAddress1, i1);
socket.setKeepAlive(true);
return socket;
}
};
OkHttpClient client = apiClient.getHttpClient().newBuilder().socketFactory(factory1).build();
apiClient.setHttpClient(client);
对官方的http客户端自定义一个SocketFactory ;重写所有方法,让他创建的Socket都设置上Tcp-Keepalive
经过这样设置后,多次实验,问题可以得到解决
原因分析
- 之前升级到H2之后已经有了ping线程了,为什么还是没法从异常状态中恢复过来呢?
dump出来后可以看到ping线程的和watch线程一样,也卡死了,处理不了这种情形。
- reflect线程为什么不更新了呢?
这里可以看到它一直再等待<0x00000005c51b7480> 这个锁被释放,但是这个锁被watch线程一直持有,不释放,怀疑是产生了死锁