下面先简单的介绍一下RestHighLevelClient使用,再看下大家在使用过程中可能踩的坑。
1、springboot如何集成RestHighLevelClient?
2、RestHighLevelClient初始化源码分析
一、springboot如何集成RestHighLevelClient?
1、引入maven依赖
<!--引入es相关依赖-->
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>7.6.2</version>
</dependency>
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-client</artifactId>
<version>7.6.2</version>
</dependency>
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>7.6.2</version>
</dependency>
2、创建RestHighLevelClient实例对象
RestClientBuilder builder = RestClient.builder(
//单机部署
new HttpHost("127.0.0.1", 9200, "http")
);
RestHighLevelClient highLevelClient = new RestHighLevelClient(builder);
3、通过client实例访问ES
//1.创建 SearchRequest搜索请求
SearchRequest searchRequest = new SearchRequest();
//指定要查询的索引
searchRequest.indices("user");
//2.创建 SearchSourceBuilder条件构造。builder模式这里就先不简写了
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
MatchAllQueryBuilder matchAllQueryBuilder = QueryBuilders.matchAllQuery();
searchSourceBuilder.query(matchAllQueryBuilder);
searchRequest.source(searchSourceBuilder);
SearchResponse searchResponse = highLevelClient.search(searchRequest, RequestOptions.DEFAULT);
System.out.println("查询响应结果: " + searchResponse);
通过以上三步即可简单的访问ES服务。
二、初始化RestHighLevelClient的坑
对于初始化restHighLevelClient的正常流程来说只需要在spring容器启动后,使用@Bean注解实例化一个restHighLevelClient对象,然后在不用的service中@Autowire注入即可使用。
那有没有想过为什么正常只推荐单例模式就可以了?如果我每次使用restHighLevelClient都去new一个会出现什么问题?
下面我们就简单的分析下restHighLevelClient的原理。
先说一下结论
restHighLevelClient整体流程采取的是NIO+Reactor模式来处理网络IO通信问题(关于NIO和Reactor模式可以看我另一篇介绍Netty的文章),而且在这个的基础上增加了一个池,通过池一方面可以弹性配置连接的大小,另一方面可以客户端做一些实现来对连接池的状态进行监控。所以在Elasticsearch的restClient使用中,为什么使用单例而且能保持比较高的性能。
如何验证?
我们可以建一个springboot项目demo,在项目启动后实例化一个restHighLevelClient对象,然后用jdk自带的jvm监控工具进行分析。
@SpringBootApplication
public class SpringLearningApplication implements ApplicationListener<ApplicationReadyEvent> {
public static void main(String[] args) {
SpringApplication.run(SpringLearningApplication.class, args);
}
// 应用启动后创建实例化一个highLevelClient
@SneakyThrows
@Override
public void onApplicationEvent(@NotNull ApplicationReadyEvent event) {
RestClientBuilder builder = RestClient.builder(
//单机部署
new HttpHost("127.0.0.1", 9200, "http")
);
Thread.sleep(60000);
RestHighLevelClient highLevelClient = new RestHighLevelClient(builder);
}
}
应用启动后,我们通过jvisualvm连接上应用可以看到还未初始化restHighLevelClient时I/O dispatcher线程只有4个。
new RestHighLevelClient(builder)执行之后I/O dispatcher线程变成8个。
从图中我们直观的可以看到线程数的变化,接下来从RestHighLevelClient构造方法的源码进入逐步分析以下为什么这里默认创建了4个线程,这个跟什么有关?
-
RestHighLevelClient构造方法
他的核心在restClientBuilder.build()创建RestClient。
创建CloseableHttpAsyncClient过程中如果没有配置IOReactorConfig,则采用默认的IOReactorConfig.DEFAULT 中创建的默认的ioThreadCount为cpu核心数
因此我们在使用RestHighLevelClient时连接一个ES服务只需要创建一个RestHighLevelClient实例。如果有需要可以自己按照如下方式设置下线程池大小,正常业务不是并发量特别大的场景,直接默认的配置即可。
restClientBuilder.setHttpClientConfigCallback(
new RestClientBuilder.HttpClientConfigCallback() {
@Override
public HttpAsyncClientBuilder customizeHttpClient(
HttpAsyncClientBuilder httpClientBuilder) {
return httpClientBuilder.setDefaultIOReactorConfig(
IOReactorConfig.custom()
.setIoThreadCount(10) // 设置线程数
.build());
}
});
如果每次使用时都去new,造成的结果就是应用线程数不断增长,很可能抛出OutOfMemoryError异常,且不能提供正常的服务。
// 测试循环new RestHighLevelClient
for (int i = 0; i < 10000000; i++) {
RestHighLevelClient highLevelClient = new RestHighLevelClient(builder);
Thread.sleep(200);
}
总结
大家在引入RestHighLevelClient连接同一个ES服务使用单例模式!