【问题标题】:How to Properly Close Raw RestClient When Using Elastic Search 5.5.0 for Optimal Performance?使用 Elastic Search 5.5.0 时如何正确关闭 Raw RestClient 以获得最佳性能?
【发布时间】:2017-09-15 17:46:53
【问题描述】:

我正在使用 Spring Boot 1.5.4.RELEASE 微服务使用 ElasticSearch 提供的低级 Rest Client 连接到 ElasticSearch 5.5.0 实例。

pom.xml

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.5.4.RELEASE</version>
</parent>

<dependencies>
    <!-- Spring -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>

    <!-- Elasticsearch -->
    <dependency>
        <groupId>org.elasticsearch</groupId>
        <artifactId>elasticsearch</artifactId>
        <version>5.5.0</version>
    </dependency>

    <dependency>
        <groupId>org.elasticsearch.client</groupId>
        <artifactId>transport</artifactId>
        <version>5.5.0</version>
    </dependency>

    <!-- Apache Commons -->
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-lang3</artifactId>
        <version>3.6</version>
    </dependency>

    <!-- Jackson -->
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-core</artifactId>
        <version>2.8.9</version>
    </dependency>

    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.8.9</version>
    </dependency>

    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-annotations</artifactId>
        <version>2.8.9</version>
    </dependency>

    <!-- Log4j -->
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version>
    </dependency>

    <!-- JUnit -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.11</version>
        <scope>test</scope>
    </dependency>

    <!-- Swagger -->
    <dependency>
        <groupId>io.springfox</groupId>
        <artifactId>springfox-swagger2</artifactId>
        <version>2.6.1</version>
        <scope>compile</scope>
    </dependency>

    <dependency>
        <groupId>io.springfox</groupId>
        <artifactId>springfox-swagger-ui</artifactId>
        <version>2.6.1</version>
        <scope>compile</scope>
    </dependency>
</dependencies>

一切设置正确,但经过多次点击后,客户端应用程序报告 HTTP 500 错误,这就是日志文件中出现的内容:

java.io.IOException: Too many open files
        at sun.nio.ch.IOUtil.makePipe(Native Method) ~[na:1.8.0_141]
        at sun.nio.ch.EPollSelectorImpl.<init>(EPollSelectorImpl.java:65) ~[na:1.8.0_141]
        at sun.nio.ch.EPollSelectorProvider.openSelector(EPollSelectorProvider.java:36) ~[na:1.8.0_141]
        at java.nio.channels.Selector.open(Selector.java:227) ~[na:1.8.0_141]
        at org.apache.http.impl.nio.reactor.AbstractMultiworkerIOReactor.<init>(AbstractMultiworkerIOReactor.java:142) ~[httpcore-nio-4.4.5.jar!/:4.4.5]
        at org.apache.http.impl.nio.reactor.DefaultConnectingIOReactor.<init>(DefaultConnectingIOReactor.java:79) ~[httpcore-nio-4.4.5.jar!/:4.4.5]
        at org.apache.http.impl.nio.client.IOReactorUtils.create(IOReactorUtils.java:43) ~[httpasyncclient-4.1.3.jar!/:4.1.3]
        at org.apache.http.impl.nio.client.HttpAsyncClientBuilder.build(HttpAsyncClientBuilder.java:666) ~[httpasyncclient-4.1.3.jar!/:4.1.3]
        at org.elasticsearch.client.RestClientBuilder.createHttpClient(RestClientBuilder.java:202) ~[rest-5.5.0.jar!/:5.5.0]
        at org.elasticsearch.client.RestClientBuilder.build(RestClientBuilder.java:180) ~[rest-5.5.0.jar!/:5.5.0]
        at com.myapp.controller.SearchController.getSearchQueryResults(SearchController.java:94) ~[classes!/:1.0]

