Elasticsearch底層原理基本解析
基礎概念:
Elasticsearch是一個基於Apache Lucene全文搜尋引擎開發的分散式的 RESTful 風格的的實時搜尋與資料分析引擎,它比Lucene更強大,並且是開源的。官方網站:https://www.elastic.co/cn/
Elasticsearch是面向文件型資料庫,一條資料就是一個文件,和資料結構mongoDB類似,文件序列化之後是JSON格式,例如一條使用者資料:
{ "name":"tino", "age":"25", "department":"DC", "hobies":[ "sports", "music", "movie" ] }
相當於oracle/mysql資料庫中的一張user表中的一條記錄,這user表有name、age、department、hobies欄位,而在Elasticsearch中,這是一個文件,而user則是整個文件的一個型別,Elasticsearch和關係型資料庫術語的對照基本上是:
Elasticsearch 使用的是標準的 RESTful API 和 JSON,它的互動可以通過HTTP請求,也可以通過java API,如果插入一條記錄,可以傳送一個HTTP請求來實現:
POST /user/add { "name":"tino", "age":"25", "department":"DC", "hobies":[ "sports", "music", "movie" ]
}
更新和查詢也是類似的操作。
基本特徵:
- 實現了用於全文檢索的倒排索引,實現了用於儲存數值資料和位置資料的 BKD 樹, 以及用於分析的列儲存。
- 將每個欄位編入索引,使其可搜尋,提高搜尋速度。
- 實時分析的分散式引擎,確保故障時仍安全可用。
- 可以在承載了 PB (2的50次方個位元組,約為1000個TB)級資料的成百上千臺伺服器上執行。
- 可處理多種資料型別,數字、文字、地理位置、結構化、非結構化。
倒排索引:
Elasticsearch最強大的就是為每個欄位提供了倒排索引,當查詢的時候不用擔心沒有索引可以利用,什麼是倒排索引,舉個簡單例子:
文件ID |
年齡 |
性別 |
1 |
25 |
女 |
2 |
32 |
女 |
3 |
25 |
男 |
每一行是一個文件(document),每個document都有一個文件ID。那麼給這些文件建立的倒排索引就是:
年齡的索引:
25 |
[1,3] |
32 |
[2] |
性別的索引:
女 |
[1,2] |
男 |
[3] |
可以看到,倒排索引是針對每個欄位的,每個欄位都有自己的倒排索引,25、32這些叫做term,[1,3]這種叫做posting list,
它是一個int的陣列,儲存了所有符合某個term的文件id,這時候我們想找出年齡為25的人,就會很快速,但是這裡只有兩個term,如果有成百上千個term呢,那找出某個term就會很慢,因為term還沒有排序,解決這個問題需要了解兩個概念:Term Dictionary 和 Term Index。
Term Dictionary
Elasticsearch為了能快速找到某個term,將所有的term進行了排序,然後二分法查詢term,類似於上學時候老師教我們的翻新華字典的方式,所以這叫做Term Dictionary,這種查詢方式其實和傳統關係型資料庫的B-Tree的方式很相似,所以這並不是Elasticsearch快的原因。
Term Index
如果說Term Dictionary是直接去二分法翻字典,那麼Term Index就是字典的目錄頁(當然這比我們真的去翻字典目錄快多了),假設我們的term如果全是英文單詞,那麼Term Index就是26個字母表,但是通常term未必都是英文,而可以是任意的byte陣列。因為就算26個英文字元也不一定都有對應的term,比如:a開頭的term只有一個,c開頭的term有一百萬個,x開頭的term一個也沒有,這樣查詢到c的時候又會很慢了。所以通常情況下Term Index 是包含term的一些字首的一棵樹,例如這樣的一個Term Index:
這樣的情況下通過Term Index據可以快速定位到某個offset(分支的開端),然後以此位置向下查詢,再加上FST(Finite-State Transducer,Lucene4.0開始使用該演算法來查詢Term 在Dictionary中的位置)的壓縮技術,將Term Index 快取到記憶體中,通過Term Index 找到對應的Term Dictionary的 block,然後再去磁碟直接找到term,減少磁碟的隨機讀寫次數,大大的提升查詢效率。(FST在下個章節單獨介紹)
Posting List壓縮技術 Roaring Bitmap
什麼是bitmap?
- 談到Roaring Bitmap就要先了解bitmap或者BitSet(java中的BitSet實現就是用了bitmap的方式)。
- bitmap是一種資料結構,與Posting List 的對應關係如下:
-
postingList=[2,3,5,7,9] bitmap=[0,1,1,0,1,0,1,0,1,0]
- 從上可以看出來Bitset是用0和1來表示該位置的數值的有無,這種做法就是一個byte可以代表8個文件,不過當大資料量時,仍然會消耗很多記憶體,所以直接將bitset結構存入記憶體也是不太理想的,所以出現了壓縮率更高的Roaring Bitmap。
- Elasticsearch不僅壓縮了Term Index,還對posting list 進行了壓縮,posting list雖然只儲存了文件id,但是當文件id很大的時候,比PB級的資料,Elasticsearch對posting list的壓縮做了兩件事:排序和大數變小數,引用一張被引用無數次的圖:
- 簡單解讀一下這種壓縮技巧:
- step1:在對posting list進行壓縮時進行了正序排序。
- step2:通過增量將73後面的大數變成小數儲存增量值。
- step3: 轉換成二進位制,取佔最大位的數,227佔8位,前三個佔八位,30佔五位,後三個數每個佔五位。
- 從第三步可以看出,這種壓縮方式仍然不夠高效,所以Lucene使用的資料結構叫做Roaring Bitmap,其壓縮的原理可以理解為,與其儲存100個0,佔用100個bit,還不如儲存0一次,然後宣告這個0有100個,它以兩個自己可以表示的最大數65535為界,將posting list分塊,比如第一塊是0-65535,第二塊是65536-131071,如圖:
壓縮技巧解讀:
step1:從小到大進行排序。
step2:將大數除以65536,用除得的結果和餘數來表示這個大數。
step3::以65535為界進行分塊。
注意:如果一塊超過了4096 個值,直接用bitset存,2個位元組就用個簡單的陣列存放好了,比如short[],修正一下:1KB=1024B=1024byte=8192bit,每個值一個bytes,4096*2bytes=8192bytes,剛好達到每一個block的界限4096 = 65536 / 2 / 8
聯合索引:
如何使用聯合索引查詢?
- Skip List 資料結構,同時遍歷多個term的posting list,互相skip
- 使用bitset資料結構,對多個term分別求出bitset,對bitset做AN操作
Skip List 跳錶原理
先了解跳錶需要先知道跳錶應該具有以下性質:
- 由多層有序連結串列組成。
- 最底層Level 1的連結串列包含所有的其他連結串列的元素。
- 如果一個元素在連結串列Level n中存在,那麼他在Level n以下的所有連結串列中都存在。
- 每個節點都包含連個指標,分別指同Level連結串列的下一個元素和下一層的元素。
這是一個有序列連結串列:
從連結串列中搜索(27,44,61)需要查詢的次數為:2+4+6 = 12次,可以得到所有結果,這樣做其實沒有用到連結串列的有序性,我們在查詢44、66時候其實都做了一些重複查詢,勢必會造成效率低的問題,這時候可以用Skip List演算法來優化查詢次數,把某些節點提取出來,將連結串列分成兩級:
這樣我們在查詢44和61的時候次數就得到了簡化,因為列表時有序的,所以當我們查到27的時候,44的大概位置就知道了,查到44的時候,61的大概位置也就知道了,避免了一部分重複查查詢,這時候我們找到所以結果的次數為2+3+4 = 9次,查詢61的時候似乎又對44的查詢重複了一次,似乎還有優化的空間,那我們再對二級連結串列再進行一次分級:
這時候可以看到我們查詢61的時候只需要從15-50-61,三次就可以查詢到結果,沒有去走查詢44時的36,所以現在查詢次數得到了再次優化,也就是2+3+3 = 8次,有人說:就優化了一次查詢,需要做得這麼複雜嗎,能有那麼大的效能優勢嗎?的確,這裡資料量很少,組合查詢的索引也只有三個,看起來確實沒有優勢,但是當面對PB級資料的時候,連結串列的分級將無限擴大大,我們在查詢結果時所"跳" 的跨度也會非常大,原先需要查詢一百萬次的結果,可能僅僅三次就查詢到了,這個時候就會顯得非常高效,從Skip List的查詢原理可以看出,它的高效其實是犧牲了一定的空間冗餘換來的,所以在有些情況下還是使用bitset更加的直觀,比如下面這種資料結構:
111 | 222 | 333 | 444 | ||||
111 | 222 | 333 | 444 | 555 | 666 | 777 | |
111 | 222 | 333 | 444 | 555 | 666 | 777 | 888 |
如果使用跳錶,查詢第一行資料在另外兩行中查詢看是否存在,為了得到最後得到交集的結果,操作就要繁瑣的多。
如果使用bitset直接進行壓縮,按位與,得到的結果就是最後的交集。
轉載自:Elasticsearch底層原理基本解析_Tino's Space-CSDN部落格