一、问题现象
随着ES的密集使用,线上环境,不同应用最近几天陆续有报java.io.IOException: Connection reset by peer异常,感觉不太正常。直接影响就是用户查询或者变更ES数据失败。
java.io.IOException: Connection reset by peer
at org.elasticsearch.client.RestClient.extractAndWrapCause(RestClient.java:828)
at org.elasticsearch.client.RestClient.performRequest(RestClient.java:248)
at org.elasticsearch.client.RestClient.performRequest(RestClient.java:251)
at org.elasticsearch.client.RestClient.performRequest(RestClient.java:235)
at org.elasticsearch.client.RestHighLevelClient.internalPerformRequest(RestHighLevelClient.java:1514)
at org.elasticsearch.client.RestHighLevelClient.performRequest(RestHighLevelClient.java:1484)
at org.elasticsearch.client.RestHighLevelClient.performRequestAndParseEntity(RestHighLevelClient.java:1454)
at org.elasticsearch.client.RestHighLevelClient.bulk(RestHighLevelClient.java:497)
二、问题分析
1、客户端的KeepAlive
首先网上查了一番并结合源码分析,与Es的RestHighLevelClient的KeepAlive(最小空闲时间)有关,KeepAlive默认值是-1,长连接,表示连接永不过期,可循环重复使用。下图就是设置KeepAlive的时候获取的默认时间策略,不设置默认-1表示持续连接
2、服务端的KeepAlive
虽然客户端保持了长链接,然而Linux服务器TCP的Keepalive却有着自己的超时时间,可通过命令查看,如下图
可以看到这台服务器被设置的是600秒,也就是10分钟。若超过这个时间,且中间客户端没有操作,也即没有与服务端发生一个TCP数据交换,服务器就发送一个心跳包,探测下当前链接是否有效,正常情况下会收到对方的包,表示这个连接可用。不正常情况下,收不到客户端相应,服务端会多次尝试后发送,之后依然收不到客户端响应(因为网络抖动等原因),就会断开并清除TCP连接。而此时客户端还依然认为自己持有的连接是有效的,如果此时正好有涉及ES操作的请求来到,带着自认为有效但实际已经失效的连接的去请求服务端的时候就会报抛出此异常。
基于以上分析,我遇到的错误有了眉目,之前那么长时间没有收到这个错误,只有最近几天才有,自然想到这几天公司安排的机房断网演练,网路波动一阵子,导致客户端多次收不到服务器的心跳,可能与此有关
因此一种解决方案就是设置KeepAlive-最小空闲时间,这个时间要小于服务器的Keepalive时间,超过这个最小时间客户端主动便释放掉这个连接,下次新请求来到从连接池中重新获取,而不是让服务端主动断开连接。
三、解决方案
方案一
在客户端连接中构造中设置setKeepAliveStrategy((response, context) -> 180 * 1000),如设置最小空闲时间180秒,超过这个时间,客户端主动释放掉连接,新请求来到重新获取。
完整代码如下:
final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(username, password));
RestClientBuilder builder = RestClient.builder(new HttpHost(host, 9200, "http"))
.setHttpClientConfigCallback(new RestClientBuilder.HttpClientConfigCallback() {
@Override
public HttpAsyncClientBuilder customizeHttpClient(HttpAsyncClientBuilder httpAsyncClientBuilder) {
return httpAsyncClientBuilder.setDefaultCredentialsProvider(credentialsProvider).setKeepAliveStrategy((response, context) -> 180 * 1000);
}
});
RestHighLevelClient restHighLevelClient = new RestHighLevelClient(builder);
方案二
因为这个异常不是那么频繁,因此也可以在代码中获取客户端的时候try catch IOException后,就重新获取客户端连接1-3次左右,超过设定次数就失败,这个也是比较保险的
RestHighLevelClient client = null;
try {
client = esConf.getClient();
} catch (IOException e) {
log.error("IOException", e);
client = esConf.getClient();
}
最后,我按照方法一改了一个应用,上线观察一周,没有再发现此错误。然而,客户端没有改动倜然保持长链接的应用,也没有再报这个异常。推测很大概率是网络波动导致的此问题
参考:
ES两个小时没连接竟然会出现bug,为此老板给我夹了个鸡腿。。。-六虎
ElasticSearch的网络错误与Linux防火墙白名单设置 - 诺尘の笔记