帶你走進神同樣的Elasticsearch索引機制

更多精彩內容請看個人我的博客或者掃描二維碼,關注微信公衆號佛西先森
wechathtml

前言

相比於大多數人熟悉的MySQL數據庫的索引,Elasticsearch的索引機制是徹底不一樣於MySQL的B+Tree結構。索引會被壓縮放入內存用於加速搜索過程,這一點在效率上是完爆MySQL數據庫的。可是Elasticsearch會對所有text字段進行索引,必然會消耗巨大的內存,爲此Elasticsearch針對索引進行了深度的優化。在保證執行效率的同時,儘可能縮減內存空間的佔用。這篇文章就深度解析了Elasticsearch索引原理,揭開搜索的神祕面紗。數據庫

簡介

Elasticsearch是一個基於Lucene庫的開源搜索引擎,它提供分佈式的實時文件存儲和搜索,可擴展性好,而且支持經過HTTP網絡接口交互,數據以JSON格式展現。數組

Elasticsearch由於其極快的搜索和聚合速度一般被應用在各類搜索應用中,好比在本身的app裏面加一個搜索框或者分析實時日誌(ELK系統)。緩存

Elasticsearch會對全部輸入的文本進行處理,創建索引放入內存中,從而提升搜索效率。在這一點上ES要優於MySQL的B+樹的結構,MySQL須要將索引放入磁盤,每次讀取須要先從磁盤讀取索引而後尋找對應的數據節點,可是ES可以直接在內存中就找到目標文檔對應的大體位置,最大化提升效率。而且在進行組合查詢的時候MySQL的劣勢更加明顯,它不支持複雜的組合查詢好比聚合操做,即便要組合查詢也要事先建好索引,可是ES就能夠完成這種複雜的操做,默認每一個字段都是有索引的,在查詢的時候能夠各類互相組合。微信

爲了省事,如下統一用ES來代替Elasticsearch,其實咱們在公司裏面也基本都說ES,全稱比較難讀。還有一點,由於ES使用了Lucene的庫,下面說的不少優化思路都是Lucene裏面的,可是爲了講解方便,我就用ES來代替Lucene。網絡

索引

首先須要申明的是在ES中索引的概念和MySQL裏面的概念不太同樣,這裏列出一下ES和MySQL的對應的概念,方便你們理解。數據結構

MySQL ES
庫(database) 索引(index)
表(table) 類型(type)
行(row) 文檔(doc)
列(column) 字段(field)

在ES中每一個字段都是被索引的,因此不會像MySQL中那樣須要對字段進行手動的創建索引。app

ES在創建索引的時候採用了一種叫作倒排索引的機制,保證每次在搜索關鍵詞的時候可以快速定位到這個關鍵詞所屬的文檔。less

Inverted Index

好比有三句話在數據庫中:elasticsearch

  1. Winter is coming
  2. Ours is fury
  3. Ths choice is yours

若是沒有倒排索引(Inverted Index),想要去找其中的choice,須要遍歷整個文檔,才能找到對應的文檔的id,這樣作效率是十分低的,因此爲了提升效率,咱們就給輸入的全部數據的都創建索引,而且把這樣的索引和對應的文檔創建一個關聯關係,至關於一個詞典。當咱們在尋找choice的時候就能夠直接像查字典同樣,直接找到全部包含這個數據的文檔的id,而後找到數據。

index

Lucene在對文檔創建索引的時候,會給詞典的全部的元素排好序,在搜索的時候直接根據二分查找的方法進行篩選就可以快速找到數據。不過是否是感受有點眼熟,這不就是MySQL的索引方式的,直接用B+樹創建索引詞典指向被索引的文檔。

ES作的要更深一點,ES但願把這個詞典「搬進」內存,直接從內存讀取數據不就比從磁盤讀數據要快不少嗎!問題在於對於海量的數據,索引的空間消耗十分巨大,直接搬進來確定不合適,因此須要進一步的處理,創建詞典索引(term index)。經過詞典索引能夠直接找到搜索詞在詞典中的大體位置,而後從磁盤中取出詞典數據再進行查找。因此大體的結構圖就變成了這樣:

