下一篇: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集群中各个节点服务。
三阶段初始化过程
初始化过程分为三个阶段,分别对应initPhase1(),initPhase2()和initPhase3()。、
- 阶段一:从CLI进程读取配置和参数,初始化日志记录。
- 阶段二:初始化安全管理器
- 阶段三:构建节点组件并启动,同时通知父 CLI 进程系统已准备就绪
阶段三初始化节点时,Node组件调用ActionModule.initRestHandlers方法注册所有Rest API处理类,类名全部以Action结尾(XxxAction).如下图所示:
此图并未截全,实际下面还有好大一堆注册代码将org.elasticsearch.rest.action包下所有的Action都进行了注册。
RestHandler有两种类型的实现:
- SecurityRestFilter(安全管理):直接实现RestHandler接口,并负责代理所有的Action,统一管理安全认证逻辑。
- 各种Action(业务处理):继承BaseRestHandler。BaseRestHandler实现RestHandler接口,使用模板方法设计模式处理公共逻辑,并定义了需要子类扩展的接口。
每个Action类里都定义了其负责处理的Route,相同url不同http method也视为不同的Route,如RestGetAction:
注册后的RestHandler用SecurityRestFilter类包装代理,然后构建成TrieNode实例,按路径层次保存为树形结构。
RestHandler存储结构
下面简单分析一下TrieNode树结构:
根节点为"/"
第一层节点如下图(截取部份):
以 /{index}/_doc/{id} 为例,第二层匹配的是路径参数({index}),所以第二层节点匹配"*"。
可以看到/{index}下面还有63个子节点,展开子节点找到_doc
最后{id}再用"*"匹配。匹配到最后一个路径节点后,下面有一个名为methodHandlers的HashMap,里面包含了针对不同HttpMethod的进行处理的RestHandler.点开"Get",可找到对应的RestHandler为RestGetAction类。
Rest API 处理过程
核心交互过程
Elasticsearch底层基于Netty框架进行通信。
搜索ServerBootstrap,可知ServerBootstrap的配置及监听代码在Netty4HttpServerTransport中。
通过查找serverBootstrap.childHandler方法,可知处理Tcp请求的handler为Netty4HttpPipeliningHandler。所以从Netty4HttpPipeliningHandler.channelRead方法开始一步一步分析核心交互流程:
核心流程如下:
- 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处理流程。