
本篇為elasticsearch源碼分析系列文章的第十一篇,本篇開始進(jìn)入索引有關(guān)操作的講解。以后的若干篇我們會(huì)連續(xù)討論文檔的創(chuàng)建,檢索,更新,刪除,版本控制等一系列內(nèi)容。
文檔
ElasticSearch存儲(chǔ)系統(tǒng)中的實(shí)體叫做文檔,document。如果用關(guān)系型數(shù)據(jù)庫(kù)來(lái)類比的話,一個(gè)文檔相當(dāng)于數(shù)據(jù)庫(kù)中的一行記錄。ElasticSearch中的文檔有個(gè)特點(diǎn),相同字段必須是相同的類型,也就是說(shuō)所有包含title字段的文檔,title字段類型都必須一樣,要么同為string,要么同為int。
文檔由多個(gè)字段組成,每個(gè)字段的類型可以是,文本,數(shù)值,日期,還可以是字符串?dāng)?shù)組這種復(fù)雜的類型。字段類型在ElasticSearch中非常重要,它涉及到各種分析和排序操作如何被執(zhí)行的信息。Elastic官方推薦我們使用Mapping映射來(lái)干預(yù)字段的類型。與關(guān)系型數(shù)據(jù)庫(kù)不同,ElasticSearch不需要有固定的結(jié)構(gòu),每個(gè)文檔可以有不同的字段,此外,在程序開發(fā)期間,不必確定有哪些字段。
文檔的類型
在ElasticSearch中,文檔類型可以讓程序員輕松的區(qū)分單個(gè)索引中的不同對(duì)象。每個(gè)文檔可以有不同的結(jié)構(gòu),但在實(shí)際生產(chǎn)環(huán)境中我們還是推薦將文檔中的類型詳細(xì)化,這樣對(duì)以后的開發(fā)會(huì)有很大的幫助。
文檔類型的映射
上面提到的映射,指的是ElasticSearch在映射中存儲(chǔ)有關(guān)字段的信息,這種類型信息就是映射Mapping。每個(gè)文檔類型都有自己的映射,即使在初始化時(shí)沒有提前定義。在涉及到全文搜索和倒排索引的內(nèi)容中,會(huì)有對(duì)文檔分析的過(guò)程,在這個(gè)過(guò)程中每個(gè)字段都必須根據(jù)不同類型作相應(yīng)的分析。舉例來(lái)說(shuō),對(duì)數(shù)值字段和文本字段的分析肯定是不同的分析過(guò)程,數(shù)字的分析就不應(yīng)該是按照字母的排序來(lái)分析。
使用ElasticSearch的ResultAPI來(lái)新建文檔
在ElasticSearch中,所有文檔都是數(shù)據(jù),所有數(shù)據(jù)都有定義好的索引和類型?,F(xiàn)在我們通過(guò)一個(gè)比較常見的例子來(lái)建立文檔:

上面操作的意思是,我們建立了一個(gè)名為article的索引和名為computer的類型,文檔的標(biāo)示符為1。
如果一切正常,那么這種使用RESTfulAPI的創(chuàng)建方式會(huì)返回一個(gè)JSON響應(yīng),與如下輸出類似:

前面的相應(yīng)包含了操作狀態(tài)的信息,顯示了創(chuàng)建的文檔放在什么地方,還包含了文檔的唯一標(biāo)示符_id和當(dāng)前版本_version的信息。每次ElasticSearch的更新版本都會(huì)自動(dòng)遞增。
而且ElasticSearch在創(chuàng)建文檔時(shí),如果沒指定文檔標(biāo)示符,那么這個(gè)文檔的標(biāo)示符會(huì)被自動(dòng)創(chuàng)建。

這都是怎么做到的呢?我們會(huì)在下一節(jié)從源碼的角度解釋。
ElasticSearch源碼如何新建文檔
在以前文章中強(qiáng)調(diào)的Node實(shí)例化的過(guò)程中,加載了ActionModule這個(gè)模塊,這個(gè)模塊是接收客戶端發(fā)送的RESTful請(qǐng)求的的模塊,ActionModule的加載如下:
ActionModule actionModule = new ActionModule(false, settings, clusterModule.getIndexNameExpressionResolver(), settingsModule.getIndexScopedSettings(), settingsModule.getClusterSettings(), settingsModule.getSettingsFilter(), threadPool, pluginsService.filterPlugins(ActionPlugin.class), client, circuitBreakerService, usageService);
在加載完了ActionModule后,會(huì)通過(guò)ActionModule的方法initRestHandlers()來(lái)初始化HTTP處理程序,這個(gè)handler就能解析客戶端通過(guò)http協(xié)議發(fā)送到ElasticSearch集群中的RESTful請(qǐng)求。
加載RestIndexActionindex處理器,
registerHandler.accept(new RestIndexAction(settings, restController))
如下圖所示,注冊(cè)不同的REST處理程序路徑,以用來(lái)不同的匹配請(qǐng)求。