index-arch

ES經過有限狀態轉化器,把term dictionary變成了term index,放進了內存,因此這個term index到底長什麼樣呢?

Finite State Transducers

有限狀態轉換器(Finite State Transducers)至關因而一個Trie前綴樹,能夠直接根據前綴就找到對應的term在詞典中的位置。

好比咱們如今有已經排序好的單詞mopmothpopstarstoptop以及他們的順序編號0..5,能夠生成一個下面的狀態轉換圖

fst

當遍歷上面的每一條邊的時候,都會加上這條邊的輸出,好比當輸入是stop的時候會通過s/3o/1,相加獲得的排序的順序是4;而對於mop,獲得的排序的結果是0

可是這個樹並不會包含全部的term,而是不少term的前綴,經過這些前綴快速定位到這個前綴所屬的磁盤的block,再從這個block去找文檔列表。爲了壓縮詞典的空間,實際上每一個block都只會保存block內不一樣的部分,好比mopmoth在同一個以mo開頭的block,那麼在對應的詞典裏面只會保存pth,這樣空間利用率提升了一倍。

使用有限狀態轉換器在內存消耗上面要比遠比SortedMap要少,可是在查詢的時候須要更多的CPU資源。維基百科的索引就是使用的FST,只使用了69MB的空間,花了大約8秒鐘,就爲接近一千萬個詞條創建了索引,使用的堆空間不到256MB。

順帶提一句,在ES中有一種查詢叫模糊查詢(fuzzy query),根據搜索詞和字段之間的編輯距離來判斷是否匹配。在ES4.0以前,模糊查詢會先讓檢索詞和全部的term計算編輯距離篩選出全部編輯距離內的字段;在ES4.0以後,採用了由Robert開發的,直接經過有限狀態轉換器就能夠搜索指定編輯距離內的單詞的方法,將模糊查詢的效率提升了超過100倍。

如今已經把詞典壓縮成了詞條索引,尺寸已經足夠小到放入內存,經過索引可以快速找到文檔列表。如今又有另一個問題,把全部的文檔的id放入磁盤中會不會佔用了太多空間?若是有一億個文檔,每一個文檔有10個字段,爲了保存這個posting list就須要消耗十億個integer的空間,磁盤空間的消耗也是巨大的,ES採用了一個更加巧妙的方式來保存全部的id。

Posting Lists

所謂的posting list,就是全部包含特定term文檔的id的集合,須要從詞典映射到這個集合。因爲整型數字integer能夠被高效壓縮的特質,integer是最適合放在posting list做爲文檔的惟一標識的,ES會對這些存入的文檔進行處理,轉化成一個惟一的整型id

在存儲數據的時候,在每個shard裏面,ES會將數據存入不一樣的segment,這是一個比shard更小的分片單位,這些segment會按期合併。在每個segment裏面都會保存最多\(2^{31}\)個文檔,每一個文檔被分配一個惟一的id,從\(0\)\(2^{31}-1\)

Frame of Reference

