Solr/SolrCloud SearchHandler詳解

由於SearchHandler過程比較複雜,基本每次用到都需要重新看一眼才能記憶,而且還比較容易記錯。因此索引把寫博文一來方便自己,二來方便別人。

當然,對Solr來講SearchHandler並不複雜,而且十分簡潔和清晰。不過,在SolrCloud下由於需要節點的協助,所以變得有些複雜。同時SolrCloud下的設計非常巧妙,非常值得細讀和參考。
先看一眼Solr下SearchHandler流程。
這裏寫圖片描述

主幹流程就是這樣,比較清晰簡單。當然後細節依然比較複雜,主要原因Solr在組織代碼的時候並沒有單機與分佈式的邏輯分開來。也就是單機與分佈式的代碼是堆一起的,這只是其一;其次是因爲處理搜索的處理器QueryComponent邏輯也非常複雜。對了解SearchHandler而言,我覺得上面的流程就可以。對於查詢語法的解析,搜索邏輯以及搜索過程都出現QueryComponent中,另找機會介紹這一塊,本文不詳述這塊內容。

SearchHandler#inform(SolrCore core) {
    Object declaredComponents = initArgs.get(INIT_COMPONENTS);
    List<String> first = (List<String>) initArgs.get(INIT_FIRST_COMPONEMTS);
    List<String> last = (List<String>) initArgs.get(INIT_LAST_COMPONENTS);

    // TODO 不堆代碼了,主要目的是根據 Handler 的配置,構建 **components** 鏈。
    // 順序是 first -> defaultComps -> last 。記住此順序有用的。
}

SearchHandler#handleRequestBody(SolrQueryRequest req, SolrQueryRespones rsp) {
    ResponseBuilder rb = new ResponseBuilder(req, rsp, components);

    for( SearchComponent c : components) {
        c.prepare(rb);
    }

    for( SearchComponent c : components) {
        c.prepare(rb);
    }
}

這裏寫圖片描述

即是SeachComponent正常情況下可以分成三部分,前後兩可以爲空,但中間一定不爲空。而且中間部分大多數情況下都是默認提供的搜索組件。前部分預處理,後部分結果轉換,所以這兩部分都可以爲空。

這裏寫圖片描述

ResponseBuilder
這東西慣穿整個搜索過程,充當上下文環境的角色,她的意義在於她使得Handler變成一個單例多線程安全。即是相關變量存儲在她身上。

接到外部請求的Shard稱主節點,其它們所有的Shard(含主節點)稱爲從節點

OK, 接下來聊聊分佈式下搜索請求流程。在此之前我一直在想用方式表達呢,時序圖也不太適合。不過,還是用了時序圖了。首先,你得知道SolrCloud搜索是分步進行的,每一步都是一個狀態。然後極簡單的說,當請求到主節點吧。一個請求到主節點之後,主節點分步異步向所有節點(含主節點)發一個子請求,此時同單機搜索請求過程。
這裏寫圖片描述

主節點跟從節點用的同一份代碼,由各種各樣的狀態碼和標記,走着兩條完全不一樣的路徑而已。對SearchComponent而言,從節點永遠永遠只走prepare()process()

在此之前先看幾個東西,
ShardHandler : 給先安一個高大上的名字吧,就叫分佈式執行引擎。
ShardRequest : 子請求
ShardResponse : 子響應
以上所有命名都是我亂取。

原本想畫個活動圖來說明Master-Slave各自做了些什麼,節點比較多就不畫,簡單敘述即可。
1.主節點跟單機搜索請求,對所有SearchComponent進行預處理。
2.主節點通過SearchComponent.distributedProcess()構建一個子請求,並聲明爲進行詞條統計後放到ResponseBuilder。即在請求參數中把請求聲明爲Term_Stats。
3.主節點通過ShardHanlder讓所有的子節點進行任務,當時所有子節點接到的子請求跟主節點的請求基本一致,只是被標記爲某個分步以及非分佈式請求而已,所有子節點的都先走c.prepare(), 然後走c.process()
4.主節點收集所有異步請求的響應結果,並進行處理。同樣通過SearchComponents進行,這主要考慮的SearchHandler的可以擴展性,即加一個處理只有修改,或者添加一個SearchComponents便能輕鬆實現。

5.跟2差不多,構建一個子請求,標記爲 GET_TOP_IDS。
6.跟3雷同,子節點此事進行真正意義上的檢索,然後根據條件取TopK個文檔返回。文檔一般只含兩個字段,ID和Score。
7.此時收集所有結果,對結果集取TopK。

8.跟2相似,構建一個子請求,標記爲 GET_FIELD。此時向TopK的ID每在節點發送請求。
9.主節點發出請求,對應的子節點進行load迴文檔的全部字段返回。
10.收集所有結果,對結果集排序。

11.返回client。

SearchHandler的主要邏輯,其實應該配合QueryComponent的。不過那樣的就更復雜,所以就SearchHandler和QueryComponent分開來看。記住一點,SearchHandler依賴QueryComponent的狀態,也就說是SearchHanlder分步查詢是由QueryComponent(SearchComponent們)決定的。

SearchHandler#handleRequestBody() { 
    ResponseBuilder rb = new ResponseBuilder();
    for( SearchComponent c : components ) {
        c.prepare(rb);
    }
    int nextStage = 0;
    do {
        for( SearchComponent c : components )
            nextStage = c.distributedProcess(rb);

        while(rb.outgoing.size() > 0) {
            while(rb.outgoing.size() > 0) {
                for(String shard : sreq.actualShards)
                shardHanlder.submit(sreq, shard, params)
            }

            while(rb.outgoing.size() > 0) {
                rb.finished.add(srsp.getShardRequest());

                for( SearchComponent c : components )
                    c.handleResponses(rb, srsp.getShardRequest());
            }       
        } 
        for( SearchComponent c : components )
            c.finishStage(rb);
    } while(nextStage != Integer.MAX_VALUE);
}

一個簡單分佈式搜索請求分三個步走,
1. GET_TERM_STATS : Term統計
2. GET_TOP_IDS : TopK文檔編號以及評分
3. GET_FIELDS : 取文檔編號的所有字段

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章