Redis或es连接超时问题:DataAccessResource和远程主机IOException

·SpringBoot ·Redis ·ElasticSearch

我在以下环境编写我的应用程序:

  • 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  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);
                }
            }
        };
    }

这确实可以避免直接抛出异常,因为异常被捕获了,并且重新执行execute将请求再次重连,能够返回正确的结果。但是时间要等待很久,我的是大约20s,这相当于没有解决问题。苦苦思索之余,我觉得这跟前面的keep-alive一样,StackOverflow有一位大佬也提出可以在系统层面修改keep-alive。因为redis是在自身配置文件中修改的,而es默认取得是系统的设置。我在ubuntu下获取tcp=keepalive得到7200的输出,那就是默认2h。修改成60s,如下:
在/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


发表您的评论





公元2024年甲辰龍年,平安健康、龍行天下!