SearchController 内部(// 注释后的第二行是第 94 行):

@RestController
@RequestMapping("/api/v1")
public class SearchController {

    @RequestMapping(value = "/search", method = RequestMethod.GET, produces="application/json" )
    public ResponseEntity<Object> getSearchQueryResults(@RequestParam(value = "criteria") String criteria) throws IOException {

        // Setup HTTP Headers
        HttpHeaders headers = new HttpHeaders();
        headers.add("Content-Type", "application/json");

        // Setup RestClient
        RestClient restClient = RestClient.builder(new HttpHost("localhost", 9200))
        .setRequestConfigCallback(new RestClientBuilder.RequestConfigCallback() {
            @Override
            public RequestConfig.Builder customizeRequestConfig(RequestConfig.Builder requestConfigBuilder) {
                return requestConfigBuilder.setConnectTimeout(5000).setSocketTimeout(60000);
            }
        }).setMaxRetryTimeoutMillis(60000).build();

        // Setup query and send and return ResponseEntity...

    }
}

很明显,调用了restClient.performRequest()方法后它从来没有关闭过……

所以,我把它放到我的代码中:

Response response = null;
try {
   // Submit Query and Obtain Response
   response = restClient.performRequest("POST", endPoint,  Collections.singletonMap("pretty", "true"), entity);
}
catch (IOException e) {
   LOG.error("\n\n\tException: " + e + "\n\n");
   e.printStackTrace();
}
finally {
   restClient.close();
}

阅读 Elastic Search 的文档,了解 RestClient 类是线程安全的...

此外,请阅读有关 restClient.performRequestAsync() 方法的信息,但我对线程有些缺乏经验,并且文档中的描述含糊不清。

问题:

  1. 我的解决方案是处理和关闭一堆套接字资源的最佳方式吗?

  2. 如果有人能告诉我一个更好的方法来使用 Elastic Search 的低级别 RestClient 将不胜感激,因为它不会导致与未释放套接字资源导致 HTTP 500 相同的问题。应该我正在使用 restClient.performRequestAsync?有人可以举个例子吗?

感谢您抽出宝贵时间阅读本文...

【问题讨论】:

  • 我认为 finally 块中的 restClient.close() 应该可以解决问题。不工作吗?
  • 你能确认你没有使用 Elasticsearch 的 spring boot 启动器吗?
  • 其余客户端应该只在配置 bean 中创建一次,作为依赖注入到您的控制器中,仅此而已。您不需要为每个请求创建一个新实例。
  • @Val,我更新了帖子,为您提供了完整的 pom.xml 文件依赖项,它应该回答您关于 Spring Boot Starters 的问题。我正在使用 Elastic Search 的 Raw Rest Client。你能通过代码给我看一个配置bean的例子吗?顺便说一句,我没有创建一个新实例(它的 Builder 模式)我没有使用“new”Java 关键字。如果您能提供代码,我将不胜感激。可能有 1000 多个移动应用程序(iOS 和 Android)将创建到这个 Spring Boot 微服务的单个连接。所以性能很关键。和非阻塞一样。
  • 您在何时何地创建您的 restClient。你说“在SearchController里面”,但不清楚是什么时候。

标签: java elasticsearch rest-client


【解决方案1】:

对每个请求都创建RestClient 不是一个好习惯。您应该通过如下配置 bean 创建单个实例:

@Configuration
public class ElasticsearchConfig {

    @Value("${elasticsearch.host}")
    private String host;

    @Value("${elasticsearch.port}")
    private int port;

    @Bean
    public RestClient restClient() {
        return RestClient.builder(new HttpHost(host, port))
        .setRequestConfigCallback(new RestClientBuilder.RequestConfigCallback() {
            @Override
            public RequestConfig.Builder customizeRequestConfig(RequestConfig.Builder requestConfigBuilder) {
                return requestConfigBuilder.setConnectTimeout(5000).setSocketTimeout(60000);
            }
        }).setMaxRetryTimeoutMillis(60000).build();
    }
}

然后在您的 SearchController 类中,您可以像这样注入它(并且还添加一个清理方法以在您的容器出现故障时关闭 restClient 实例):

@RestController
@RequestMapping("/api/v1")
public class SearchController {

    @Autowired
    private RestClient restClient;

    @RequestMapping(value = "/search", method = RequestMethod.GET, produces="application/json" )
    public ResponseEntity<Object> getSearchQueryResults(@RequestParam(value = "criteria") String criteria) throws IOException {

        // Setup HTTP Headers
        HttpHeaders headers = new HttpHeaders();
        headers.add("Content-Type", "application/json");

        // Setup query and send and return ResponseEntity...

        Response response = this.restClient.performRequest(...);

    }

    @PreDestroy
    public void cleanup() {
        try {
            logger.info("Closing the ES REST client");
            this.restClient.close();
        } catch (IOException ioe) {
            logger.error("Problem occurred when closing the ES REST client", ioe);
        }
    }

}    

【讨论】:

  • 感谢您提供如此精心设计的方法!我还需要使用 restClient.close();在 finally { } 内还是 cleanup() 方法这样做?我之前遇到错误的原因是因为我没有调用 restClient.close()。我应该在 performRequest() 之后包含 finally 并保留 cleanup() 方法吗?
  • 不行,你可以让其余客户端一直打开,只在清理方法中关闭。试试看,你会看到 ;-)
  • 又发生了,这次使用的是您的解决方案!似乎 RestClient 从来没有关闭过它的连接。我在这里用我的发现创建了一个新帖子:stackoverflow.com/questions/46559512/…
  • 我去看看
猜你喜欢
  • 2019-11-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多