ElasticSearch 的體系結(jié)構(gòu)比較復(fù)雜侈净,層次也比較深,源碼注釋相比其他的開源項(xiàng)目要少僧凤。這是ElasticSearch 系列的第一篇畜侦。解析ElasticSearch的接口層,也就是Rest/RPC接口相關(guān)躯保。我們會(huì)描述一個(gè)請(qǐng)求從http接口到最后被處理都經(jīng)過了哪些環(huán)節(jié)旋膳。
一些基礎(chǔ)知識(shí)
早先ES的HTTP協(xié)議支持還是依賴Jetty的,現(xiàn)在不管是Rest還是RPC都是直接基于Netty了。
另外值得一提的是途事,ES 是使用Google的Guice 進(jìn)行模塊管理验懊,所以了解Guice的基本使用方式有助于你了解ES的代碼組織。
ES 的啟動(dòng)類是 org.elasticsearch.bootstrap.Bootstrap
尸变。在這里進(jìn)行一些配置和環(huán)境初始化后會(huì)啟動(dòng)org.elasticsearch.node.Node
义图。Node 的概念還是蠻重要的,節(jié)點(diǎn)的意思召烂,也就是一個(gè)ES實(shí)例碱工。RPC 和 Http的對(duì)應(yīng)的監(jiān)聽啟動(dòng)都由在該類完成。
Node 屬性里有一個(gè)很重要的對(duì)象,叫client,類型是 NodeClient,我們知道ES是一個(gè)集群痛垛,所以每個(gè)Node都需要和其他的Nodes 進(jìn)行交互草慧,這些交互則依賴于NodeClient來完成。所以這個(gè)對(duì)象會(huì)在大部分對(duì)象中傳遞匙头,完成相關(guān)的交互漫谷。
先簡(jiǎn)要說下:
- NettyTransport 對(duì)應(yīng)RPC 協(xié)議支持
- NettyHttpServerTransport 則對(duì)應(yīng)HTTP協(xié)議支持
Rest 模塊解析
首先,NettyHttpServerTransport 會(huì)負(fù)責(zé)進(jìn)行監(jiān)聽Http請(qǐng)求蹂析。通過配置http.netty.http.blocking_server
你可以選擇是Nio還是傳統(tǒng)的阻塞式服務(wù)舔示。默認(rèn)是NIO。該類在配置pipeline的時(shí)候电抚,最后添加了HttpRequestHandler惕稻,所以具體的接受到請(qǐng)求后的處理邏輯就由該類來完成了。
pipeline.addLast("handler", requestHandler);
HttpRequestHandler 實(shí)現(xiàn)了標(biāo)準(zhǔn)的 messageReceived(ChannelHandlerContext ctx, MessageEvent e)
方法蝙叛,在該方法中俺祠,HttpRequestHandler 會(huì)回調(diào)NettyHttpServerTransport.dispatchRequest
方法,而該方法會(huì)調(diào)用HttpServerAdapter.dispatchRequest
,接著又會(huì)調(diào)用HttpServer.internalDispatchRequest
方法(額借帘,好吧蜘渣,我承認(rèn)嵌套挺深有點(diǎn)深):
public void internalDispatchRequest(final HttpRequest request, final HttpChannel channel) {
String rawPath = request.rawPath();
if (rawPath.startsWith("/_plugin/")) {
RestFilterChain filterChain = restController.filterChain(pluginSiteFilter);
filterChain.continueProcessing(request, channel);
return;
} else if (rawPath.equals("/favicon.ico")) {
handleFavicon(request, channel);
return;
}
restController.dispatchRequest(request, channel);
}
這個(gè)方法里我們看到了plugin等被有限處理。最后請(qǐng)求又被轉(zhuǎn)發(fā)給 RestController肺然。
RestController 大概類似一個(gè)微型的Controller層框架蔫缸,實(shí)現(xiàn)了:
- 存儲(chǔ)了 Method + Path -> Controller 的關(guān)系
- 提供了注冊(cè)關(guān)系的方法
- 執(zhí)行Controller的功能。
那么各個(gè)Controller(Action) 是怎么注冊(cè)到RestController中的呢际起?
在ES中拾碌,Rest*Action 命名的類的都是提供http服務(wù)的,他們會(huì)在RestActionModule 中被初始化街望,對(duì)應(yīng)的構(gòu)造方法會(huì)注入RestController實(shí)例校翔,接著在構(gòu)造方法中,這些Action會(huì)調(diào)用controller.registerHandler
將自己注冊(cè)到RestController灾前。典型的樣子是這樣的:
@Inject
public RestSearchAction(Settings settings, RestController controller, Client client) {
super(settings, controller, client);
controller.registerHandler(GET, "/_search", this);
controller.registerHandler(POST, "/_search", this);
controller.registerHandler(GET, "/{index}/_search", this);
每個(gè)Rest*Action 都會(huì)實(shí)現(xiàn)一個(gè)handleRequest方法展融。該方法接入實(shí)際的邏輯處理。
@Override
public void handleRequest(final RestRequest request, final RestChannel channel, final Client client) {
SearchRequest searchRequest;
searchRequest = RestSearchAction.parseSearchRequest(request, parseFieldMatcher);
client.search(searchRequest, new RestStatusToXContentListener<SearchResponse>(channel));
}
首先是會(huì)把 請(qǐng)求封裝成一個(gè)SearchRequest對(duì)象豫柬,然后交給 NodeClient 執(zhí)行告希。
如果用過ES的NodeClient Java API,你會(huì)發(fā)現(xiàn)烧给,其實(shí)上面這些東西就是為了暴露NodeClient API 的功能燕偶,使得你可以通過HTTP的方式調(diào)用。
Transport*Action础嫡,兩層映射關(guān)系解析
我們先跑個(gè)題指么,在ES中酝惧,Transport*Action 是比較核心的類集合。這里至少有兩組映射關(guān)系伯诬。
- Action -> Transport*Action
- Transport*Action -> *TransportHandler
第一層映射關(guān)系由類似下面的代碼在ActionModule中完成:
registerAction(PutMappingAction.INSTANCE, TransportPutMappingAction.class);
第二層映射則在類似 SearchServiceTransportAction 中維護(hù)晚唇。目前看來,第二層映射只有在查詢相關(guān)的功能才有盗似,如下:
transportService.registerRequestHandler(FREE_CONTEXT_SCROLL_ACTION_NAME, ScrollFreeContextRequest.class, ThreadPool.Names.SAME, new FreeContextTransportHandler<>());
SearchServiceTransportAction 可以看做是SearchService進(jìn)一步封裝哩陕。其他的Transport*Action 則只調(diào)用對(duì)應(yīng)的Service 來完成實(shí)際的操作。
對(duì)應(yīng)的功能是赫舒,可以通過Action 找到對(duì)應(yīng)的TransportAction悍及,這些TransportAction 如果是query類,則會(huì)調(diào)用SearchServiceTransportAction,并且通過第二層映射找到對(duì)應(yīng)的Handler,否則可能就直接通過對(duì)應(yīng)的Service完成操作接癌。
下面關(guān)于RPC調(diào)用解析這塊心赶,我們會(huì)以查詢?yōu)槔?/p>
RPC 模塊解析
前面我們提到,Rest接口最后會(huì)調(diào)用NodeClient來完成后續(xù)的請(qǐng)求缺猛。對(duì)應(yīng)的代碼為:
public <Request extends ActionRequest, Response extends ActionResponse, RequestBuilder extends ActionRequestBuilder<Request, Response, RequestBuilder>> void doExecute(Action<Request, Response, RequestBuilder> action, Request request, ActionListener<Response> listener) {
TransportAction<Request, Response> transportAction = actions.get(action);
if (transportAction == null) {
throw new IllegalStateException("failed to find action [" + action + "] to execute");
}
transportAction.execute(request, listener);
}
這里的action 就是我們提到的第一層映射缨叫,找到Transport*Action.如果是查詢,則會(huì)找到TransportSearchAction荔燎。調(diào)用對(duì)應(yīng)的doExecute 方法耻姥,接著根據(jù)searchRequest.searchType找到要執(zhí)行的實(shí)際代碼。下面是默認(rèn)的:
else if (searchRequest.searchType() == SearchType.QUERY_THEN_FETCH) { queryThenFetchAction.execute(searchRequest, listener);}
我們看到Transport*Action 是可以嵌套的湖雹,這里調(diào)用了TransportSearchQueryThenFetchAction.doExecute
@Overrideprotected void doExecute(SearchRequest searchRequest, ActionListener<SearchResponse> listener) {
new AsyncAction(searchRequest, listener).start();
}
在AsyncAction中完成三個(gè)步驟:
- query
- fetch
- merge
為了分析方便,我們只分析第一個(gè)步驟曙搬。
@Overrideprotected void sendExecuteFirstPhase(
DiscoveryNode node,
ShardSearchTransportRequest request,
ActionListener<QuerySearchResultProvider> listener) {
searchService.sendExecuteQuery(node, request, listener);
}
這是AsyncAction 中執(zhí)行query的代碼摔吏。我們知道ES是一個(gè)集群,所以query 必然要發(fā)到多個(gè)節(jié)點(diǎn)去纵装,如何知道某個(gè)索引對(duì)應(yīng)的Shard 所在的節(jié)點(diǎn)呢征讲?這個(gè)是在AsyncAction的父類中完成,該父類分析完后會(huì)回調(diào)子類中的對(duì)應(yīng)的方法來完成橡娄,譬如上面的sendExecuteFirstPhase 方法诗箍。
說這個(gè)是因?yàn)樾枰屇阒溃厦尜N出來的代碼只是針對(duì)一個(gè)節(jié)點(diǎn)的查詢結(jié)果挽唉,但其實(shí)最終多個(gè)節(jié)點(diǎn)都會(huì)通過相同的方式進(jìn)行調(diào)用滤祖。所以才會(huì)有第三個(gè)環(huán)節(jié) merge操作,合并多個(gè)節(jié)點(diǎn)返回的結(jié)果瓶籽。
searchService.sendExecuteQuery(node, request, listener);
其實(shí)會(huì)調(diào)用transportService的sendRequest方法匠童。大概值得分析的地方有兩個(gè):
if (node.equals(localNode)) {
sendLocalRequest(requestId, action, request);
} else {
transport.sendRequest(node, requestId, action, request, options);
}
我們先分析,如果是本地的節(jié)點(diǎn)塑顺,則sendLocalRequest是怎么執(zhí)行的汤求。如果你跑到senLocalRequest里去看俏险,很簡(jiǎn)單,其實(shí)就是:
reg.getHandler().messageReceived(request, channel);
reg 其實(shí)就是前面我們提到的第二個(gè)映射扬绪,不過這個(gè)映射其實(shí)還包含了使用什么線程池等信息竖独,我們?cè)谇懊鏇]有說明。
這里 reg.getHandler == SearchServiceTransportAction.SearchQueryTransportHandler,所以messageReceived 方法對(duì)應(yīng)的邏輯是:
QuerySearchResultProvider result = searchService.executeQueryPhase(request);
channel.sendResponse(result);
這里挤牛,我們終于看到searchService莹痢。 在searchService里,就是整兒八景的Lucene相關(guān)查詢了赊颠。這個(gè)我們后面的系列文章會(huì)做詳細(xì)分析格二。
如果不是本地節(jié)點(diǎn),則會(huì)由NettyTransport.sendRequest 發(fā)出遠(yuǎn)程請(qǐng)求竣蹦。
假設(shè)當(dāng)前請(qǐng)求的節(jié)點(diǎn)是A,被請(qǐng)求的節(jié)點(diǎn)是B,則B的入口為MessageChannelHandler.messageReceived顶猜。在NettyTransport中你可以看到最后添加的pipeline里就有MessageChannelHandler。我們跑進(jìn)去messageReceived 看看痘括,你會(huì)發(fā)現(xiàn)基本就是一些協(xié)議解析长窄,核心方法是handleRequest,接著就和本地差不多了纲菌,我提取了關(guān)鍵的幾行代碼:
final RequestHandlerRegistry reg = transportServiceAdapter.getRequestHandler(action);
threadPool.executor(reg.getExecutor()).execute(new RequestHandler(reg, request, transportChannel));
這里被RequestHandler包了一層挠日,其實(shí)內(nèi)部執(zhí)行的就是本地的那個(gè)。RequestHandler 的run方法是這樣的:
protected void doRun() throws Exception { reg.getHandler().messageReceived(request, transportChannel);
}
這個(gè)就和前面的sendLocalRequest里的一模一樣了翰舌。
總結(jié)
到目前為止嚣潜,我們知道整個(gè)ES的Rest/RPC 的起點(diǎn)是從哪里開始的。RPC對(duì)應(yīng)的endpoint 是MessageChannelHandler椅贱,在NettyTransport 被注冊(cè)懂算。Rest 接口的七點(diǎn)則在NettyHttpServerTransport,經(jīng)過層層代理,最終在RestController中被執(zhí)行具體的Action庇麦。 Action 的所有執(zhí)行都會(huì)被委托給NodeClient计技。 NodeClient的功能執(zhí)行單元是各種Transport*Action。對(duì)于查詢類請(qǐng)求山橄,還多了一層映射關(guān)系垮媒。