Elasticsearch8.5.3 源码分析(1)-启动过程及Rest请求处理

1,643 阅读4分钟

下一篇:Elasticsearch8.5.3 源码分析(2) - 掘金 (juejin.cn)

启动初始化配置过程

CliToolLauncher.main启动集群

分析elasticsearch.bat启动脚本,可知应用启动类为:org.elasticsearch.launcher.CliToolLauncher

%JAVA% ^
  %CLI_JAVA_OPTS% ^
  -Dcli.name="%CLI_NAME%" ^
  -Dcli.script="%CLI_SCRIPT%" ^
  -Dcli.libs="%CLI_LIBS%" ^
  -Des.path.home="%ES_HOME%" ^
  -Des.path.conf="%ES_PATH_CONF%" ^
  -Des.distribution.type="%ES_DISTRIBUTION_TYPE%" ^
  -cp "%LAUNCHER_CLASSPATH%" ^
  org.elasticsearch.launcher.CliToolLauncher ^
  %*

Elasticsearch.main启动节点

CliToolLauncher调用org.elasticsearch.bootstrap.Elasticsearch类的Main方法启动Elasticsearch集群中各个节点服务。

image.png

三阶段初始化过程

初始化过程分为三个阶段,分别对应initPhase1(),initPhase2()和initPhase3()。、

  • 阶段一:从CLI进程读取配置和参数,初始化日志记录。
  • 阶段二:初始化安全管理器
  • 阶段三:构建节点组件并启动,同时通知父 CLI 进程系统已准备就绪
image.png

阶段三初始化节点时,Node组件调用ActionModule.initRestHandlers方法注册所有Rest API处理类,类名全部以Action结尾(XxxAction).如下图所示:

image.png

此图并未截全,实际下面还有好大一堆注册代码将org.elasticsearch.rest.action包下所有的Action都进行了注册。

image.png

RestHandler有两种类型的实现:

  • SecurityRestFilter(安全管理):直接实现RestHandler接口,并负责代理所有的Action,统一管理安全认证逻辑。
  • 各种Action(业务处理):继承BaseRestHandler。BaseRestHandler实现RestHandler接口,使用模板方法设计模式处理公共逻辑,并定义了需要子类扩展的接口。
image.png

每个Action类里都定义了其负责处理的Route,相同url不同http method也视为不同的Route,如RestGetAction:

image.png

注册后的RestHandler用SecurityRestFilter类包装代理,然后构建成TrieNode实例,按路径层次保存为树形结构。

RestHandler存储结构

下面简单分析一下TrieNode树结构:

根节点为"/"

第一层节点如下图(截取部份):

image.png

以 /{index}/_doc/{id} 为例,第二层匹配的是路径参数({index}),所以第二层节点匹配"*"。

image.png

可以看到/{index}下面还有63个子节点,展开子节点找到_doc

image.png

最后{id}再用"*"匹配。匹配到最后一个路径节点后,下面有一个名为methodHandlers的HashMap,里面包含了针对不同HttpMethod的进行处理的RestHandler.点开"Get",可找到对应的RestHandler为RestGetAction类。

Rest API 处理过程

核心交互过程

Elasticsearch底层基于Netty框架进行通信。

搜索ServerBootstrap,可知ServerBootstrap的配置及监听代码在Netty4HttpServerTransport中。 通过查找serverBootstrap.childHandler方法,可知处理Tcp请求的handler为Netty4HttpPipeliningHandler。所以从Netty4HttpPipeliningHandler.channelRead方法开始一步一步分析核心交互流程:

image.png

核心流程如下:

  • Netty监听到TCP事件,将读写事件转发给Netty4HttpPipeliningHandler处理。
  • Netty4HttpPipeliningHandler从Netty上下文获取Netty4HttpChannel对象,做为参数转调用Netty4HttpServerTransport.incomingRequest(pipelinedRequest, channel)方法;
  • Netty4HttpServerTransport将HttpRequest包装成RestRequest,HttpChannel包装成RestChannel后转发RestController处理。
  • RestController在tryAllHandlers方法中,遍历寻找Request URL匹配的RestHandler实现类-XxxAction,再调用此Action的handleRequest方法。Action类被SecurityRestFilter安全代理。每个Action提供独立的函数式接口RestChannelConsumer的实现。
  • Action最终调用函数式接口RestChannelConsumer的accept方法处理业务。

整个调用链路Channel都是透传的,任何一个环节产生异常,都可以通过Channel写入异常消息(Response)返回给客户端。

