ElasticSearch : Client 端解析

986 阅读6分钟

首先分享之前的所有文章 , 欢迎点赞收藏转发三连下次一定 >>>> 😜😜😜
文章合集 : 🎁 juejin.cn/post/694164…
Github : 👉 github.com/black-ant
CASE 备份 : 👉 gitee.com/antblack/ca…

一 . 前言

近期对 Elastic 进行了细节梳理 , 整理了一下在开发过程中对源码涉及比较多的地方 , 在此分享一下.

此文章主要对以下 2个Jar 包进行解析 :

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>

<dependency>
  <groupId>org.elasticsearch</groupId>
  <artifactId>elasticsearch</artifactId>
</dependency>

二 . Spring 主逻辑

3.1 基础使用

@Repository
public interface OrderRepository extends ElasticsearchRepository<Order, String> {
    List<Order> findFirstByUsername(String username);
}

@Data
@Document(indexName = "order")
public class Order {
    private String username;
}

基于 Spring 的使用很简单 , 参照 JPA 写法即可 , 这里就不扩展了 , 后文来深入看一下源码的走向.

3.2 Spring ElasticsearchRepository 的初始化

初始化主要是指方法的代理和配置的加载 , 这里主要集中在2个环节中.小技巧之一 ,针对 Spring 体系 , 想要知道初始化配置方式 , 直接搜索框架 + Configuration 即可 . 通常自动装配类就是

这里简单过一下几个重要的自动装配类 :

// S1 : 连接配置类 
C- ElasticsearchDataConfiguration
    - 创建 SimpleElasticsearchMappingContext , 主要用于 Spirng Data Repository 的映射
    - 创建 ElasticsearchRestTemplate , 默认使用 RestHighLevelClient , 发起底层 Client 的调用
    - 创建 ReactiveElasticsearchTemplate , 针对 Reactive 体系
    
    
// S2 : Client 端配置类
C- ElasticsearchRestClientConfigurations
    - 创建 RestClientBuilder , ES 的 Client 只允许使用该类创建 , 此类初始化 HOST , PORT
    - 创建 RestHighLevelClient , ES 高级接口 , 也是主要的 Client 访问类

3.3 查询主流程

通常业务上的问题 , 主要集中在查询上 , 我们想要达到业务需求 , 就需要了解其底层的访问逻辑.

在核心查询流程中 , 主要涉及到的类为AbstractElasticsearchRepositorySimpleElasticsearchRepository.

这里代理的转换后续再分析 , 首先 Spring 的处理入口为 ElasticsearchPartQuery

// S1 : ElasticsearchPartQuery # execute 对 Query 请求进行处理
1. 获取到实体类 
    - queryMethod.getEntityInformation().getJavaType()
2. 创建出 CriteriaQuery 查询体 
    - new ElasticsearchQueryCreator
3. 标注高亮查询 
    - query.setHighlightQuery(queryMethod.getAnnotatedHighlightQuery());
4. 判断查询类型 
    - isPageQuery / isStreamQuery / isCollectionQuery / isCountProjection
    - PS : 此处同样会先 count 再 search
5. 调用 search 进行查询    
    - result = elasticsearchOperations.search(query, clazz, index)


// S2 :通过 ElasticsearchRestTemplate 发起 Client JAR 的调用
> PS : Spring 中大量使用使用模板方法用于将不同的客户端框架聚合进来
> PS : 这个类有30多位作者~~

// PS : 这里主要是 CURD 的一些操作 , 操作的方式大同小异 , 不过多的关注流程 , 着重关注一下用法
1. 通过 RequestFactory 创建 SearchRequest
    ?- SearchRequest 是 Client 的内部类 , 这里已经开始向 Client 进行过度了
2. 调用 Client 发起查询

整体来说就是通过 Template 实际调用了 org.elasticsearch jar 包 , 从而进行业务处理

image.png

下面主要看一下 jar 包的结构

三 . Client 端部分

Client 端的处理主要集中在org.elasticsearch.client 包中 , 以一个业务中最常见的Search 操作为例 , SpringData 体系会通过 Proxy 代理 , 映射到 elasticsearch Client 中 , 进行正式的处理:

3.1 Client 接收入口

Jar 包中主要通过 Client 进行处理 , 常见的有 :

  • RestHighLevelClient : 高级API , 可以完成很多复杂的查询
  • IndicesClient : 提供一个可用于访问索引 API 的 IndicesClient
  • RestClient : 当前高级客户端实例用于执行请求的低级客户端 , 通常为最底层 API
  • ClusterClient : 可以用于访问集群 Cluster API
  • IngestClient : 可用于访问 Ingest API (Ingest 节点是数据前置处理转换的节点,可以用于数据转换,清洗)
  • SnapshotClient : 数据备份迁移
  • TasksClient : 定时任务处理
  • XPackClient : XPack 框架支持
  • WatcherClient : 预警服务 API
  • GraphClient : 访问 elasticlicensed Graph explore API 的方法
  • LicenseClient : 提供访问 elasticlicensed 许可 api 的方法
  • MigrationClient : 迁移 api
  • MachineLearningClient : 机器学习 api 的方法
  • SecurityClient : 安全认证客户端
  • IndexLifecycleClient : 提供了访问弹性索引生命周期 api 的方法
  • RollupClient : 用于访问汇总作业 API
  • CcrClient : 实现 CCR API 的方法 (CCR 跨集群复制)
  • TransformClient : Transform 数据汇总转储 API 的工具
  • EnrichClient
  • DataFrameClient : 可访问数据框架 API

3.2 发送流程

从配置类中就可以看出来 , ES 的 Client 调用在 初始化 Tempalte 的时候就进行了集成 , 后续 Client 的调用也主要调用 RestHighLevelClient

