ElasticSearch Rest/RPC 接口解析

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)了:

  1. 存儲(chǔ)了 Method + Path -> Controller 的關(guān)系
  2. 提供了注冊(cè)關(guān)系的方法
  3. 執(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è)步驟:

  1. query
  2. fetch
  3. 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)系垮媒。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市航棱,隨后出現(xiàn)的幾起案子睡雇,更是在濱河造成了極大的恐慌,老刑警劉巖饮醇,帶你破解...
    沈念sama閱讀 222,464評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件入桂,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡驳阎,警方通過查閱死者的電腦和手機(jī)抗愁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,033評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門馁蒂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人蜘腌,你說我怎么就攤上這事沫屡。” “怎么了撮珠?”我有些...
    開封第一講書人閱讀 169,078評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵沮脖,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我芯急,道長(zhǎng)勺届,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,979評(píng)論 1 299
  • 正文 為了忘掉前任娶耍,我火速辦了婚禮免姿,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘榕酒。我一直安慰自己胚膊,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,001評(píng)論 6 398
  • 文/花漫 我一把揭開白布想鹰。 她就那樣靜靜地躺著紊婉,像睡著了一般。 火紅的嫁衣襯著肌膚如雪辑舷。 梳的紋絲不亂的頭發(fā)上喻犁,一...
    開封第一講書人閱讀 52,584評(píng)論 1 312
  • 那天,我揣著相機(jī)與錄音何缓,去河邊找鬼肢础。 笑死,一個(gè)胖子當(dāng)著我的面吹牛歌殃,可吹牛的內(nèi)容都是我干的乔妈。 我是一名探鬼主播蝙云,決...
    沈念sama閱讀 41,085評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼氓皱,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了勃刨?” 一聲冷哼從身側(cè)響起波材,我...
    開封第一講書人閱讀 40,023評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎身隐,沒想到半個(gè)月后廷区,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,555評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡贾铝,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,626評(píng)論 3 342
  • 正文 我和宋清朗相戀三年隙轻,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了埠帕。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,769評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡玖绿,死狀恐怖敛瓷,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情斑匪,我是刑警寧澤呐籽,帶...
    沈念sama閱讀 36,439評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站蚀瘸,受9級(jí)特大地震影響狡蝶,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜贮勃,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,115評(píng)論 3 335
  • 文/蒙蒙 一贪惹、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧衙猪,春花似錦馍乙、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,601評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至棵譬,卻和暖如春显蝌,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背订咸。 一陣腳步聲響...
    開封第一講書人閱讀 33,702評(píng)論 1 274
  • 我被黑心中介騙來泰國(guó)打工曼尊, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人脏嚷。 一個(gè)月前我還...
    沈念sama閱讀 49,191評(píng)論 3 378
  • 正文 我出身青樓骆撇,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親父叙。 傳聞我的和親對(duì)象是個(gè)殘疾皇子神郊,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,781評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容