SecurityRestFilter安全代理

public void handleRequest(RestRequest request, RestChannel channel, NodeClient client) throws Exception {
    if (request.method() == Method.OPTIONS) {
        // CORS - 允许未经安全认证的跨域预检请求
        restHandler.handleRequest(request, channel, client);
        return;
    }

    //只有xpack.security.enabled配置项为true,才会进行身份认证。
    if (XPackSettings.SECURITY_ENABLED.get(settings)) {
        // xpack.security.http.ssl为true时,需要提取客户端证书
        if (extractClientCertificate) {
            HttpChannel httpChannel = request.getHttpChannel();
            SSLEngineUtils.extractClientCertificates(logger, threadContext, httpChannel);
        }

        final String requestUri = request.uri();
        // 标准化身份验证 
        // header使用 Authorization: Basic <TOKEN>
        // TOKEN=base64(USERNAME:PASSWORD)
        // 由于Elasticsearch是无状态的,因此每个请求都必须发送此header
        authenticationService.authenticate(maybeWrapRestRequest(request), ActionListener.wrap(authentication -> {
            if (authentication == null) {
                logger.trace("No authentication available for REST request [{}]", requestUri);
            } else {
                logger.trace("Authenticated REST request [{}] as {}", requestUri, authentication);
            }
            // 某些Rest API支持辅助授权,使用es-secondary-authorization做为http header 
            // es-secondary-authorization: Basic <TOKEN> TOKEN=base64(USERNAME:PASSWORD)
            // es-secondary-authorization: ApiKey <TOKEN> TOKEN=base64(API key ID:API key)
            secondaryAuthenticator.authenticateAndAttachToContext(request, ActionListener.wrap(secondaryAuthentication -> {
                if (secondaryAuthentication != null) {
                    logger.trace("Found secondary authentication {} in REST request [{}]", secondaryAuthentication, requestUri);
                }
                RemoteHostHeader.process(request, threadContext);
                try {
                    //身份验证通过,执行代理的restHandler处理rest请求
                    restHandler.handleRequest(request, channel, client);
                } catch (Exception e) {
                    handleException(ActionType.RequestHandling, request, channel, e);
                }
            }, e -> handleException(ActionType.SecondaryAuthentication, request, channel, e)));
        }, e -> handleException(ActionType.Authentication, request, channel, e)));
    } else {
        //不需要身份验证,直接执行代理的restHandler处理rest请求
        restHandler.handleRequest(request, channel, client);
    }
}

elasticsearch.yml开启了身份验证时,Elasticsearch会对Rest API请求进行身份验证。

xpack.security.enabled: true

Elasticsearch使用标准HTTP basic authentication header对用户进行身份验证。参考官方文档

Authorization: Basic  <TOKEN>

TOKEN=base64(USERNAME:PASSWORD)

也可以使用基于令牌的身份验证,使用以下http header

API令牌:

Authorization: ApiKey <TOKEN> 

TOKEN=base64(API key ID:API key)

授权令牌:

Authorization: Bearer <TOKEN>

同时某些Rest API支持辅助授权,使用es-secondary-authorization做为http header,语法和Authorization类似。

es-secondary-authorization: Basic <TOKEN> 

TOKEN=base64(USERNAME:PASSWORD)

es-secondary-authorization: ApiKey <TOKEN> 

TOKEN=base64(API key ID:API key)

Action的RestChannelConsumer实现

RestUpdateAction的RestChannelConsumer实现:

return channel -> client.update(
    updateRequest,
    new RestStatusToXContentListener<>(channel, r -> r.getLocation(updateRequest.routing()))
);

RestGetAction的RestChannelConsumer实现:

return channel -> client.get(getRequest, new RestToXContentListener<GetResponse>(channel) {
    @Override
    protected RestStatus getStatus(final GetResponse response) {
        return response.isExists() ? OK : NOT_FOUND;
    }
});

RestDeleteAction的RestChannelConsumer实现:

return channel -> client.delete(deleteRequest, new RestStatusToXContentListener<>(channel));

由以上三个实现类可以看到每个Action的RestChannelConsumer实现,最终都是调用NodeClient类相关的接口处理,同时Channel将做为参数传递,以便后续向客户端发送处理结果(Response)。

NodeClient后续会将请求转发给TransportAction处理。TransportAction负责集群内部网络传输调用。下一篇继续分析TransportAction处理流程。