[英]Elasticsearch Rest Client Still Giving IOException : Too Many Open Files
這是我之前發布的解決方案的后續跟進:
使用Elastic Search 5.5.0獲得最佳性能時如何正確關閉原始RestClient?
同樣的錯誤信息又回來了!
2017-09-29 18:50:22.497 ERROR 11099 --- [8080-Acceptor-0] org.apache.tomcat.util.net.NioEndpoint : Socket accept failed
java.io.IOException: Too many open files
at sun.nio.ch.ServerSocketChannelImpl.accept0(Native Method) ~[na:1.8.0_141]
at sun.nio.ch.ServerSocketChannelImpl.accept(ServerSocketChannelImpl.java:422) ~[na:1.8.0_141]
at sun.nio.ch.ServerSocketChannelImpl.accept(ServerSocketChannelImpl.java:250) ~[na:1.8.0_141]
at org.apache.tomcat.util.net.NioEndpoint$Acceptor.run(NioEndpoint.java:453) ~[tomcat-embed-core-8.5.15.jar!/:8.5.15]
at java.lang.Thread.run(Thread.java:748) [na:1.8.0_141]
2017-09-29 18:50:23.885 INFO 11099 --- [Thread-3] ationConfigEmbeddedWebApplicationContext : Closing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@5387f9e0: startup date [Wed Sep 27 03:14:35 UTC 2017]; root of context hierarchy
2017-09-29 18:50:23.890 INFO 11099 --- [Thread-3] o.s.c.support.DefaultLifecycleProcessor : Stopping beans in phase 2147483647
2017-09-29 18:50:23.891 WARN 11099 --- [Thread-3] o.s.c.support.DefaultLifecycleProcessor : Failed to stop bean 'documentationPluginsBootstrapper'
... 7 common frames omitted
2017-09-29 18:50:53.891 WARN 11099 --- [Thread-3] o.s.c.support.DefaultLifecycleProcessor : Failed to shut down 1 bean with phase value 2147483647 within timeout of 30000: [documentationPluginsBootstrapper]
2017-09-29 18:50:53.891 INFO 11099 --- [Thread-3] o.s.j.e.a.AnnotationMBeanExporter : Unregistering JMX-exposed beans on shutdown
2017-09-29 18:50:53.894 INFO 11099 --- [Thread-3] com.app.controller.SearchController : Closing the ES REST client
我嘗試使用上一篇文章中的解決方案。
ElasticsearchConfig:
@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:
@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);
}
}
}
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>
<!-- Log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
</dependencies>
這讓我覺得RestClient從來沒有明確地關閉連接,首先......
這是令人驚訝的,因為我的Elasticsearch基於Spring Boot的微服務在兩個不同的AWS EC-2服務器上進行負載平衡。
該異常發生的情況類似於日志文件報告的2000次,並且最終只有preDestroy()關閉了客戶端。 請參閱StackTrace末尾記錄的@PreDestroy()清理方法中的INFO。
我是否需要在SearchController中顯式放置finally子句並顯式關閉RestClient連接?
由於此搜索微服務依賴於許多不同的移動客戶端(iOS和Android),因此不會再次發生此IOException非常關鍵。
需要它具有容錯性和可擴展性......或者,至少,不要破壞。
這是在日志文件底部的唯一原因:
2017-09-29 18:50:53.894 INFO 11099 --- [Thread-3] com.app.controller.SearchController : Closing the ES REST client
是因為我這樣做了:
kill -3 jvm_pid
我應該保留@PreDestory cleanup()方法,但更改我的SearchController.getSearchResults()方法的內容以反映類似這樣的內容:
@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 = null;
try {
// Submit Query and Obtain Response
response = this.restClient.performRequest("POST", endPoint, Collections.singletonMap("pretty", "true"), entity);
}
catch(IOException ioe) {
logger.error("Exception when performing POST request " + ioe);
}
finally {
this.restClient.close();
}
// return response as EsResponse();
}
這樣RestClient連接總是關閉...
如果有人可以幫助我,我將不勝感激。
從我的角度來看,你做錯了很少但我會直接找到解決方案。
我不會寫完整的解決方案(事實上,我沒有執行或測試任何東西),但重要的是理解它。 此外,最好將所有與數據訪問相關的內容移動到另一個層。 無論如何,這只是一個例子,所以設計並不完美 。
第1步:導入正確的庫。
實際上和你的例子一樣。 我更新了示例以使用5.6.2版中推薦的最后一個客戶端庫
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.acervera</groupId>
<artifactId>elastic-example</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>elastic-example</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<es.version>5.6.2</es.version>
</properties>
<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>${es.version}</version>
</dependency>
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>${es.version}</version>
</dependency>
<!-- Log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
</dependencies>
</project>
第2步:在bean工廠中創建並關閉客戶端。
在bean工廠中,創建並銷毀它。 您可以重用相同的客戶端。
import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.io.IOException;
@Configuration
public class ElasticsearchConfig {
// Here all init stuff with @Value(....)
RestClient lowLevelRestClient;
RestHighLevelClient client;
@PostConstruct
public void init() {
lowLevelRestClient = RestClient.builder(new HttpHost("host", 9200, "http")).build();
client = new RestHighLevelClient(lowLevelRestClient);
}
@PreDestroy
public void destroy() throws IOException {
lowLevelRestClient.close();
}
@Bean
public RestHighLevelClient getClient() {
return client;
}
}
步驟3:使用Java Transport Client執行查詢。
使用Java Transport Client執行查詢。
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
@RestController
@RequestMapping("/api/v1")
public class SearchController {
@Autowired
private RestHighLevelClient client;
@RequestMapping(value = "/search", method = RequestMethod.GET, produces="application/json" )
public ResponseEntity<Tweet> getSearchQueryResults(@RequestParam(value = "criteria") String criteria) throws IOException {
// This is only one example. Of course, this logic make non sense and you are going to put it in a DAO
// layer with more logical stuff
SearchRequest searchRequest = new SearchRequest();
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(QueryBuilders.matchAllQuery());
SearchResponse searchResponse = client.search(searchRequest);
if(searchResponse.getHits().totalHits > 0) {
SearchHit searchHit = searchResponse.getHits().iterator().next();
// Deserialize to Java. The best option is to use response.getSource() and Jackson
// This is other option.
Tweet tweet = new Tweet();
tweet.setId(searchHit.getField("id").getValue().toString());
tweet.setTittle(searchHit.getField("tittle").getValue().toString());
return ResponseEntity.ok(tweet);
} else {
return ResponseEntity.notFound().build();
}
}
}
此外,使用bean來構建響應。
public class Tweet {
private String id;
private String tittle;
public String getTittle() {
return tittle;
}
public void setTittle(String tittle) {
this.tittle = tittle;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
// Here rest of bean stuff (equal, hash, etc) or Lombok
}
第四步
享受Elasticsearch!
注: Java REST客戶端[5.6]»Java高級REST客戶端
PS。 有必要重構這個例子。 只是了解方式。
您確定在每個HTTP請求上都沒有啟動與elasticsearch服務器的新(線程池)連接嗎? 即,排隊
Response response = this.restClient.performRequest(...);
在單個HTTP請求后仔細檢查elasticsearch服務器上的日志。 您應該嘗試在沒有@Autowired注釋的情況下實現Singleton模式,並查看問題是否仍然存在。
從您的堆棧跟蹤看,由於too many open files
錯誤,嵌入式tomcat(您的應用程序容器)似乎不再能夠接受新連接。 從您的代碼中, elasticsearch rest client
似乎沒有問題。
由於在為搜索請求提供服務時重新使用RestClient
的單個實例,因此與ES群集的打開連接可能不會超過30個( org.elasticsearch.client.RestClientBuilder.DEFAULT_MAX_CONN_TOTAL
)。 因此RestClient
不太可能導致問題。
其他潛在的根本原因可能是您的服務的消費者正在與您的(tomcat)服務器保持連接打開更長時間,或者他們沒有正確關閉連接。
我是否需要在SearchController中顯式放置finally子句並顯式關閉RestClient連接?
不,你不應該。 在關閉服務時應該關閉Rest客戶端(在@PreDestroy方法中,因為你已經正確地做了)。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.