// 访问主流程 
C1 : RestHighLevelClient # search  进行 Client 高层调用
C2 : RestHighLevelClient # performRequestAndParseEntity : 发起请求
C3 : RestClient # performRequest : 最终 Rest 调用

调用主要关注调用的请求格式 :

补充 : 外部请求类型

请求搜索时默认会先进行 Count ,对于这类请求 , 会直接通过 URL 进行处理

client.execute(context.requestProducer, context.asyncResponseConsumer, context.context, null).get();

// 其中Context 中会有四个核心的属性 : 
- node : 标识当前访问的操作节点
- requestProducer : request 请求体 , 主要格式如下
- asyncResponseConsumer : 异步请求下 , Response 的主要消费者

// Request 请求主要格式 : 
{
  "aborted": false,
  "allHeaders": [],
  "method": "GET", 请求方式
  "protocolVersion": {
    "protocol": "HTTP"
  },
  // 在 URL 中就可以看到查询的主要内容
  "uRI": "/order/_search?pre_filter_shard_size=128&typed_keys=true&max_concurrent_shard_requests=5&ignore_unavailable=false&expand_wildcards=open&allow_no_indices=true&ignore_throttled=true&search_type=dfs_query_then_fetch&batched_reduce_size=512&ccs_minimize_roundtrips=true"
}



补充 : 内部 Body

而实际的查询方式 , 会通过 POST 进行查询处理 , 其 Body 主要承载对象为 QueryStringQueryBuilder , 类似的还有 QueryStringQueryParserSimpleQueryStringQueryParser

{
  "from": 0,
  "size": 1,
  "query": {
    "bool": {
      "must": [
        {
          "query_string": {
            "query": "AntBlack42", // 查询的数据
            "fields": [
              "username^1.0" // 查询的字段 , 这里可以看到携带了版本号
            ],
            "type": "best_fields",
            "default_operator": "and",
            "max_determinized_states": 10000, // 查询总数
            "enable_position_increments": true,
            "fuzziness": "AUTO", // 模糊匹配模式
            "fuzzy_prefix_length": 0,
            "fuzzy_max_expansions": 50,
            "phrase_slop": 0,
            "escape": false,
            "auto_generate_synonyms_phrase_query": true,
            "fuzzy_transpositions": true,
            "boost": 1.0
          }
        }
      ],
      "adjust_pure_negative": true,
      "boost": 1.0
    }
  },
  "version": true
}

四 . 核心类解析

elasticsearch 中的核心 API 包括 RestHighLevelClient 等 ,这里简单过一遍

4.1 RestHighLevelClient

RestHighLevelClient 中包含了很多子 Client 的封装 , 其中主要包括同步和异步2种方式 , 异步使用 ActionListener 的实现作为参数,需要实现处理成功响应和失败场景的方法.

// 同步和异步 : 
M- search (SearchRequest,RequestOptions)
N- searchAsync (SearchRequest,RequestOptions,ActionListener) 



内部方法流程

// S1 : Search 入口
public final SearchResponse search(SearchRequest searchRequest, RequestOptions options) {
    return performRequestAndParseEntity(
                searchRequest,  // 请求 Request 体 , 会包含请求参数 ,如上文
                r -> RequestConverters.search(r, "_search"), // 将 SearchRequest 转换为 Request
                options,SearchResponse::fromXContent,emptySet());
}


// S2 : 透传和数据校验
- 通过 performRequestAndParseEntity 透传到 performRequest
- 通过 request.validate() 对 Request 中的参数进行校验


// S3 : internalPerformRequest 发起实际请求
1. 发起实际调用 -> client.performRequest(req)  -> S4
2. responseConverter 进行转换 -> responseConverter.apply(response)


// S4 : 此处就是调用 RestClient 或者其他的子Client 进行实际的 API 处理了

4.2 RestClient

相比于其他的 Client , RestClient 是非常底层的核心之一 , 它通过 HTTP 连接到 Elasticsearch 集群的客户端

// 核心方法 : 
M- performRequest : 向集群发送请求 , 在其 API 文档中 , 主要描述了以下特性
    - 以循环方式从提供的主机中选择
    - 故障主机会被标记为死机 , 并且在一定时间内重试
    - 请求可以同步 , 也可以异步
    - 可以通过跟踪持续跟踪请求
    
    
// PS001 : 循环选择
RequestContext context = request.createContextForNextAttempt(nodeTuple.nodes.next(), nodeTuple.authCache);
    ?- 在创建 RequestContext 时 , 会通过 nodeTuple.nodes.next() 获取下一个节点
    
    
// PS002 : 故障标记 
> 存储方式 : 
    - private final ConcurrentMap<HttpHost, DeadHostState> blacklist = new ConcurrentHashMap<>();
> onFailure 时写入 :
    - blacklist.putIfAbsent(node.getHost(), new DeadHostState(DeadHostState.DEFAULT_TIME_SUPPLIER));
    ?- 此处主要是 catch 中调用 onFailure
> selectNodes 时选择 : 
    - List<Node> livingNodes = new ArrayList<>(Math.max(0, nodeTuple.nodes.size() - blacklist.size()));
    ?- 先创建可用集合 , 然后 for 循环中进行比对
    

总结

简单学习一下 ElasticSearch 提供的客户端 , 有助于在业务场景中进行更多复杂的操作 .

ES Client 的相关梳理以流程为主 , 没有刻意的去学习设计 , 后续 ES 也会主要集中在使用上.

可以了解到 , ES Client 最终都是通过 Rest 请求进行访问 ,如果业务上出现相关代码使用困惑 ,完全可以通过 RestClient # performRequest 进行 Debug , 了解具体代码存在的问题