Elasticsearch原理分析

 

目錄

 

基礎概念:

基本特徵:

倒排索引:

Term Dictionary

Term Index

Roaring Bitmap

聯合索引:

Skip List 跳錶原理


基礎概念:

Elasticsearch是一個基於Apache Lucene全文搜索引擎開發的分佈式的 RESTful 風格的的實時搜索與數據分析引擎,它比Lucene更強大,並且是開源的。官方網站:https://www.elastic.co/cn/

Elasticsearch是面向文檔型數據庫,一條數據就是一個文檔,文檔序列化之後是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"
    ]
}

更新和查詢也是類似的操作。

基本特徵:

  1. 實現了用於全文檢索的倒排索引,實現了用於存儲數值數據和位置數據的 BKD 樹, 以及用於分析的列存儲。
  2. 將每個字段編入索引,使其可搜索,提高搜索速度。
  3. 實時分析的分佈式引擎,確保故障時仍安全可用。
  4. 可以在承載了 PB (2的50次方個字節,約爲1000個TB)級數據的成百上千臺服務器上運行。
  5. 可處理多種數據類型,數字、文本、地理位置、結構化、非結構化。

倒排索引:

 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

談到roaring bitmaps就要先了解bitset 或者bitmap,Bitset是一種的數據結構,對應posting list如果是:[2,3,5,7,9] 那麼對於對應的bitset就是:[0,1,1,0,1,0,1,0,1,0],用0和1來表示該位置的數值的有無,這種做法就是一個byte可以代表8個文檔,當大數據量時,仍然會消耗很多內存,所以直接將bitset結構存入內存不太理想。

Elasticsearch不僅壓縮了Term Index,還對posting list 進行了壓縮,posting list雖然只存儲了文檔id,但是當文檔id很大的時候,比PB級的數據,Elasticsearch對posting list的壓縮做了兩件事:排序和大數變小數,引用一張被引用無數次的圖:

Alt text

簡單解讀一下這種壓縮技巧:

  • 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,如圖:

Alt text

壓縮技巧解讀:

  • step1:從小到大進行排序。
  • step2:將大數除以65536,用除得的結果和餘數來表示這個大數。
  • step3::以65535爲界進行分塊。

注意:如果一塊超過了4096 個值,直接用bitset存,2個字節就用個簡單的數組存放好了,比如shor[]。1KB=1024B=1024byte=8192bit,每個值一個bit,4096*2bit = 8192bit,剛好控制在1KB以內,磁盤尋道的單位是KB,超過1KB將再讀一次,這樣可以減少磁盤讀取次數。

聯合索引:

如何使用聯合索引查詢?

  1. Skip List 數據結構,同時遍歷多個term的posting list,互相skip
  2. 使用bitset數據結構,對多個term分別求出bitset,對bitset做AN操作

Skip List 跳錶原理

先了解跳錶需要先知道跳錶應該具有以下性質:

  1. 由多層有序鏈表組成。
  2. 最底層Level 1的鏈表包含所有的其他鏈表的元素。
  3. 如果一個元素在鏈表Level n中存在,那麼他在Level n以下的所有鏈表中都存在。
  4. 每個節點都包含連個指針,分別指同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直接進行壓縮,按位與,得到的結果就是最後的交集。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章