[11]elasticsearch原始碼深入分析——文件(document)的落地

飛來來發表於2018-02-26

本篇為elasticsearch原始碼分析系列文章的第十一篇,本篇開始進入索引有關操作的講解。以後的若干篇我們會連續討論文件的建立,檢索,更新,刪除,版本控制等一系列內容。

文件

ElasticSearch儲存系統中的實體叫做文件,document。如果用關係型資料庫來類比的話,一個文件相當於資料庫中的一行記錄。ElasticSearch中的文件有個特點,相同欄位必須是相同的型別,也就是說所有包含title欄位的文件,title欄位型別都必須一樣,要麼同為string,要麼同為int。

文件由多個欄位組成,每個欄位的型別可以是,文字,數值,日期,還可以是字串陣列這種複雜的型別。欄位型別在ElasticSearch中非常重要,它涉及到各種分析和排序操作如何被執行的資訊。Elastic官方推薦我們使用Mapping對映來干預欄位的型別。與關係型資料庫不同,ElasticSearch不需要有固定的結構,每個文件可以有不同的欄位,此外,在程式開發期間,不必確定有哪些欄位。

文件的型別

在ElasticSearch中,文件型別可以讓程式設計師輕鬆的區分單個索引中的不同物件。每個文件可以有不同的結構,但在實際生產環境中我們還是推薦將文件中的型別詳細化,這樣對以後的開發會有很大的幫助。

文件型別的對映

上面提到的對映,指的是ElasticSearch在對映中儲存有關欄位的資訊,這種型別資訊就是對映Mapping。每個文件型別都有自己的對映,即使在初始化時沒有提前定義。在涉及到全文搜尋和倒排索引的內容中,會有對文件分析的過程,在這個過程中每個欄位都必須根據不同型別作相應的分析。舉例來說,對數值欄位和文字欄位的分析肯定是不同的分析過程,數字的分析就不應該是按照字母的排序來分析。

使用ElasticSearch的ResultAPI來新建文件

在ElasticSearch中,所有文件都是資料,所有資料都有定義好的索引和型別。現在我們通過一個比較常見的例子來建立文件:

建立文件

上面操作的意思是,我們建立了一個名為article的索引和名為computer的型別,文件的標示符為1

如果一切正常,那麼這種使用RESTfulAPI的建立方式會返回一個JSON響應,與如下輸出類似:

文件建立成功

前面的相應包含了操作狀態的資訊,顯示了建立的文件放在什麼地方,還包含了文件的唯一標示符**_id和當前版本_version**的資訊。每次ElasticSearch的更新版本都會自動遞增。

而且ElasticSearch在建立文件時,如果沒指定文件標示符,那麼這個文件的標示符會被自動建立。

自動建立文件標示符

這都是怎麼做到的呢?我們會在下一節從原始碼的角度解釋。

ElasticSearch原始碼如何新建文件

在以前文章中強調的Node例項化的過程中,載入了ActionModule這個模組,這個模組是接收客戶端傳送的RESTful請求的的模組,ActionModule的載入如下:

ActionModule actionModule = new ActionModule(false, settings, clusterModule.getIndexNameExpressionResolver(), settingsModule.getIndexScopedSettings(), settingsModule.getClusterSettings(), settingsModule.getSettingsFilter(), threadPool, pluginsService.filterPlugins(ActionPlugin.class), client, circuitBreakerService, usageService);

在載入完了ActionModule後,會通過ActionModule的方法**initRestHandlers()**來初始化HTTP處理程式,這個handler就能解析客戶端通過http協議傳送到ElasticSearch叢集中的RESTful請求。

載入RestIndexActionindex處理器,

registerHandler.accept(new RestIndexAction(settings, restController))

如下圖所示,註冊不同的REST處理程式路徑,以用來不同的匹配請求。

不同的索引請求

可以看到控制器匹配路徑中,有index,type和id,如果不指定id,則id會被自動建立,而且不指定id必須用POST方法來傳送請求。

因為ElasticSearch中的Controller底層都是Netty實現的。所以在埠繫結後,Netty4HttpChannel會去監聽埠收到的http請求。在ElasticSearch的Controller接收到Netty4HttpChannel轉發的請求後,會呼叫RestIndexAction中的方法prepareRequest()。該方法返回RestChannelConsumer型別的例項,該例項是虛擬類BaseRestHandler中的Functional介面。閱讀這個介面的定義的方法,可以知道ElasticSearch中的REST請求是通過準備一個表示通道的請求執行的通道消費者(a channel consumer)來處理的。

接收到請求後開始構建IndexRequest,這個例項作用是將JSON型別的文件轉換為一個特定的和可搜尋的索引。

IndexRequest回首先取得RestRequest中的三個構造例項必須的引數:

  • index:文件的索引
  • type:文件的型別
  • id:文件指定的標識

然後在依次取得一些附加引數:

  • routing:控制分片的路由請求。使用這個值來雜湊的分片,而不是id。
  • parent:設定document的父id。
  • pipeline:在執行索引document前,設定攝取管道(ingest pipeline)
  • source:設定document索引的位元組形式。
  • timeout:超時時間
  • refresh:解析重新整理策略
  • version_type:設定版本型別
  • op_type:字串,用來表示是索引資料還是新建資料

引數詳情如下圖:

image.png

這引數都是NodeClient在索引文件時候需要用到的資料,NodeClient在Node初始化時候就載入完成,他是用來在本地節點上執行操作的模擬客戶端。

方法prepareRequest最後返回channel -> client.index(indexRequest, new RestStatusToXContentListener<>(channel, r -> r.getLocation(indexRequest.routing()))),因為該方法需要返回RestChannelConsumer型別的返回值,所以改寫成jdk7版本易於理解的程式碼版本如下圖所示:

返回程式碼

該段程式碼中最重要的就是NodeClient的index()方法,此方法的關鍵是新建了一個Task,這個Task包含了id,type,action,description,parentTask,startTime等資訊。

該task在老版本會被TransportIndexAction處理,但是6.0版本後TransportBulkAction已經取代了TransportIndexAction。task會被當做引數送入TransportBulkAction的doExecute方法中,另外兩個引數是BulkRequest和ActionListener

void doExecute(Task task, BulkRequest bulkRequest, ActionListener<BulkResponse> listener)
複製程式碼

BulkRequest中包含了該文件儲存的資訊,而ActionListener則用來監聽action的響應或失敗,用以做回撥操作。

doExecute方法主要做了以下操作:

  • 收集請求中的所有索引
  • 過濾掉不存在的索引,同時建立一個我們無法建立的索引圖。判斷不存在的索引和無法建立的索引主要是看索引是否有別名
  • 如果有遺漏的索引,則建立缺少的所有索引。注意在所有的建立完成後開始批量處理資料

然後執行TransportBulkAction類的executeBulk方法,完成資料的落地。

相關文章