ElasticSearchRest/RPC介面解析
一些基礎知識
早先ES的HTTP協議支援還是依賴Jetty的,現在不管是Rest還是RPC都是直接基於Netty了。
另外值得一提的是,ES 是使用Google的Guice 進行模組管理,所以瞭解Guice的基本使用方式有助於你瞭解ES的程式碼組織。
ES 的啟動類是 org.elasticsearch.bootstrap.Bootstrap。在這裡進行一些配置和環境初始化後會啟動org.elasticsearch.node.Node。Node 的概念還是蠻重要的,節點的意思,也就是一個ES例項。RPC 和 Http的對應的監聽啟動都由在該類完成。
Node 屬性裡有一個很重要的物件,叫client,型別是 NodeClient,我們知道ES是一個叢集,所以每個Node都需要和其他的Nodes 進行互動,這些互動則依賴於NodeClient來完成。所以這個物件會在大部分物件中傳遞,完成相關的互動。
先簡要說下:
- NettyTransport 對應RPC 協議支援
- NettyHttpServerTransport 則對應HTTP協議支援
Rest 模組解析
首先,NettyHttpServerTransport 會負責進行監聽Http請求。通過配置http.netty.http.blocking_server 你可以選擇是Nio還是傳統的阻塞式服務。預設是NIO。該類在配置pipeline的時候,最後新增了HttpRequestHandler,所以具體的接受到請求後的處理邏輯就由該類來完成了。
pipeline.addLast("handler", requestHandler);
HttpRequestHandler 實現了標準的 messageReceived(ChannelHandlerContext ctx, MessageEvent e) 方法,在該方法中,HttpRequestHandler 會回撥NettyHttpServerTransport.dispatchRequest方法,而該方法會呼叫HttpServerAdapter.dispatchRequest,接著又會呼叫HttpServer.internalDispatchRequest方法(額,好吧,我承認巢狀挺深有點深):
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);
}
這個方法裡我們看到了plugin等被有限處理。最後請求又被轉發給 RestController。
RestController 大概類似一個微型的Controller層框架,實現了:
- 儲存了 Method + Path -> Controller 的關係
- 提供了註冊關係的方法
- 執行Controller的功能。
那麼各個Controller(Action) 是怎麼註冊到RestController中的呢?
在ES中,Rest*Action 命名的類的都是提供http服務的,他們會在RestActionModule 中被初始化,對應的構造方法會注入RestController例項,接著在構造方法中,這些Action會呼叫controller.registerHandler 將自己註冊到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);
每個Rest*Action 都會實現一個handleRequest方法。該方法接入實際的邏輯處理。
@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));
}
首先是會把 請求封裝成一個SearchRequest物件,然後交給 NodeClient 執行。
如果用過ES的NodeClient Java API,你會發現,其實上面這些東西就是為了暴露NodeClient API 的功能,使得你可以通過HTTP的方式呼叫。
Transport*Action,兩層對映關係解析
我們先跑個題,在ES中,Transport*Action 是比較核心的類集合。這裡至少有兩組對映關係。
Action -> Transport*Action
TransportAction -> TransportHandler
第一層對映關係由類似下面的程式碼在ActionModule中完成:
registerAction(PutMappingAction.INSTANCE, TransportPutMappingAction.class);
第二層對映則在類似 SearchServiceTransportAction 中維護。目前看來,第二層對映只有在查詢相關的功能才有,如下:
transportService.registerRequestHandler(FREE_CONTEXT_SCROLL_ACTION_NAME, ScrollFreeContextRequest.class, ThreadPool.Names.SAME, new FreeContextTransportHandler<>());
SearchServiceTransportAction 可以看做是SearchService進一步封裝。其他的Transport*Action 則只呼叫對應的Service 來完成實際的操作。
對應的功能是,可以通過Action 找到對應的TransportAction,這些TransportAction 如果是query類,則會呼叫SearchServiceTransportAction,並且通過第二層對映找到對應的Handler,否則可能就直接通過對應的Service完成操作。
下面關於RPC呼叫解析這塊,我們會以查詢為例。
RPC 模組解析
前面我們提到,Rest介面最後會呼叫NodeClient來完成後續的請求。對應的程式碼為:
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.如果是查詢,則會找到TransportSearchAction。呼叫對應的doExecute 方法,接著根據searchRequest.searchType找到要執行的實際程式碼。下面是預設的:
else if (searchRequest.searchType() == SearchType.QUERY_THEN_FETCH) { queryThenFetchAction.execute(searchRequest, listener);}
我們看到Transport*Action 是可以巢狀的,這裡呼叫了
TransportSearchQueryThenFetchAction.doExecute
@Overrideprotected void doExecute(SearchRequest searchRequest, ActionListener<SearchResponse> listener) {
new AsyncAction(searchRequest, listener).start();
}
在AsyncAction中完成三個步驟:
- query
- fetch
- merge
為了分析方便,我們只分析第一個步驟。
@Overrideprotected void sendExecuteFirstPhase(
DiscoveryNode node,
ShardSearchTransportRequest request,
ActionListener<QuerySearchResultProvider> listener) {
searchService.sendExecuteQuery(node, request, listener);
}
這是AsyncAction 中執行query的程式碼。我們知道ES是一個叢集,所以query 必然要發到多個節點去,如何知道某個索引對應的Shard 所在的節點呢?這個是在AsyncAction的父類中完成,該父類分析完後會回撥子類中的對應的方法來完成,譬如上面的sendExecuteFirstPhase 方法。
說這個是因為需要讓你知道,上面貼出來的程式碼只是針對一個節點的查詢結果,但其實最終多個節點都會通過相同的方式進行呼叫。所以才會有第三個環節 merge操作,合併多個節點返回的結果。
searchService.sendExecuteQuery(node, request, listener);
其實會呼叫transportService的sendRequest方法。大概值得分析的地方有兩個:
if (node.equals(localNode)) {
sendLocalRequest(requestId, action, request);
} else {
transport.sendRequest(node, requestId, action, request, options);
}
我們先分析,如果是本地的節點,則sendLocalRequest是怎麼執行的。如果你跑到senLocalRequest裡去看,很簡單,其實就是:
reg.getHandler().messageReceived(request, channel);
reg 其實就是前面我們提到的第二個對映,不過這個對映其實還包含了使用什麼執行緒池等資訊,我們在前面沒有說明。
這裡 reg.getHandler == SearchServiceTransportAction.SearchQueryTransportHandler,所以messageReceived 方法對應的邏輯是:
QuerySearchResultProvider result = searchService.executeQueryPhase(request);
channel.sendResponse(result);
這裡,我們終於看到searchService。 在searchService裡,就是整兒八景的Lucene相關查詢了。這個我們後面的系列文章會做詳細分析。
如果不是本地節點,則會由NettyTransport.sendRequest 發出遠端請求。假設當前請求的節點是A,被請求的節點是B,則B的入口為MessageChannelHandler.messageReceived。在NettyTransport中你可以看到最後新增的pipeline裡就有MessageChannelHandler。我們跑進去messageReceived 看看,你會發現基本就是一些協議解析,核心方法是handleRequest,接著就和本地差不多了,我提取了關鍵的幾行程式碼:
final RequestHandlerRegistry reg = transportServiceAdapter.getRequestHandler(action);
threadPool.executor(reg.getExecutor()).execute(new RequestHandler(reg, request, transportChannel));
這裡被RequestHandler包了一層,其實內部執行的就是本地的那個。RequestHandler 的run方法是這樣的:
protected void doRun() throws Exception { reg.getHandler().messageReceived(request, transportChannel);
}
這個就和前面的sendLocalRequest裡的一模一樣了。
總結
到目前為止,我們知道整個ES的Rest/RPC 的起點是從哪裡開始的。RPC對應的endpoint 是MessageChannelHandler,在NettyTransport 被註冊。Rest 介面的七點則在NettyHttpServerTransport,經過層層代理,最終在RestController中被執行具體的Action。 Action 的所有執行都會被委託給NodeClient。 NodeClient的功能執行單元是各種Transport*Action。對於查詢類請求,還多了一層對映關係。
相關文章
- RPC介面和http介面的區別RPCHTTP
- RPC呼叫介面設計RPC
- JMeter 測試 thrift RPC 介面JMeterRPC
- 功放AUX介面解析UX
- Java 版抖音解析介面Java
- 騰訊視訊解析介面
- postman(一):主介面模組解析Postman
- Hadoop3.2.1 【 YARN 】原始碼分析 :RPC通訊解析HadoopYarn原始碼RPC
- Thrift RPC 系列教程(5)—— 介面設計篇:struct & enum設計RPCStruct
- [原始碼解析] PyTorch 分散式 Autograd (2) ---- RPC基礎原始碼PyTorch分散式RPC
- iOS介面資料解析問題iOS
- Java RPC 框架 Solon 1.3.1 釋出,推出Cloud介面與配置規範JavaRPC框架Cloud
- Java RPC 框架 Solon 1.3.7 釋出,增強Cloud介面能力範圍JavaRPC框架Cloud
- RPCRPC
- [RPC]RPC
- 介紹一個 EOS 區塊鏈 RPC API 介面的 PHP SDK 包區塊鏈RPCAPIPHP
- 【高併發】深入解析Callable介面
- AIDL 跨程式呼叫 -- 介面層解析AI
- Android ListView 介面卡模式解析AndroidView模式
- 路由器網路介面解析大全路由器
- Java程式設計架構深入解析-RPC訊息協議設計Java程式設計架構RPC協議
- 好程式設計師Python培訓分享Python如何呼叫RPC介面程式設計師PythonRPC
- Eolink Apikit「 零程式碼」快速發起 RPC 介面自動化測試APIRPC
- 介面自動化測試:Thrift框架RPC協議客戶端開發框架RPC協議客戶端
- mybatis原始碼解析-日誌介面卡MyBatis原始碼
- 微信對賬單介面返回值解析
- Go RpcGoRPC
- PoS RPCRPC
- 生成 rpcRPC
- [原始碼解析] PyTorch 分散式(18) --- 使用 RPC 的分散式管道並行原始碼PyTorch分散式RPC並行
- [原始碼解析] PyTorch 分散式(17) --- 結合DDP和分散式 RPC 框架原始碼PyTorch分散式RPC框架
- 什麼樣的RPC才是好用的RPCRPC
- .NET輕量級RPC框架:Rabbit.RpcRPC框架
- .Net 8.0 下的新RPC,IceRPC之介面定義語言 [Slice] VS [Protobuf]RPC
- 結合案例深入解析介面卡模式(二)模式
- golang RPC 應用(1) :net/rpc的應用GolangRPC
- 螞蟻 RPC 框架 SOFA-RPC 初體驗RPC框架
- RPC的概述RPC