可以看到控制器匹配路徑中,有index,type和id,如果不指定id,則id會(huì)被自動(dòng)創(chuàng)建,而且不指定id必須用POST方法來(lái)發(fā)送請(qǐng)求。
因?yàn)镋lasticSearch中的Controller底層都是Netty實(shí)現(xiàn)的。所以在端口綁定后,Netty4HttpChannel會(huì)去監(jiān)聽端口收到的http請(qǐng)求。在ElasticSearch的Controller接收到Netty4HttpChannel轉(zhuǎn)發(fā)的請(qǐng)求后,會(huì)調(diào)用RestIndexAction中的方法prepareRequest()。該方法返回RestChannelConsumer類型的實(shí)例,該實(shí)例是虛擬類BaseRestHandler中的Functional接口。閱讀這個(gè)接口的定義的方法,可以知道ElasticSearch中的REST請(qǐng)求是通過(guò)準(zhǔn)備一個(gè)表示通道的請(qǐng)求執(zhí)行的通道消費(fèi)者(a channel consumer)來(lái)處理的。
接收到請(qǐng)求后開始構(gòu)建IndexRequest,這個(gè)實(shí)例作用是將JSON類型的文檔轉(zhuǎn)換為一個(gè)特定的和可搜索的索引。
IndexRequest回首先取得RestRequest中的三個(gè)構(gòu)造實(shí)例必須的參數(shù):
- index:文檔的索引
- type:文檔的類型
- id:文檔指定的標(biāo)識(shí)
然后在依次取得一些附加參數(shù):
- routing:控制分片的路由請(qǐng)求。使用這個(gè)值來(lái)哈希的分片,而不是id。
- parent:設(shè)置document的父id。
- pipeline:在執(zhí)行索引document前,設(shè)置攝取管道(ingest pipeline)
- source:設(shè)置document索引的字節(jié)形式。
- timeout:超時(shí)時(shí)間
- refresh:解析刷新策略
- version_type:設(shè)置版本類型
- op_type:字符串,用來(lái)表示是索引數(shù)據(jù)還是新建數(shù)據(jù)
參數(shù)詳情如下圖:

這參數(shù)都是NodeClient在索引文檔時(shí)候需要用到的數(shù)據(jù),NodeClient在Node初始化時(shí)候就加載完成,他是用來(lái)在本地節(jié)點(diǎn)上執(zhí)行操作的模擬客戶端。
方法prepareRequest最后返回channel -> client.index(indexRequest, new RestStatusToXContentListener<>(channel, r -> r.getLocation(indexRequest.routing()))),因?yàn)樵摲椒ㄐ枰祷?strong>RestChannelConsumer類型的返回值,所以改寫成jdk7版本易于理解的代碼版本如下圖所示:

該段代碼中最重要的就是NodeClient的index()方法,此方法的關(guān)鍵是新建了一個(gè)Task,這個(gè)Task包含了id,type,action,description,parentTask,startTime等信息。
該task在老版本會(huì)被TransportIndexAction處理,但是6.0版本后TransportBulkAction已經(jīng)取代了TransportIndexAction。task會(huì)被當(dāng)做參數(shù)送入TransportBulkAction的doExecute方法中,另外兩個(gè)參數(shù)是BulkRequest和ActionListener
void doExecute(Task task, BulkRequest bulkRequest, ActionListener<BulkResponse> listener)
BulkRequest中包含了該文檔存儲(chǔ)的信息,而ActionListener則用來(lái)監(jiān)聽action的響應(yīng)或失敗,用以做回調(diào)操作。
doExecute方法主要做了以下操作:
- 收集請(qǐng)求中的所有索引
- 過(guò)濾掉不存在的索引,同時(shí)建立一個(gè)我們無(wú)法創(chuàng)建的索引圖。判斷不存在的索引和無(wú)法創(chuàng)建的索引主要是看索引是否有別名
- 如果有遺漏的索引,則創(chuàng)建缺少的所有索引。注意在所有的創(chuàng)建完成后開始批量處理數(shù)據(jù)
然后執(zhí)行TransportBulkAction類的executeBulk方法,完成數(shù)據(jù)的落地。