建立索引的時候,咱們經過Mapping 映射定義好索引的基本結構信息,接下來咱們確定須要往 ES 裏面新增業務文檔數據了,例如用戶,日誌等業務數據。新增的業務數據,咱們根據 Mapping 來生成對應的倒排索引信息 。緩存
咱們一直說,Elasticsearch是一個基於Apache Lucene 的開源搜索引擎。Elasticsearch的搜索高效的緣由並非像Redis那樣重依賴內存的,而是經過創建特殊的索引數據結構--倒排索引實現的。因爲它的使用場景:處理PB級結構化或非結構化數據,數據量大且須要持久化防止斷電丟失,因此 Elasticsearch 的數據和索引存儲是依賴於服務器的硬盤。這也是爲何咱們在ES性能調優的時候能夠將使用SSD硬盤存儲做爲其中一個優化項來考慮。服務器
倒排索引的概念,我相信你們都已經知道了,這裏就不在贅述,倒排索引能夠說是Elasticsearch搜索高效和支持非結構化數據檢索的主要緣由了,可是倒排索引被寫入磁盤後是不可改變 的:它永遠不會修改。數據結構
倒排索引的不可變性,這點主要是由於 Elasticsearch 的底層是基於 Lucene,而在 Lucene 中提出了按段搜索的概念,將一個索引文件拆分爲多個子文件,則每一個子文件叫做段,每一個段都是一個獨立的可被搜索的數據集,而且段具備不變性,一旦索引的數據被寫入硬盤,就不可再修改。app
段 的概念提出主要是由於:在早期全文檢索中爲整個文檔集合創建了一個很大的倒排索引,並將其寫入磁盤中。若是索引有更新,就須要從新全量建立一個索引來替換原來的索引。這種方式在數據量很大時效率很低,而且因爲建立一次索引的成本很高,因此對數據的更新不能過於頻繁,也就不能保證時效性。性能
並且在底層採用了分段的存儲模式,使它在讀寫時幾乎徹底避免了鎖的出現,大大提高了讀寫性能。說到這,大家可能會想到 ConcurrentHashMap 的分段鎖 的概念,其實原理有點相似。優化
並且 Elasticsearch 中的倒排索引被設計成不可變的,有如下幾個方面優點:搜索引擎
- 不須要鎖。若是你歷來不更新索引,你就不須要擔憂多進程同時修改數據的問題。
- 一旦索引被讀入內核的文件系統緩存,便會留在哪裏。因爲其不變性,只要文件系統緩存中還有足夠的空間,那麼大部分讀請求會直接請求內存,而不會命中磁盤。這提供了很大的性能提高。
- 其它緩存(像filter緩存),在索引的生命週期內始終有效。它們不須要在每次數據改變時被重建,由於數據不會變化。
- 寫入單個大的倒排索引容許數據被壓縮,減小磁盤 I/O 和 須要被緩存到內存的索引的使用量。
每個段自己都是一個倒排索引,但索引在 Lucene 中除表示全部段的集合外,還增長了提交點的概念。設計
爲了提高寫的性能,Lucene並無每新增一條數據就增長一個段,而是採用延遲寫的策略,每當有新增的數據時,就將其先寫入內存中,而後批量寫入磁盤中。如有一個段被寫到硬盤,就會生成一個提交點,提交點就是一個列出了全部已知段和記錄全部提交後的段信息的文件。3d
上面說過 ES 的索引的不變性,還有段和提交點的概念。那麼它的具體實現細節和寫入磁盤的過程是怎樣的呢?日誌
用戶建立了一個新文檔,新文檔被寫入內存中。
不時地, 緩存被提交,這時緩存中數據會以段的形式被先寫入到文件緩存系統而不是直接被刷到磁盤。
這是由於,提交一個新的段到磁盤須要一個fsync
來確保段被物理性地寫入磁盤,這樣在斷電的時候就不會丟失數據。 可是 fsync
操做代價很大;若是每次索引一個文檔都去執行一次的話會形成很大的性能問題,可是這裏新段會被先寫入到文件系統緩存,這一步代價會比較低。
fsync
把數據從文件系統緩存刷(flush)到硬盤,咱們不能保證數據在斷電甚至是程序正常退出以後依然存在。Elasticsearch 增長了一個 translog ,或者叫事務日誌,在每一次對 Elasticsearch 進行操做時均進行了日誌記錄。如上圖所示,一個文檔被索引以後,就會被添加到內存緩衝區,而且同時追加到了 translog。每隔一段時間,更多的文檔被添加到內存緩衝區和追加到事務日誌(translog),以後新段被不斷從內存緩存區被寫入到文件緩存系統,這時內存緩存被清空,可是事務日誌不會。隨着 translog 變得愈來愈大,達到必定程度後索引被刷新,在刷新(flush)以後,段被全量提交,一個提交點被寫入硬盤,而且事務日誌被清空。
從整個流程咱們能夠了解到如下幾個問題:
因爲自動刷新流程每秒會建立一個新的段 ,這樣會致使短期內的段數量暴增。而段數目太多會帶來較大的麻煩。 每個段都會消耗文件句柄、內存和cpu運行週期。更重要的是,每一個搜索請求都必須輪流檢查每一個段;因此段越多,搜索也就越慢。
Elasticsearch經過在後臺進行段合併來解決這個問題。小的段被合併到大的段,而後這些大的段再被合併到更大的段。
段合併的時候會將那些舊的已刪除文檔 從文件系統中清除。 被刪除的文檔(或被更新文檔的舊版本)不會被拷貝到新的大段中。
上文闡述了索引的持久化流程和倒排索引被設定爲不可修改以及這樣設定的好處。由於它是不可變的,你不能修改它。可是若是你須要讓一個新的文檔可被搜索,這就涉及到索引的更新了,索引不可被修改但又須要更新,這種看似矛盾的要求,咱們須要怎麼作呢?
ES 的解決方法就是:用更多的索引。什麼意思?就是原來的索引不變,咱們對新的文檔再建立一個索引。這樣說完不知道你們有沒有疑惑或者沒理解,咱們經過圖表的方式說明下。
假如咱們現有兩個日誌信息的文檔,信息以下:
這時候咱們獲得的倒排索引內容(省略一部分)是:
詞項(term) | 文檔(Doc) |
---|---|
the | doc 1,doc 2 |
request | doc 1 |
param | doc 1,doc 2 |
is | doc 1,doc 2 |
name | doc 1 |
response | doc 2 |
result | doc 2 |
... | ... |
若是咱們這時新增一個文檔 doc 3:the request param is name = 'li si' and sex is femal,或者修改文檔 doc 2的內容爲:the response result is code = 9999 and msg = 'false'。這時 ES 是如何處理的呢?
正如上文所述的,爲了保留索引不變性,ES 會建立一個新的索引,對於新增的文檔索引信息以下:
詞項(term) | 文檔(Doc) |
---|---|
the | doc 3 |
request | doc 3 |
param | doc 3 |
is | doc 3 |
name | doc 3 |
sex | doc 3 |
... | ... |
對於修改的文檔索引信息以下;
詞項(term) | 文檔(Doc) |
---|---|
the | doc 2 |
response | doc 2 |
result | doc 2 |
is | doc 2 |
code | doc 2 |
sex | doc 2 |
... | ... |
經過增長新的補充索引來反映新近的修改,而不是直接重寫整個倒排索引。每個倒排索引都會被輪流查詢到(從最先的開始),查詢完後再對結果進行合併。
正如上文所述那樣,對於修改的場景來講,同一個文檔這時磁盤中同時會有兩個索引數據一個是原來的索引,另外一個是修改以後的索引。
以正常邏輯來看,咱們知道搜索的時候確定以新的索引爲標準,可是段是不可改變的,因此既不能從把文檔從舊的段中移除,也不能修改舊的段來進行反映文檔的更新。 取而代之的是,每一個提交點會包含一個 .del
文件,文件中會列出這些被刪除文檔的段信息。
當一個文檔被 「刪除」 時,它實際上只是在.del
文件中被 標記 刪除。一個被標記刪除的文檔仍然能夠被查詢匹配到, 但它會在最終結果被返回前從結果集中移除。
文檔更新也是相似的操做方式:當一個文檔被更新時,舊版本文檔被標記刪除,文檔的新版本被索引到一個新的段中。 可能兩個版本的文檔都會被一個查詢匹配到,但被刪除的那個舊版本文檔在結果集返回前就已經被移除。