《ElasticSearch实战与原理解析》—— 客户端(初级+高级)实战笔记
学问勤中得,萤窗万卷书
初级客户端实战
实用ES服务,首先获取一个ES客户端。(创建一个连接🔗到集群的传输客户端对象)
初级客户端初始化
🔰初级客户端的主要功能:
- 跨可用节点的负载均衡
- 节点故障和特定响应代码时的故障转移
- 失败连接的惩罚机制。判断一个失败节点是否重试,取决于客户端连接时连接失败的次数;失败的尝试次数越多,客户端再次尝试同一个节点之前等待的时间越长
- 持久连接
- 请求和响应 的跟踪日志记录
- 自动发现群集节点。
🏳🌈 1. 获取客户端组件
添加工程依赖(elasticsearch-rest-client初级客户端实用Apache HTTP异步客户端发送HTTP请求)
<dependency> <groupId>org.elasticsearch.client</groupId> <artifactId>elasticsearch-rest-client</artifactId> <version>${elasticsearch.version}</version> </dependency> <!-- 添加ES的依赖begin --> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpasyncclient</artifactId> <version>4.1.4</version> </dependency> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpcore-nio</artifactId> <version>4.4.11</version> </dependency> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpcore</artifactId> <version>4.4.11</version> </dependency> <dependency> <groupId>commons-codec</groupId> <artifactId>commons-codec</artifactId> <version>1.14</version> </dependency> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.2</version> </dependency>
🏳🌈 2.客户端初始化
用户通过
RestClientBuilder
类来构建RestClient实例,该类是通过RestClient builder(HttpHost...)静态方法创建的。
RestClient在初始化时需要将服务主机🖥的地址+端口号
加载构建,例如:
RestClient restClient = RestClient.builder( new HttpHost("localhost", 9200, "http"), new HttpHost("localhost", 9201, "http")).build();
RestClient类是线程安全的。它应该于应用程序具有相同的声明周期。当不需要时,应该关闭它,以便释放它使用的所有资源以及底层HTTP客户端实例及其线程。
关闭方法:restClient.close
✅实例代码-> 构建初级客户端:https://gist.github.com/serendipitywzz/3a8281bfd90ca5c23d0ac6ad5ddf3b04
Controller层、Service层、ServiceImpl实现层
👽 Service层
public interface MeetElasticSearchService { void initEs(); void closeEs(); }
👽 ServiceImpl实现层
@Service // @Service用于标注业务组件,表示定义一个Bean,自动根据Bean的类名实例化一个首写字母小写的Bean public class MeetElasticSearchServiceImpl implements MeetElasticSearchService { // 创建logManager对象,可以在log的日志文件中看到 private static Log log = LogFactory.getLog(MeetElasticSearchServiceImpl.class); private RestClient restClient; // 声明客户端实例对象 @PostConstruct // 在该类向外提供服务之前调用 @Override public void initEs() { restClient = RestClient .builder( // 节点地址+端口号 new HttpHost("localhost", 9200, "http"), new HttpHost("localhost", 9201, "http")) .build(); log.info("ElasticSerach init in service☺!!!"); } @Override public void closeEs() { try { restClient.close(); } catch (IOException e) { e.printStackTrace(); } } }
👽 Controller层
@RestController @RequestMapping("/springboot/es") public class MeetElasticSearchController { @Autowiredd private MeetElasticSearchService meetElasticSearchService; @RequestMapping("/init") public String initElasticSearch(){ meetElasticSearchService.initEs(); // 客户端构建工作 return "Init ElasticSearch Over❤"; } }
🥫输出
RestClientBuilder在构建RestClient时选择性配置参数
🔷Serivce层
void initEs(); // 初始化RestClient初级客户端 void initEsWithHeader(); // 请求头配置方法 void initEsWithFail(); // 配置监听器 void initEsWithNodeSelector(); // 配置节点选择器 void initEsWithTimeout(); // 设置超时时间 void closeEs(); // 关闭RestClient初级客户端
🔶ServiceImpl层
@Service // @Service用于标注业务组件,表示定义一个Bean,自动根据Bean的类名实例化一个首写字母小写的Bean public class MeetElasticSearchServiceImpl implements MeetElasticSearchService { // 创建logManager对象,可以在log的日志文件中看到 private static Log log = LogFactory.getLog(MeetElasticSearchServiceImpl.class); private RestClient restClient; // 声明客户端实例对象 @PostConstruct // 在该类向外提供服务之前调用 @Override public void initEs() { restClient = RestClient .builder( // 节点地址+端口号 new HttpHost("localhost", 9200, "http"), new HttpHost("localhost", 9201, "http")) .build(); log.info("ElasticSerach init in service☺!!!"); } /** * 请求头配置方法 * */ @Override public void initEsWithHeader() { RestClientBuilder builder = RestClient.builder( new HttpHost("localhost",9200,"http") ); // 设置每个请求需要发送的默认请求头,以防止在每个请求中指定它们 Header[] defaultHeaders = new Header[]{ new BasicHeader("header", "value") }; builder.setDefaultHeaders(defaultHeaders); } /** * 配置监听器 - 带失败监听 * */ @Override public void initEsWithFail() { RestClientBuilder builder = RestClient.builder( new HttpHost("localhost", 9200, "http") ); // 设置一个监听器,该监听器在每次节点失败时都会收到通知,在启用嗅探时用在内部 builder.setFailureListener(new RestClient.FailureListener(){ @Override public void onFailure(Node node){ } }); } /** * 配置节点选择器 * */ @Override public void initEsWithNodeSelector() { RestClientBuilder builder = RestClient.builder( new HttpHost("localhost", 9200, "http") ); builder.setNodeSelector(NodeSelector.SKIP_DEDICATED_MASTERS); } /** * 设置超时时间 * */ @Override public void initEsWithTimeout() { RestClientBuilder builder = RestClient.builder( new HttpHost("localhost", 9200, "http") ); builder.setRequestConfigCallback(new RestClientBuilder.RequestConfigCallback() { @Override public RequestConfig.Builder customizeRequestConfig(RequestConfig.Builder builder) { return builder.setSocketTimeout(10000); } }); } @Override public void closeEs() { try { restClient.close(); } catch (IOException e) { e.printStackTrace(); } } }
提交请求
客户端构建好之后,执行请求之前需要构建请求对象。用户通过调用客户端的performRequest
和performRequestAsync
来发送请求。
🐷performRequest: 是同步请求方法: 将阻塞调用线程,并在请求成功时返回响应,或在请求失败时引发异常
🐷performRequestAsync: 是异步方法:接收一个ResponseListener对象作为参数。如果请求成功,则该参数使用响应进行调用;如果请求失败,则使用异常进行调用
构建请求对象Request
请求对象Request的请求方式于The HTTP的请求方式相同。eg: GET、POST、PUT、DELETE、PATCH、HEAD、OPTIONS等
public Request buildRequest(){ Request request = new Request("GET","/"); // 与The HTTP的请求方式相同,eg: GET、POST、PUT、DELETE, and the like.(等等;依次类推) return request; // 返回请求对象 }
请求的执行
在构建请求(Request)后,可执行请求。请求有同步执行和异步执行两种方式。
💊理解同步和异步方式
形象理解:
同步(阻塞方式): 你叫我去吃饭,我听到了就和你去吃饭;如果没有听到,你就扯开嗓子一直喊,知道我告诉你听到了,才一起去吃饭。
异步(非阻塞方式): 你叫我,然后自己去吃饭,我得到消息后可能立即走,也可能得到下班才吃饭。
打电话是同步;发消息是异步
同步: 两个或两个以上随时间变化的量在变化过程中保持一定的相对关系
异步: 当一个异步过程调用发出后,调用者不能立刻得到结果。
💙同步方式(perforRequest)
以同步方式执行Request时,客户端会等待ES服务器返回的查询结果Response。在收到Response后,客户端继续执行相关的逻辑代码。(客户端需要等到结果返回才会执行下一步)
🔸Service层
String executeSyncRequest(); // 构建对ES服务的同步请求
🔸ServiceImpl层
/** * 如何构建对ES服务的请求 * */ @Override public String executeSyncRequest() { // 与 The HTTP的请求方式相同,eg: GET、POST、PUT、DELETE... Request request = new Request("GET","/"); try{ Response response = restClient.performRequest(request); // 执行同步请求 return response.toString(); }catch(Exception e){ e.printStackTrace(); } try { restClient.close(); } catch (IOException e) { e.printStackTrace(); } return "Get result failed!😡"; }
🔸Controller层
@RequestMapping("/buildSyncRequest") public String executeSyncRequestForElasticSearch(){ return meetElasticSearchService.executeSyncRequest(); }
🔸执行结果:
💚异步方式(performRequestAsyc)
当以异步方式执行请求时,初级客户端不必同步等待请求结果的返回,可以直接向接口调用方返回异步接口执行成功的结果。
处理异步返回的响应信息或处理在请求执行过程中引发的异常信息,用户需要指定监听器。以异步方式调用(客户端发送请求后不管结果如何都会继续走下去)
🔷Service层
String executeAsyncRequest(); // 构建对ES服务的异步请求
🔷ServiceImpl层
@Override public String executeAsyncRequest() { // 与The HTTP的请求方式相同,eg: GET、POST、PUT、HEAD、OPTIONS Request request = new Request("GET", "/"); // 在服务器上请求 restClient.performRequestAsync(request, new ResponseListener() { @Override public void onSuccess(Response response) { log.info("异步请求成功!" + response.toString()); } @Override public void onFailure(Exception e) { log.error("异步请求失败!"); e.printStackTrace(); } }); try{ restClient.close(); }catch(IOException e){ e.printStackTrace(); } return "GET result failed!"; }
🔷Controller层
@RequestMapping("/buildAsyncRequest") public String executeAsyncRequesForElasticSearch(){ return meetElasticSearchService.executeAsyncRequest(); }
🔹执行结果:
可选参数配置
不论同步请求,还是异步请求都可以在请求中添加参数。
⭐第一种
request.addParameter("pretty","true");
⭐第二种
request.setEntity(new NStringEntity("{\"json\":\"text\"}",ContentType.APPLICATION_JSON));
⭐第三种
request.setJsonEntity("{\"json\":\"text\"}");
RequestOptions类中保存的请求,可以在同一应用程序的多个请求之间共享。用户可以创建一个单实例,然后在所有请求之间共享。
/** * 设置全局单实例RequestOptions * */ private static final RequestOptions COMMON_OPTIONS; static{ RequestOptions.Builder builder = RequestOptions.DEFAULT.toBuilder(); builder.addHeader("Authorization", "Bearer " + "my-token"); builder.setHttpAsyncResponseConsumerFactory( new HttpAsyncResponseConsumerFactory .HeapBufferedResponseConsumerFactory( 30 * 1024 * 1024 * 1024 )); COMMON_OPTIONS = builder.build(); } /** * 如何构建对ES服务的请求 * */ @Override public String buildRequestWithRequestOptions(){ Request request = new Request("GET", "/"); // 在服务器上请i去 try { Response response = restClient.performRequest(request); RequestOptions.Builder options = COMMON_OPTIONS.toBuilder(); options.addHeader("title", "u r my dear!"); request.setOptions(COMMON_OPTIONS); return response.toString(); } catch (IOException e) { e.printStackTrace(); } try { restClient.close(); } catch (IOException e) { e.printStackTrace(); } return "Get Result Failed!!!"; }
多个并行异步操作
除了单个操作的执行外,ES的客户端可以并行执行操作。
eg: 在ServiceImpl实现层展示对多文档进行并行索引。
/** * 并发处理文档数据 * */ public void multiDocumentProcess(HttpEntity[] documents){ final CountDownLatch latch = new CountDownLatch(documents.length); for(int i = 0; i < documents.length; i++){ Request request = new Request("PUT", "/posts/doc/" + i); // 假设documents存储在HttpEntity数组中 request.setEntity(documents[i]); restClient.performRequestAsync(request, new ResponseListener() { @Override public void onSuccess(Response response) { latch.countDown(); } @Override public void onFailure(Exception e) { latch.countDown(); } }); try { latch.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } }
对请求结果的解析
请求对象分为: 同步请求和异步请求。
对请求的响应结果Response的解析分为两种:
🐷同步请求得到的响应对象是由performRequest
方法返回的;
🐷异步请求得到的响应对象是通过ResponseListener
类下onSuccess(Response)
方法中的参数接收的。响应对象中包装HTTP客户端返回的响应对象,并公开一些附加信息。
以同步请求方式为例,对请求结果进行解析
😱 Service层
void parseElasticSearchResponse(); // 解析ES服务的返回结果
😱 ServiceImpl层
/** * 如何解析ElasticSearch服务的返回结果 * */ @Override public void parseElasticSearchResponse() { try { Response response = restClient.performRequest(new Request("GET", "/")); // 已执行请求的信息 RequestLine requestLine = response.getRequestLine(); // Host返回的信息 HttpHost httpHost = response.getHost(); // 响应状态行,从中解析状态代码 int statusCode = response.getStatusLine().getStatusCode(); // 响应头,可以通过getHeader(string)按名称获取 Header[] headers = response.getHeaders(); String responseBody = EntityUtils.toString(response.getEntity()); log.warn("parse ElasticSearch Response, responseBody is: " + responseBody); } catch (IOException e) { e.printStackTrace(); } try { restClient.close(); } catch (IOException e) { e.printStackTrace(); } }
😱 Controller层
@RequestMapping("/parseEsResponse") public String parseElasticSearchResponse(){ meetElasticSearchService.parseElasticSearchResponse(); return "Parse ElasticSearch Response Is Over!"; }
😱 执行结果
常见通用设置
客户端还支持一些常见通用设置,eg: 超时设置、线程数设置、节点选择器设置和配置嗅探器等。
超时设置
构建RestClient提供requestconfigCallback的实例来完成超时设置。该接口有一个方法,接收
org.apache.http.client.config.requestconfig.builder
的实例作为参数,并具有相同的返回类型。
用户可以修改请求配置生成器org.apache.http.client.config.requestconfig.builder
的实例,然后返回。
/** * 设置超时时间 * */ @Override public void initEsWithTimeout() { RestClientBuilder builder = RestClient.builder( new HttpHost("localhost", 9200, "http") ); builder.setRequestConfigCallback(new RestClientBuilder.RequestConfigCallback() { @Override public RequestConfig.Builder customizeRequestConfig(RequestConfig.Builder builder) { return builder .setConnectTimeout(50000) // 连接超时默认1s .setSocketTimeout(10000); // 套接字超时默认30s } }); }
线程数设置
Apache HTTP异常客户端默认启动一个调度程序线程,连接管理器使用多个工作线程。一般线程数与本地检测到的处理器数量相同,线程数主要取决于Runtime.getRuntime().availableProcessors()返回的结果。
ES允许用户修改线程。
/** * 设置线程数 * */ @Override public void setThreadNumber(int threadNumber) { RestClientBuilder builder = RestClient.builder(new HttpHost("localhost", 9200)) .setHttpClientConfigCallback(new RestClientBuilder.HttpClientConfigCallback() { @Override public HttpAsyncClientBuilder customizeHttpClient(HttpAsyncClientBuilder httpAsyncClientBuilder) { return httpAsyncClientBuilder.setDefaultIOReactorConfig( IOReactorConfig.custom().setIoThreadCount(threadNumber).build() ); } }); }
节点选择器
在默认情况下,客户端以轮询的方式将每个请求发送到配置的各个节点中
ES允许用户自由选择要连接的节点,通过初始化客户端来配置节点选择器,以便筛选节点。该功能在启用嗅探器时可以用来防止HTTP请求只命中专用的主节点。
配置后,对于每个请求,客户端都通过节点选择器来筛选备选节点。
/** * 节点选择器设置 * */ @Override public void setNodeSelector() { RestClientBuilder builder = RestClient.builder(new HttpHost("localhost",9200,"http")); builder.setNodeSelector(new NodeSelector(){ @Override public void select(Iterable<Node> nodes){ boolean foundOne = false; for(Node node : nodes){ String rackId = node.getAttributes().get("rack_id").get(0); if("targetId".equals(rackId)){ foundOne = true; break; } } if(foundOne){ Iterator<Node> nodesIt = nodes.iterator(); while(nodesIt.hasNext()){ Node node = nodesIt.next(); String rackId = node.getAttributes().get("rack_id").get(0); if("targetId".equals(rackId) == false){ nodesIt.remove(); } } } } }); }
配置嗅探器
嗅探器允许自动发现运行中ES集群中的节点,并将其设置为现有的RestClient实例
默认i情况下,嗅探器使用nodes info API检索属于集群的节点并采用jackson解析获得JSON响应
🐷Maven版本依赖
<dependency> <groupId>org.elasticsearch.client</groupId> <artifactId>elasticsearch-rest-client-sniffer</artifactId> <version>${elasticsearch.version}</version> </dependency>
创建RestClient实例就可以采用嗅探器与其互联。嗅探器利用RestClient提供的定期机制(默认定期时间为5min),从集群中获取当前节点的列表,通过调用RestClient类中的setNodes方法来更新。
/** * 配置嗅探器 * */ @Override public void setSniffer() { // 创建客户端实例 RestClient restClient = RestClient.builder( new HttpHost("localhost", 9200, "http")) .build(); Sniffer sniffer = Sniffer.builder(restClient) // 嗅探器每隔5min更新一次节点,可以通过setSniffIntervalMills(以毫秒为单位) .setSniffIntervalMillis(60000).build(); // 使用后,结束客户端和嗅探器 try { sniffer.close(); restClient.close(); } catch (IOException e) { e.printStackTrace(); } }
除了在客户端启动时配置嗅探器外可以在失败时启用嗅探器,在每次失败后,节点列表都会立即更新,而不是在接下来的普通嗅探循环中更新。
需要创建SniffOnFailureListener
,然后创建RestClient时配置。在创建嗅探器后,同一个SniffOnFailureListener实例会相互关联,以便在每次失败时候都通知该实例并使用嗅探器执行嗅探动作。
@Override public void setSnifferWhenFail(int failTime) { SniffOnFailureListener sniffOnFailureListener = new SniffOnFailureListener(); RestClient restClient = RestClient.builder( new HttpHost("localhost", 9200)) .setFailureListener(sniffOnFailureListener).build(); Sniffer sniffer = Sniffer.builder(restClient) .setSniffAfterFailureDelayMillis(failTime).build(); sniffOnFailureListener.setSniffer(sniffer); // 使用后,结束客户端和嗅探器 try { sniffer.close(); restClient.close(); } catch (IOException e) { e.printStackTrace(); } }
由于ES节点信息API不会返回连接到节点时要使用的协议只返回host:port,在默认情况下使用
http
如果要使用https
,需要手动创建并提供ElasticSearchNodesSniffer实例
@Override public void setSnifferWithHTTPS(int failTime) { RestClient restClient = RestClient.builder( new HttpHost("localhost", 9200)) .build(); NodesSniffer nodesSniffer = new ElasticsearchNodesSniffer(restClient, ElasticsearchNodesSniffer.DEFAULT_SNIFF_REQUEST_TIMEOUT, ElasticsearchNodesSniffer.Scheme.HTTPS); Sniffer sniffer = Sniffer.builder(restClient).setNodesSniffer(nodesSniffer) .build(); // 使用后结束客户端和嗅探器 try { sniffer.close(); restClient.close(); } catch (IOException e) { e.printStackTrace(); } }
高级客户端初始化
在ES7.x中关闭TransportClient,并在8.x中完全删除
TransportClient
。作为替代品,应该使用高级客户端执行HTTP请求而不是序列化Java请求。
高级客户端的主要目标是特定的API方法,这些API方法将接收请求作为参数并返回响应结果。以便由客户端本身处理请求和响应结果
高级客户端同初级一样有同步、异步两种API调用方式。以同步调用方式调用后直接返回响应对象,而异步调用方式则需要配置一个监听器参数才能调用,将参数在收到响应或错误后会得到相应的结果通知。
物质决定意识,意识对物质具有反作用,人的意识具有主观能动性
🔶高级客户端Maven依赖
<!-- ES高级客户端 --> <dependency> <groupId>org.elasticsearch.client</groupId> <artifactId>elasticsearch-rest-high-level-client</artifactId> <version>${elasticsearch.version}</version> </dependency> <!-- 添加ES高级客户端依赖begin--> <dependency> <groupId>org.elasticsearch</groupId> <artifactId>elasticsearch</artifactId> <version>${elasticsearch.version}</version> </dependency> <!-- 添加ES高级客户端依赖end-->
高级客户端的初始化需要依赖于RestHighLevelClient实现的,而RestHightLeaveClient是基于初级客户端生成器构建的
高级客户端在内部创建执行请求的初级客户端,该初级客户端会维护一个连接池并启动一些线程。
🌐Serivce层
@Service // @Service用于标注业务组件,表示定义一个Bean将会自动根据类名首字母小写创建对应实例对象 public class MeetHighElasticSearchServiceImpl implements MeetHightElasticSearchService { /*要求在构造器初始化之后自动调用在init()方法之前执行 (表示该类实例化完毕后如何要向外提供服务需要先执行此方法 执行顺序: @Constructor() -> @Autowired() -> @Postconstruct() */ private static Log log = LogFactory.getLog(MeetHightElasticSearchService.class); private RestHighLevelClient restHighLevelClient; // 初始化连接 @PostConstruct @Override public void initEs() { restHighLevelClient = new RestHighLevelClient(RestClient.builder( new HttpHost("localhost", 9200), new HttpHost("localhost", 9201), new HttpHost("localhost", 9202) )); log.warn(">>>>>ElasticSearc init in service<<<<<"); } // 关闭连接 @Override public void closeEs() { try { restHighLevelClient.close(); } catch (IOException e) { e.printStackTrace(); } } }
🌐Controller层
@RequestMapping("/higtlevelinit") public String initHighLevelSearch(){ meetHighElasticSearchService.initEs(); // 高级客户端构建工作 return "Init HighLevelElasticSearch Over❤"; }
🌐结果
知识点关联
什么是同步和异步?以及它们之间的区别和联系...
对不同线程而言,会有同步和异步之分
对同一个线程而言,会有阻塞和非阻塞之分
💧同步: 当前线程发起请求调用后,在被调用的线程处理消息过程中,当前线程必须等被调用的线程处理完毕后才能返回结果,如果被调用的线程还未处理完则当前线程必须于被调用线程保持同步不能返回结果;
💧异步: 当前线程发起请求调用后,在被调用的线程处理消息过程中,不需要等到被调用线程处理完毕直接进入下一步操作;
💧阻塞:当前线程同步等待其他线程的处理结果;
💧非阻塞:当前线程同步于需配合的其他线程的结果处理过程;
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· winform 绘制太阳,地球,月球 运作规律
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· 写一个简单的SQL生成工具