本篇為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:字串,用來表示是索引資料還是新建資料
引數詳情如下圖:
這引數都是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方法,完成資料的落地。