在進行查詢的時候常常會進行組合查詢,好比查詢同時包含choicethe的文檔,那麼就須要分別查出包含這兩個單詞的文檔的id,而後取這兩個id列表的交集;若是是查包含choice或者the的文檔,那麼就須要分別查出posting list而後取並集。爲了可以高效的進行交集和並集的操做,posting list裏面的id都是有序的。同時爲了減少存儲空間,全部的id都會進行delta編碼(delta-encoding,我以爲能夠翻譯成增量編碼

好比如今有id列表[73, 300, 302, 332, 343, 372],轉化成每個id相對於前一個id的增量值(第一個id的前一個id默認是0,增量就是它本身)列表是[73, 227, 2, 30, 11, 29]。在這個新的列表裏面,全部的id都是小於255的,因此每一個id只須要一個字節存儲。

實際上ES會作的更加精細,它會把全部的文檔分紅不少個block,每一個block正好包含256個文檔,而後單獨對每一個文檔進行增量編碼,計算出存儲這個block裏面全部文檔最多須要多少位來保存每一個id,而且把這個位數做爲頭信息(header)放在每一個block 的前面。這個技術叫Frame of Reference,我翻譯成索引幀。

好比對上面的數據進行壓縮(假設每一個block只有3個文件而不是256),壓縮過程以下

frame-of-ref

在返回結果的時候,其實也並不須要把全部的數據直接解壓而後一股腦所有返回,能夠直接返回一個迭代器iterator,直接經過迭代器的next方法逐一取出壓縮的id,這樣也能夠極大的節省計算和內存開銷。

經過以上的方式能夠極大的節省posting list的空間消耗,提升查詢性能。不過ES爲了提升filter過濾器查詢的性能,還作了更多的工做,那就是緩存。

Roaring Bitmaps

ES會緩存頻率比較高的filter查詢,其中的原理也比較簡單,即生成(fitler, segment)和id列表的映射,可是和倒排索引不一樣,咱們只把經常使用的filter緩存下來而倒排索引是保存全部的,而且filter緩存應該足夠快,否則直接查詢不就能夠了。ES直接把緩存的filter放到內存裏面,映射的posting list放入磁盤中。

ES在filter緩存使用的壓縮方式和倒排索引的壓縮方式並不相同,filter緩存使用了roaring bitmap的數據結構,在查詢的時候相對於上面的Frame of Reference方式CPU消耗要小,查詢效率更高,代價就是須要的存儲空間(磁盤)更多。

Roaring Bitmap是由int數組和bitmap這兩個數據結構改良過的成果——int數組速度快可是空間消耗大,bitmap相對來講空間消耗小可是無論包含多少文檔都須要12.5MB的空間,即便只有一個文件也要12.5MB的空間,這樣實在不划算,因此權衡以後就有了下面的Roaring Bitmap。

  1. Roaring Bitmap首先會根據每一個id的高16位分配id到對應的block裏面,好比第一個block裏面id應該都是在0到65535之間,第二個block的id在65536和131071之間
  2. 對於每個block裏面的數據,根據id數量分紅兩類
    • 若是數量小於4096,就是用short數組保存
    • 數量大於等於4096,就使用bitmap保存

在每個block裏面,一個數字實際上只須要2個字節來保存就好了,由於高16位在這個block裏面都是相同的,高16位就是block的id,block id和文檔的id都用short保存。

cache

至於4096這個分界線,由於當數量小於4096的時候,若是用bitmap就須要8kB的空間,而使用2個字節的數組空間消耗就要少一點。好比只有2048個值,每一個值2字節,一共只須要4kB就能保存,可是bitmap須要8kB。

總結

ES爲了提升搜索效率、優化存儲空間作了不少工做。

爲了可以快速定位到目標文檔,ES使用倒排索引技術來優化搜索速度,雖然空間消耗比較大,可是搜索性能提升十分顯著。

因爲索引數量巨大,ES沒法直接把所有索引放入內存,轉而創建詞典索引,構建有限狀態轉換器(FST)放入內存,進一步提升搜索效率。

數據文檔的id在詞典內的空間消耗也是巨大的,ES使用了索引幀(Frame of Reference)技術壓縮posting list,帶來的壓縮效果十分明顯。

ES的filter語句採用了Roaring Bitmap技術來緩存搜索結果,保證高頻filter查詢速度的同時下降存儲空間消耗。

參考

MySQL和Lucene(Elasticsearch)索引對比分析
Elasticsearch from the Bottom Up, Part 1
時間序列數據庫的祕密 (2)——索引
Frame of Reference and Roaring Bitmaps
Using Finite State Transducers in Lucene
Lucene's FuzzyQuery is 100 times faster in 4.0

相關文章
相關標籤/搜索