Redis或es连接超时问题:DataAccessResource和远程主机IOException
我在以下环境编写我的应用程序:
- SpringBoot:v2.6.12
- spring-data-elasticsearch:v4.3.7 (rest-high-level-elasticsearch)
- spring-data-redis:v2.6.7
- redis:v5.0.7
- elasticsearch:v7.15.2
当我启动springboot application时,一切正常。接口访问时,也ok。但是有时候再次调用接口时,会等待很久导致超时抛出类似“java.io.IOException: 远程主机强迫关闭了一个现有的连接”、“DataAccessResourceFailureException”。
首先,这些异常全是在启动后,每空闲一段时间的第一次访问就会产生,之后的访问就一切正常。估计是超过了系统设定的时间,都没有任何连接唤醒,服务器释放了连接池,再次连接就超时报错了。首先,这个IOException看报错信息是来自redis的。如果这个问题是在你第一次访问就产生,你应该注意redis连接配置是否正确,比如密码、远程访问开启。我的是空闲一段时间抛出,因此不可能是连接配置有问题。当我尝试yaml文件中配置shutdown-timeout也是无效的:spring: redis: host: password: client-type: lettuce lettuce: # 以秒为单位, 默认为0.1秒 设置大于redis的keep-alive shutdown-timeout: 100最终解决办法是:在redis配置文件redis.conf中修改keep-alive。这个选项默认是找不到的,需要主动添加进去,用vim在末尾添加:tcp-keepalive 60保存并重启redis就可以了,默认它的值是300的好像。springboot配置文件中的shutdown-timeout要设置大于tcp-keepalive,具体为什么,可以自行搜索一下。 上述操作完成后,空闲再请求连接就再也不会抛出异常了。但是紧接着发生了“DataAccessResourceFailureException”,查看信息,来自于es。我苦思冥想,修改了yaml配置文件,比如timeout之类的,如:
@NotNull @Bean @Override public RestHighLevelClient elasticsearchClient() { final ClientConfiguration clientConfiguration = ClientConfiguration.builder() .connectedTo(elasticSearchConfigProperties.getHostAndPort()) .withBasicAuth(elasticSearchConfigProperties.getUsername(), elasticSearchConfigProperties.getPassword()) // 接下来的这些with代码都是无用的,解决办法可以继续往下看 .withConnectTimeout(elasticSearchConfigProperties.getConnectTimeout()) .withSocketTimeout(elasticSearchConfigProperties.getSocketTimeout()) .withClientConfigurer( // 配置KeepAlive,避免空闲后的第一次连接出现DataAccessResourceFailureException异常 RestClients.RestClientConfigurationCallback.from( httpAsyncClientBuilder -> httpAsyncClientBuilder.setKeepAliveStrategy( (response, context)-> Duration.ofMinutes(1).toMillis() ) ) ) .build(); return RestClients.create(clientConfiguration).rest(); }搞了半天也无济于事!!我baidu and google了很久,在StackOverflow上找到了elastic search评论的github issue,教你如何在抛出此异常时用回调重新尝试连接:
public ElasticsearchRestTemplate getElasticsearchRestTemplate(){ return new ElasticsearchRestTemplate(getElasticsearchRestHighClient()) { @NotNull @Override public这确实可以避免直接抛出异常,因为异常被捕获了,并且重新执行execute将请求再次重连,能够返回正确的结果。但是时间要等待很久,我的是大约20s,这相当于没有解决问题。苦苦思索之余,我觉得这跟前面的keep-alive一样,StackOverflow有一位大佬也提出可以在系统层面修改keep-alive。因为redis是在自身配置文件中修改的,而es默认取得是系统的设置。我在ubuntu下获取tcp=keepalive得到7200的输出,那就是默认2h。修改成60s,如下:T execute(@NotNull ClientCallback callback) { try { return super.execute(callback); } catch (DataAccessResourceFailureException e) { // 重写execute,空闲时间连接出现长时间连接异常时,重新尝试回调结果返回,避免抛出异常 System.out.println("DataAccessResourceFailureException in ElasticsearchRestTemplate retry"); return super.execute(callback); } } }; }
在/etc/sysctl.conf中设置 net.ipv4.tcp_keepalive_time=60最终重启linux,启动application,现在是无论空闲多久,再次访问都一切正常。 其实这个问题也应该从客户端程序入手:导致这个问题的原因是空闲久了没有任何一个线程连接,长时间无数据交互,这些连接长时间会造成系统资源的消耗。我们可以在springboot中写一个定时连接策略,时不时地让客户端和服务器之间tcp保活,这种问题也能迎刃而解,并且不需要修改服务端的系统配置。
来自:Java
更新于2023-03-09 21:36:56 发表于2023-03-09 20:46:09
更新于2023-03-09 21:36:56 发表于2023-03-09 20:46:09
发表您的评论