倒排索引
搜索引擎如何工作?
信息檢索已經發展的非常成熟了,應該所有人都不陌生。我有幸這幾年接觸過並且實際做過一些搜索引擎開發的工作,特此總結並分享給大家。實際上,一個成熟的搜索引擎是想當復雜的,比如百度的,就分nginx,vui,us,as,bs,da.....等等這些模塊,當然這些簡寫的字母大家也不必了解,只要知道它確實復雜就可以。
今天我所講的是一個簡化版的搜索引擎,簡化到只涉及到倒排建立和拉取。雖然簡單,但是它是整個搜索引擎的最核心組件。一個最簡單的搜索引擎如下圖所示:
- merger: 接收查詢請求,分詞后請求下游Indexer分別獲取各個indexer的局部TopK文檔,歸攏后排序返回全局相似度最高的TopK文檔。
- indexer:負責倒排拉取,並利用夾角余弦算法計算相似度,返回TopK結果。夾角余弦可以在 http://www.cnblogs.com/haolujun/p/8011776.html 中了解。
- index:倒排索引。
倒排索引長成什么樣子呢?就是圖中標記的那樣,每個詞后面有一個拉鏈,拉鏈中存放包含該詞的文檔編號,利用這個數據結構能快速的找到包含某一個詞的所有文檔。
今天介紹則是搜索引擎的核心中的核心:倒排索引。接下來的所有內容都圍繞着倒排索引展開。
如何建立倒排索引
對幾億甚至幾十億幾百億規模的文檔集合建立倒排索引並不是一件很輕松的事情,它將面對着IO以及CPU計算的雙重瓶頸,需要根據實際情況找到最優方法,接下來介紹兩種不同的建立倒排的方式。
單遍內存型
內存中維護每個詞對應的文檔編號列表,當一個詞對應的buffer滿時,把內存中的數據flush到磁盤上,這樣每個詞對應一個文件。最后按照詞編號由小到大的合並所有文件,得到最終的倒排索引。
- 優點:使用內存存放倒排索引,並分批flush到磁盤。這樣減少了一些排序操作,速度快。
- 缺點:內存使用量稍大。可以減少內存buffer大小來降低內存使用量,但是這樣做得缺點就是增加了磁盤隨機寫的次數。想想,每當一個buffer滿時都會寫對應的文件,並且buffer是隨機的,所以增加了磁盤尋址的開銷。
多路並歸型
步驟如下:
- 首先,解析文檔,把<詞,文檔編號,tf>寫入到磁盤文件。
- 然后,對磁盤文件進行外部排序,排序規則:按照詞的字典序從小到大排序,如果詞相同,則按照文檔編號從小到大排序,這樣相同的詞就排到了一起。
- 最后,順序掃描排序后的文件,建立倒排索引。由於相同的詞緊挨在一起,可以一個詞一個詞的建立倒排索引。
與內存型相比,這種方式適合在內存小,磁盤大的情況下進行倒排索引的建立,它的優缺點如下。
- 優點:內存使用量小,沒有磁盤隨機讀寫,基本全是順序讀寫。對於超大規模文檔集合來說,這種方式相對靈活一些(主要是排序靈活)。
- 缺點:多次歸並排序,比較慢。但是,可以使用並發進行歸並排序,這樣能夠提高一些速度。
索引切分
考慮到在海量文檔下,倒排索引非常大,單台機器無法在內存中裝下全部索引,所以有必要把索引進行切分,使得每一個索引服務只對文檔中一部分的內容進行拉取、計算。常見的有兩種可選擇的方式。
按文檔編號切
按照文檔編號把文檔分成幾個小的集合,對每個小的文檔集合單獨建立索引。在這種方式建立索引上進行查詢時,merge需要把查詢請求下發到所有的后端indexer服務(因為每個index都有可能存在包含查詢詞的文檔),indexer服務的計算量比較大。但是它也有一個優點:每個詞的倒排拉鏈的長度可控。
按term切分
按照詞進行索引划分,每個索引只保存若干詞的所有文檔編號。在這種方式建立的索引上進行查詢時,merge可以根據查詢詞精確的把請求下發到對應的indexer上,減少了后端indexer的計算量。
但是這么做引入幾個新問題:
- 1:某些高頻詞倒排拉鏈過長,導致這台indexer計算時間超出可忍受范圍,出現拖后腿現象;
- 2:維護詞表與indexer的對應關系,運維復雜;
- 3:對於熱詞所在的索引,對應的indexer請求量大,計算量大,負載高,需要考慮熱詞打散。
那么在實際中該如何進行索引切分呢?主要看是什么類型的查詢、查詢的量、以及文檔集合的規模。
- 對於普通搜索引擎,用戶輸入的是數量較少的查詢詞,按照term切分可以有效減少查詢時后端indexer的計算量,收益比較大。
- 對於現在流行的拍照搜題,拍攝的圖片中文字一般較多,按照term切分的優勢不明顯而且引入了復雜的運維,建議按照文檔編號切分。
- 對於高頻詞出現的文檔,可以把這些文檔選出來,然后按文檔切分的方式建立索引,這樣不會出現倒排拉鏈過長。總結下來就是:高頻詞按文檔切分,低頻詞按照term切分,檢索的時候,根據查詢詞是否是高頻詞執行不同的檢索策略。
- 在不差錢並且文檔規模不大,並且查詢量沒有達到必須優化索引的前提下,盡量使用運維更簡單的索引:按照文檔編號切分。
增量索引
很多搜索引擎都注重時效性搜索,比如對於時下剛剛發生的某件熱門事件,需要搜索引擎能夠第一時間搜索到該熱門事件的頁面,這該如何做到呢?由於建立一次全量文檔倒排的時間基本都是按天計,如果不設計一些實時增量索引,那么根本滿足不了時效性的檢索。下面介紹增量索引,可以解決時效性搜索問題。
倒排索引雙buffer設計方案
增量步驟:
- 1:indexer從索引2切換到索引1
- 2:更新索引2
- 3:indexer從索引1切回到索引2
- 4:更新索引1
這樣可以保證實時的動態更新,但是它的缺點也很明顯:必須使用2倍索引大小的內存,機器成本比較高,實際中更常用的是下面一種方案。
增量索引服務+雙buffer方案
全量索引服務用來查詢截止到某一個日期的全部文檔,增量索引服務使用雙buffer設計方案查詢最近一段時間(可能是小時級或者分鍾級)內實時更新的文檔內容,然后定期(每天、每周、每月一次)把最近一段時間更新的文檔追加在全量索引中。這樣做的好處就在於只有少量近期更新文檔的查詢需要使用雙倍內存,機器成本降低。需要注意的一點是,用這種方式建立增量索引時,必須更新全局word的df信息,對於發現的新詞還需為其添加全局唯一id,這些信息統統要更新到線上正在運行的全量索引服務。
利用Hadoop並行建立倒排索引
對於超大規模的文檔集合,可以使用Hadoop建立倒排索引。
- map端讀入每個文檔並進行解析,生成一個tuple,key為詞,value為tf+文檔編號,發送該tuple給下游reduce,這樣相同的詞會分配給同一個reduce。
- reduce保存每個tuple,並在結束時對每個詞倒排按照文檔編號由小到大排序,並保存到對應的文件中。
- 有多少個reduce就會有多少個索引文件,最后匯總這些文件得到最終的倒排索引。
實際工作中,為了增加map端讀數據性能,並不是每個文檔存放成單獨一個文件,而是先把文檔序列化成文件中的一行,這樣每個文件可以存放多個文檔內容,這就減少了小文件數,增加了map端的吞吐量。
總結
倒排的建立還有查詢涉及到的技術內容遠遠不止於此,在這里可以推薦兩本書給大家,有興趣的小伙伴可以進行深入的學習,共同進步。
-《深入搜索引擎-海量信息的壓縮、索引和查詢》 Lan H.Witten, Alistair Moffat, Timothy C.Bell著,梁斌譯。
-《信息檢索導論》 Christopher D.Manning, Prabhakar Raghavan, Hinrich Schutze著,王斌譯。