深入理解搜索引擎原理
時間:2023-03-20 08:16:01 | 來源:電子商務
時間:2023-03-20 08:16:01 來源:電子商務
之前幾段工作經(jīng)歷都與搜索有關(guān),現(xiàn)在也有業(yè)務在用搜索,對搜索引擎做一個原理性的分享,包括搜索的一系列核心數(shù)據(jù)結(jié)構(gòu)和算法,盡量覆蓋搜索引擎的核心原理,但不涉及數(shù)據(jù)挖掘、NLP等。
一、搜索引擎引題
搜索引擎是什么?
這里有個概念需要提一下。信息檢索 (Information Retrieval 簡稱 IR) 和 搜索 (Search) 是有區(qū)別的,信息檢索是一門學科,研究信息的獲取、表示、存儲、組織和訪問,而搜索只是信息檢索的一個分支,其他的如問答系統(tǒng)、信息抽取、信息過濾也可以是信息檢索。
本文要講的搜索引擎,是通常意義上的全文搜索引擎、垂直搜索引擎的普遍原理,比如 Google、Baidu,天貓搜索商品、口碑搜索美食、飛豬搜索酒店等。
Lucene 是非常出名且高效的全文檢索工具包,ES 和 Solr 底層都是使用的 Lucene,本文的大部分原理和算法都會以 Lucene 來舉例介紹。
為什么需要搜索引擎?
看一個實際的例子:如何從一個億級數(shù)據(jù)的商品表里,尋找名字含“秋褲”的 商品。
使用SQL Like
select * from item where name like '%秋褲%'
如上,大家第一能想到的實現(xiàn)是用 like,但這無法使用上索引,會在大量數(shù)據(jù)集上做一次遍歷操作,查詢會非常的慢。有沒有更簡單的方法呢,可能會說能不能加個秋褲的分類或者標簽,很好,那如果新增一個商品品類怎么辦呢?要加無數(shù)個分類和標簽嗎?如何能更簡單高效的處理全文檢索呢?
使用搜索引擎
答案是搜索,會事先 build 一個倒排索引,通過詞法語法分析、分詞、構(gòu)建詞典、構(gòu)建倒排表、壓縮優(yōu)化等操作構(gòu)建一個索引,查詢時通過詞典能快速拿到結(jié)果。這既能解決全文檢索的問題,又能解決了SQL查詢速度慢的問題。
那么,淘寶是如何在1毫秒從上億個商品找到上千種秋褲的呢,谷歌如何在1毫秒從萬億個網(wǎng)頁中找尋到與你關(guān)鍵字匹配的幾十萬個網(wǎng)頁,如此大的數(shù)據(jù)量是怎么做到毫秒返回的。二、搜索引擎是怎么做的?
Part1. 分詞
分詞就是對一段文本,通過規(guī)則或者算法分出多個詞,每個詞作為搜索的最細粒度一個個單字或者單詞。
只有分詞后有這個詞,搜索才能搜到,分詞的正確性非常重要。分詞粒度太大,搜索召回率就會偏低,分詞粒度太小,準確率就會降低。如何恰到好處的分詞,是搜索引擎需要做的第一步。
正確性&粒度
- 分詞正確性
- “他說的確實在理”,這句話如何分詞?
- “他-說-的確-實在-理” [錯誤語義]
- “他-說-的-確實-在理” [正確語義]
- 分詞的粒度
- “中華人民共和國憲法”,這句話如何分詞?
- “中華人民共和國-憲法”,[搜索 中華、共和國 無結(jié)果]
- “中華-人民-共和國-憲法”,[搜索 共和 無結(jié)果]
- “中-華-人-民-共-和-國-憲-法”,[搜索其中任意字都有結(jié)果]
分詞的粒度并不是越小越好,他會降低準確率,比如搜索 “中秋” 也會出現(xiàn)上條結(jié)果,而且粒度越小,索引詞典越大,搜索效率也會下降,后面會細說。
如何準確的把控分詞,涉及到 NLP 的內(nèi)容啦,這里就不展開了。
停用詞
很多語句中的詞都是沒有意義的,比如 “的”,“在” 等副詞、謂詞,英文中的 “a”,“an”,“the”,在搜索是無任何意義的,所以在分詞構(gòu)建索引時都會去除,降低不不要的索引空間,叫停用詞 (StopWord)。
通常可以通過文檔集頻率和維護停用詞表的方式來判斷停用詞。
詞項處理
詞項處理,是指在原本的詞項上在做一些額外的處理,比如歸一化、詞形歸并、詞干還原等操作,以提高搜索的效果。并不是所有的需求和業(yè)務都要詞項處理,需要根據(jù)場景來判斷。
1.歸一化- USA - U.S.A. [縮寫]
- 7月30日 - 7/30 [中英文]
- color - colour [通假詞]
- 開心 - 高興 [同義詞擴展范疇]
這樣查詢 U.S.A. 也能得到 USA 的結(jié)果,同義詞可以算作歸一化處理,不過同義詞還可以有其他的處理方式。
2.詞形歸并(Lemmatization)針對英語同一個詞有不同的形態(tài),可以做詞形歸并成一個,如:
- am, are, is -> be
- car, cars, car's, cars' -> car
- the boy's cars are different colors -> the boy car be different color
3.詞干還原(Stemming)通常指的就粗略的去除單詞兩端詞綴的啟發(fā)式過程
- automate(s), automatic, automation -> automat.
- 高高興興 -> 高興 [中文重疊詞還原]
- 明明白白 -> 明白
英文的常見詞干還原算法,Porter算法。
Part2、倒排索引
要了解倒排索引,先看一下什么是正排索引。比如有下面兩句話:
- id1, “搜索引擎提供檢索服務”
- id2, “搜索引擎是信息檢索系統(tǒng)”
正排索引
正排索引就是 MySQL 里的 B+ Tree,索引的結(jié)果是:
- “搜索引擎是信息檢索系統(tǒng)” -> id2
- “搜索引擎提供檢索服務” -> id1
表示對完整內(nèi)容按字典序排序,得到一個有序的列表,以加快檢索的速度。
倒排索引
第一步 分詞
- “搜索引擎-提供-檢索-服務” -> id1
- “搜索引擎-信息-檢索-系統(tǒng)” -> id2
第二步 將分詞項構(gòu)建一個詞典
第三步 構(gòu)建倒排鏈
- 搜索引擎 -> id1, id2
- 提供 -> id1
- 檢索 -> id1, id2
- 服務 -> id1
- 信息 -> id2
- 系統(tǒng) -> id2
由此,一個倒排索引就完成了,搜索 “檢索” 時,得到 id1, id2,說明這兩條數(shù)據(jù)都有,搜索 “服務” 只有 id1 存在。但如果搜索 “檢索系統(tǒng)”,此時會先建搜索詞按照與構(gòu)建同一種策略分詞,得到 “檢索-系統(tǒng)”,兩個詞項,分別搜索 檢索 -> id1, id2 和 系統(tǒng) -> id2,然后對其做一個交集,得到 id2。同理,通過求并集可以支持更復雜的查詢。
倒排索引到此也就講清楚了吧。
存儲結(jié)構(gòu)
以 Lucene 為例,簡單說明一下 Lucene 的存儲結(jié)構(gòu)。從大到小是Index -> Segment -> Doc -> Field -> Term,類比 MySQL 為 Database -> Table -> Record -> Field -> Value。
Part 3、查詢結(jié)果排序
搜索結(jié)果排序是根據(jù) 關(guān)鍵字 和 Document 的相關(guān)性得分排序,通常意義下,除了可以人工的設(shè)置權(quán)重 boost,也存在一套非常有用的相關(guān)性得分算法,看完你會覺得非常有意思。
TF-IDF
TF(詞頻)-IDF(逆文檔頻率) 在自動提取文章關(guān)鍵詞上經(jīng)常用到,通過它可以知道某個關(guān)鍵字在這篇文檔里的重要程度。其中 TF 表示某個 Term 在 Document 里出現(xiàn)的頻次,越高說明越重要;DF 表示在全部 Document 里,共有多少個 Document 出現(xiàn)了這個詞,DF 越大,說明這個詞很常見,并不重要,越小反而說明他越重要,IDF 是 DF 的倒數(shù)(取log), IDF 越大,表示這個詞越重要。
TF-IDF 怎么影響搜索排序,舉一個實際例子來解釋:
假定現(xiàn)在有一篇博客
《Blink 實戰(zhàn)總結(jié)》,我們要統(tǒng)計這篇文章的關(guān)鍵字,首先是對文章分詞統(tǒng)計詞頻,出現(xiàn)次數(shù)最多的詞是--"的"、"是"、"在",這些是“停用詞”,基本上在所有的文章里都會出現(xiàn),他對找到結(jié)果毫無幫助,全部過濾掉。
只考慮剩下的有實際意義的詞,如果文章中詞頻數(shù)關(guān)系: “Blink” > “詞頻” = “總結(jié)”,那么肯定是 Blink 是這篇文章更重要的關(guān)鍵字。但又會遇到了另一個問題,如果發(fā)現(xiàn) "Blink"、"實戰(zhàn)"、"總結(jié)"這三個詞的出現(xiàn)次數(shù)一樣多。這是不是意味著,作為關(guān)鍵詞,它們的重要性是一樣的?
不是的,通過統(tǒng)計全部博客,你發(fā)現(xiàn) 含關(guān)鍵字總博客數(shù): “Blink” < “實戰(zhàn)” < “總結(jié)”,這時候說明 “Blink” 不怎么常見,一旦出現(xiàn),一定相比 “實戰(zhàn)” 和 “總結(jié)”,對這篇文章的重要性更大。
BM25
上面解釋了 TF 和 IDF,那么 TF 和 IDF 誰更重要呢,怎么計算最終的相關(guān)性得分呢?那就是 BM25。
BM25算法,通常用來作搜索相關(guān)性平分。一句話概況其主要思想:對Query進行語素解析,生成語素qi;然后,對于每個搜索結(jié)果D,計算每個語素qi與D的相關(guān)性得分,最后,將qi相對于D的相關(guān)性得分進行加權(quán)求和,從而得到Query與D的相關(guān)性得分。
BM25算法的一般性公式如下:
其中,Q表示Query,qi表示Q解析之后的一個語素(對中文而言,我們可以把對Query的分詞作為語素分析,每個詞看成語素qi。);d表示一個搜索結(jié)果文檔;Wi表示語素qi的權(quán)重;R(qi,d)表示語素qi與文檔d的相關(guān)性得分。
其中 Wi 通常使用 IDF 來表達,R 使用 TF 來表達;綜上,BM25算法的相關(guān)性得分公式可總結(jié)為:
BM25 通過使用不同的語素分析方法、語素權(quán)重判定方法,以及語素與文檔的相關(guān)性判定方法,我們可以衍生出不同的搜索相關(guān)性得分計算方法,這就為我們設(shè)計算法提供了較大的靈活性。
Part 4、空間索引
在點評口碑上,經(jīng)常有類似的場景,搜索 “1公里以內(nèi)的美食”,那么這個1公里怎么實現(xiàn)呢?
在數(shù)據(jù)庫中可以通過暴力計算、矩形過濾、以及B樹對經(jīng)度和維度建索引,但這性能仍然很慢(可參考 為什么需要空間索引 )。搜索里用了一個很巧妙的方法,Geo Hash。
如上圖,表示根據(jù) GeoHash 對北京幾個區(qū)域生成的字符串,有幾個特點:
- 一個字符串,代表一個矩形區(qū)域
- 字符串越長,表示的范圍越精確 (長度為8時精度在19米左右,而當編碼長度為9時精度在2米左右)
- 字符串相似的,表示距離相近 (這就可以利用字符串的前綴匹配來查詢附近的POI信息)
Geo Hash 如何編碼?
地球上任何一個位置都可以用經(jīng)緯度表示,緯度的區(qū)間是 [-90, 90],經(jīng)度的區(qū)間 [-180, 180]。比如天安門的坐標是 39.908,116.397,整體編碼過程如下:
一、對緯度 39.908 的編碼如下:- 將緯度劃分2個區(qū)間,左區(qū)間 [-90, 0) 用 0 表示,右區(qū)間 [0, 90] 用 1 表示, 39.908 處在右區(qū)間,故第一位編碼是 1;
- 在將 [0, 90] 劃分2個區(qū)間,左區(qū)間 [0, 45) 用 0 表示,右區(qū)間 [45, 90] 用 1 表示,39.908處在左區(qū)間, 故第二位編碼是 0;
- 同1、2的計算步驟,39.908 的最后10位編碼是 “10111 00011”
二、對經(jīng)度 116.397 的編碼如下:- 將經(jīng)度劃分2個區(qū)間,左區(qū)間 [-180, 0) 用 0 表示,右區(qū)間 [0, 180] 用 1 表示,116.397處在右區(qū)間, 故第一位編碼是 1;
- 在將 [0, 180] 劃分2個區(qū)間,左區(qū)間 [0, 90) 用 0 表示,右區(qū)間 [90, 180] 用 1 表示,116.397處在右區(qū)間,故第二位編碼是 1;
- 同1、2的計算步驟,116.397 的最后6位編碼是 “11010 01011”
三、合并組碼- 將奇數(shù)位放經(jīng)度,偶數(shù)位放緯度,把2串編碼組合生成新串:“11100 11101 00100 01111”;
- 通過 Base32 編碼,每5個二進制編碼一個數(shù),“28 29 04 15”
- 根據(jù) Base32 表,得到 Geo Hash 為:“WX4G”
即最后天安門的4位 Geo Hash 為 “WX4G”,如果需要經(jīng)度更準確,在對應的經(jīng)緯度編碼粒度再往下追溯即可。
附:Base32 編碼圖
Geo Hash 如何用于地理搜索?
舉個例子,搜索天安門附近 200 米的景點,如下是天安門附近的Geo編碼
搜索過程如下:
- 首先確定天安門的Geo Hash為 WX4G0B,(6位區(qū)域碼約 0.34平分千米,約為長寬600米區(qū)域)
- 而6位編碼表示 600 米,半徑 300 米 > 要求的 200 米,搜索所有編碼為 WX4G0B 的景點即可
- 但是由于天安門處于 WX4G0B 的邊緣位置,并不一定處在正中心。這就需要將 WX4G0B 附近的8個區(qū)域同時納入搜索,故搜索 WX4G0B、WX4G09、WX4G0C 一共9個編碼的景點
- 第3步已經(jīng)將范圍縮小到很小的一個區(qū)間,但是得到的景點距離并不是準確的,需要在通過距離計算過濾出小于 200 米的景點,得到最終結(jié)果。
由上面步驟可以看出,Geo Hash 將原本大量的距離計算,變成一個字符串檢索縮小范圍后,再進行小范圍的距離計算,及快速又準確的進行距離搜索。
Geo Hash 依據(jù)的數(shù)學原理
如圖所示,我們將二進制編碼的結(jié)果填寫到空間中,當將空間劃分為四塊時候,編碼的順序分別是左下角00,左上角01,右下腳10,右上角11,也就是類似于Z的曲線。當我們遞歸的將各個塊分解成更小的子塊時,編碼的順序是自相似的(分形),每一個子快也形成Z曲線,這種類型的曲線被稱為Peano空間填充曲線。
這種類型的空間填充曲線的優(yōu)點是將二維空間轉(zhuǎn)換成一維曲線(事實上是分形維),對大部分而言,編碼相似的距離也相近, 但Peano空間填充曲線最大的缺點就是突變性,有些編碼相鄰但距離卻相差很遠,比如0111與1000,編碼是相鄰的,但距離相差很大。
除Peano空間填充曲線外,還有很多空間填充曲線,如圖所示,其中效果公認較好是Hilbert空間填充曲線,相較于Peano曲線而言,Hilbert曲線沒有較大的突變。為什么GeoHash不選擇Hilbert空間填充曲線呢?可能是Peano曲線思路以及計算上比較簡單吧,事實上,Peano曲線就是一種四叉樹線性編碼方式。
Part 5、數(shù)值索引
Lucene的倒排索引決定,索引內(nèi)容是一個可排序的字符串,如果要查找一個數(shù)字,那么也需要將數(shù)字轉(zhuǎn)成字符串。這樣,檢索一個數(shù)字是沒問題的,如果需要搜索一個數(shù)值范圍,怎么做呢?
要做范圍查找,那么要求數(shù)字轉(zhuǎn)成的字符串也是有序并單調(diào)的,但數(shù)字本身的位數(shù)是不一樣的,最簡單的版本就是前綴補0,比如 35, 234, 1 都補成 4 位,得到 0035, 0234, 0001,這樣能保證:
數(shù)字(a) > 數(shù)字(b) ===> 字符串(a) > 字符串(b)
這時候,查詢應該用范圍內(nèi)的所有數(shù)值或查詢,比如查詢 [33, 36) 這個范圍,對應的查詢語法是:
33 || 34 || 35
嗯看起來很好的解決了范圍查詢,但是,這樣存在3個問題:
- 補位多少合適呢?總有一個數(shù)字會超出你的補位范圍
- 因為存在補位,就會多出很多的空間,這在搜索引擎里寶貴的內(nèi)存是無法接受的
- 如果是范圍查詢,需要用多次或查詢,性能并不高
故,涉及到范圍不能簡單的做字符串補位轉(zhuǎn)換,是否存在及節(jié)省空間,又能更高效解決問題的方案呢?
就是:
數(shù)值Trie樹,下面詳細介紹
上面說了怎么索引,那么Query呢?比如我給你一個Range Query從423-642,怎么找到那6個term呢?
我們首先可以用shift==0找到范圍的起點后終點(有可能沒有相等的,比如搜索422,也會找到423)。然后一直往上找,直到找到一個共同的祖先(肯定能找到,因為樹根是所有葉子節(jié)點的祖先),對應起點,每次往上走的時候, 左邊范圍節(jié)點都要把它右邊的兄弟節(jié)點都加進去, 右邊范圍節(jié)點都要把它左邊的兄弟節(jié)點加進去, 若已經(jīng)到達頂點, 則是將左邊范圍節(jié)點和右邊范圍節(jié)點之間的節(jié)點加進行去
查找423到642之間的具體的區(qū)間:
- 423-429,640-642
- 43-49,60-63
- 5-5
另外還有一個問題,比如423會被分詞成423,42和4,那么4也會被分詞成4,那么4表示哪個呢?
所以intToPrefixCoded方法會額外用一個char來保存shift:buffer[0] = (char)(SHIFT_START_INT + shift);
比如423分詞的4的shift是2(這里是10進制的例子,二進制也是同樣的),423分成423的shift是0,4的shift是0,因此前綴肯定比后綴大。
最后,由于索引在判斷時無需感知是否是數(shù)字,可以把所有的數(shù)字當成二進制處理,這樣在存儲和效率上更高。
三、搜索引擎的極致優(yōu)化
LSM思想
LSM (Log Structured Merge Tree),最早是谷歌的 “BigTable” 提出來的,目標是保證寫入性能,同時又能支持較高效率的檢索,在很多 NoSQL 中都有使用,Lucene 也是使用 LSM 思想來寫入。
普通的B+樹增加記錄可能需要執(zhí)行 seek+update 操作,這需要大量磁盤尋道移動磁頭。而 LSM 采用記錄在文件末尾,順序?qū)懭霚p少移動磁頭/尋道,執(zhí)行效率高于 B+樹。具體 LSM 的原理是什么呢?
為了保持磁盤的IO效率,lucene避免對索引文件的直接修改,所有的索引文件一旦生成,就是只讀,不能被改變的。其操作過程如下:
- 在內(nèi)存中保存新增的索引, 內(nèi)存緩存(也就是memtable);
- 內(nèi)存中的索引數(shù)量達到一定閾值時,觸發(fā)寫操作,將這部分數(shù)據(jù)批量寫入新文件,我們稱為segment;也就是 sstable文件
- 新增的segment生成后,不能被修改;
- update操作和delete操作不會立即導致原有的數(shù)據(jù)被修改或者刪除,會以append的方式存儲update和delete標記;
- 最終得到大量的 segment,為了減少資源占用,也提高檢索效率,會定期的將這些小的 segment 合并成大的 segment,由于map中的數(shù)據(jù)都是排好序的,所以合并也不會有隨機寫操作;
- 通過merge,還可以把update和delete操作真正生效,刪除多余的數(shù)據(jù),節(jié)省空間。
合并的過程:
Basic Compaction
每個文件固定N個數(shù)量,超過N,則新建一個sstable;當sstable數(shù)大于M,則合并一個大sstable;當大sstable的數(shù)量大于M,則合并一個更大的sstable文件,依次類推。
但是,這會出現(xiàn)一個問題,就是大量的文件被創(chuàng)建,在最壞的情況下,所有的文件都要搜索。
Levelled Compaction
像 LevelDB 和 Cassandra解決這個問題的方法是:實現(xiàn)了一個分層的,而不是根據(jù)文件大小來執(zhí)行合并操作。
- 每層維護指定數(shù)量的文件,保證不讓 key 重疊,查找一個 key 只會查找一個 key;
- 每次文件只會被合并到上一層的一個文件。當一層的文件數(shù)滿足特定個數(shù)時,合并到上一層。
所以, LSM 是日志和傳統(tǒng)的單文件索引(B+ tree,Hash Index)的中立,他提供一個機制來管理更小的獨立的索引文件(sstable)。
通過管理一組索引文件而不是單一的索引文件,LSM 將B+樹等結(jié)構(gòu)昂貴的隨機IO變的更快,而代價就是讀操作要處理大量的索引文件(sstable)而不是一個,另外還是一些IO被合并操作消耗。
Lucene的Segment設(shè)計思想,與LSM類似但又有些不同,繼承了LSM中數(shù)據(jù)寫入的優(yōu)點,但是在查詢上只能提供近實時而非實時查詢。
Segment在被flush或commit之前,數(shù)據(jù)保存在內(nèi)存中,是不可被搜索的,這也就是為什么Lucene被稱為提供近實時而非實時查詢的原因。讀了它的代碼后,發(fā)現(xiàn)它并不是不能實現(xiàn)數(shù)據(jù)寫入即可查,只是實現(xiàn)起來比較復雜。原因是Lucene中數(shù)據(jù)搜索依賴構(gòu)建的索引(例如倒排依賴Term Dictionary),Lucene中對數(shù)據(jù)索引的構(gòu)建會在Segment flush時,而非實時構(gòu)建,目的是為了構(gòu)建最高效索引。當然它可引入另外一套索引機制,在數(shù)據(jù)實時寫入時即構(gòu)建,但這套索引實現(xiàn)會與當前Segment內(nèi)索引不同,需要引入額外的寫入時索引以及另外一套查詢機制,有一定復雜度。
FST
數(shù)據(jù)字典 Term Dictionary,通常要從數(shù)據(jù)字典找到指定的詞的方法是,將所有詞排序,用二分查找即可。這種方式的時間復雜度是 Log(N),占用空間大小是 O(N*len(term))。缺點是消耗內(nèi)存,存在完整的term,當 term 數(shù)達到上千萬時,占用內(nèi)存非常大。
lucene從4開始大量使用的數(shù)據(jù)結(jié)構(gòu)是FST(Finite State Transducer)。FST有兩個優(yōu)點:
- 空間占用小,通過讀 term 拆分復用及前綴和后綴的重用,壓縮了存儲空間;
- 查詢速度快,查詢僅有 O(len(term)) 時間復雜度
那么 FST 數(shù)據(jù)結(jié)構(gòu)是什么原理呢? 先來看看什么是 FSM (Finite State Machine), 有限狀態(tài)機,從“起始狀態(tài)”到“終止狀態(tài)”,可接受一個字符后,自循環(huán)或轉(zhuǎn)移到下一個狀態(tài)。
而FST呢,就是一種特殊的 FSM,在 Lucene 中用來實現(xiàn)字典查找功能(NLP中還可以做轉(zhuǎn)換功能),F(xiàn)ST 可以表示成FST的形式
舉例:對“cat”、 “deep”、 “do”、 “dog” 、“dogs” 這5個單詞構(gòu)建FST(注:必須已排序),結(jié)構(gòu)如下:
當存在 value 為對應的 docId 時,如 cat/0 deep/1 do/2 dog/3 dogs/4, FST 結(jié)構(gòu)圖如下:
FST 還有一個特點,就是在前綴公用的基礎(chǔ)上,還會做一個后綴公用,目標同樣是為了壓縮存儲空間。
其中紅色的弧線表 NEXT-optimized,可以通過 畫圖工具 來測試。
SkipList
為了能夠快速查找docid,lucene采用了SkipList這一數(shù)據(jù)結(jié)構(gòu)。SkipList有以下幾個特征:
- 元素排序的,對應到我們的倒排鏈,lucene是按照docid進行排序,從小到大;
- 跳躍有一個固定的間隔,這個是需要建立SkipList的時候指定好,例如下圖以間隔是;
- SkipList的層次,這個是指整個SkipList有幾層
在什么位置設(shè)置跳表指針?
? 設(shè)置較多的指針,較短的步長, 更多的跳躍機會
? 更多的指針比較次數(shù)和更多的存儲空間
? 設(shè)置較少的指針,較少的指針比較次數(shù),但是需要設(shè)置較長的步長?較少的連續(xù)跳躍
如果倒排表的長度是L,那么在每隔一個步長S處均勻放置跳表指針。
BKD Tree
也叫 Block KD-tree,根據(jù)FST思路,如果查詢條件非常多,需要對每個條件根據(jù) FST 查出結(jié)果,進行求并集操作。如果是數(shù)值類型,那么潛在的 Term 可能非常多,查詢銷量也會很低,為了支持高效的數(shù)值類或者多維度查詢,引入 BKD Tree。在一維下就是一棵二叉搜索樹,在二維下是如果要查詢一個區(qū)間,logN的復雜度就可以訪問到葉子節(jié)點對應的倒排鏈。
- 確定切分維度,這里維度的選取順序是數(shù)據(jù)在這個維度方法最大的維度優(yōu)先。一個直接的理解就是,數(shù)據(jù)分散越開的維度,我們優(yōu)先切分。
- 切分點的選這個維度最中間的點。
- 遞歸進行步驟1,2,我們可以設(shè)置一個閾值,點的數(shù)目少于多少后就不再切分,直到所有的點都切分好停止。
BitSet 過濾
二進制處理,通過BKD-Tree查找到的docID是無序的,所以要么先轉(zhuǎn)成有序的docID數(shù)組,或者構(gòu)造BitSet,然后再與其他結(jié)果合并。
IndexSorting
IndexSorting是一種預排序,在ES6.0之后才有,與查詢時的Sort不同,IndexSorting是一種預排序,即數(shù)據(jù)預先按照某種方式進行排序,它是Index的一個設(shè)置,不可更改。
一個Segment中的每個文檔,都會被分配一個docID,docID從0開始,順序分配。在沒有IndexSorting時,docID是按照文檔寫入的順序進行分配的,在設(shè)置了IndexSorting之后,docID的順序就與IndexSorting的順序一致。
舉個例子來說,假如文檔中有一列為Timestamp,我們在IndexSorting中設(shè)置按照Timestamp逆序排序,那么在一個Segment內(nèi),docID越小,對應的文檔的Timestamp越大,即按照Timestamp從大到小的順序分配docID。
IndexSorting 之所以可以優(yōu)化性能,是因為可以提前中斷以及提高數(shù)據(jù)壓縮率,但是他并不能滿足所有的場景,比如使用非預排序字段排序,還會損耗寫入時的性能。
搜索引擎正是靠優(yōu)秀的理論加極致的優(yōu)化,做到查詢性能上的極致,后續(xù)會再結(jié)合源碼分析壓縮算法如何做到極致的性能優(yōu)化的。
附:進一步閱讀http://lucene.apache.org/https://wiki.apache.org/lucene-java/FrontPagehttps://zhuanlan.zhihu.com/p/35814539http://www.runoob.com/java/java-bitset-class.htmlhttps://www.cnblogs.com/skycore/p/5093257.htmlhttps://www.cnblogs.com/LBSer/p/4119841.htmlhttps://blog.csdn.net/zhufenglonglove/article/details/51700898https://www.jianshu.com/p/1e498888f505http://www.nosqlnotes.com/technotes/searchengine/lucene-invertedindex/https://www.jianshu.com/p/69d56f9c0576添加Java高級架構(gòu)交流群 383681414關(guān)注微信公眾號“托尼的技術(shù)成長之路”