1、什么是檢索?
指從用戶特定的信息需求出發,對特定的信息集合采用一定的方法、技術手段,根據一定的線索與規則從中找出相關信息。
對應到我們實際工作中,檢索其實就是:
如何用最小的內存(物理成本),最快(時間成本)的取出我們需要的數據。
2、檢索體系架構
3、存儲介質層
3.1 磁盤為什么能存儲數據

機械硬盤的磁盤主體是一塊金屬薄片(也有用其他材料的),上面塗覆一層磁性材料,可以理解為一層小磁針。
硬盤工作時,磁盤在馬達的驅動下高速旋轉,轉速高達數千轉每分鍾,磁頭則在磁頭驅動系統的的控制下,在高速旋轉的磁盤表面飛行。
①、寫數據
磁頭線圈上通電,在其周圍產生磁場,磁化磁盤表面的磁性材料,不同方向的電流產生的磁場方向不同,磁盤表面的磁性材料被磁化的極性也不同,不同極性便代表0與1;
②、讀數據
磁頭線圈切割磁盤表面的磁性材料的磁場,產生電信號,不同極性的磁性材料產生的感應電流方向不同,因此可以讀出0與1。
注意:斷電並不會影響磁盤表面的磁性材料的極性,因此斷電后數據仍然不會消失,但劇烈的碰撞或加熱則有可能導致數據丟失。
3.2 磁盤和內存的區別
①、持久性
磁盤能永久存儲(HDD10年,SDD5年),斷電不丟失數據;
內存斷電即丟失數據。
②、容量
磁盤通常是幾百G到幾個T;
內存通常是幾個G到幾十個G。
③、價格
內存 > 磁盤
④、讀寫速度
內存 > SDD > HDD
4、數據結構層
4.1 數組
1.數組是相同數據類型的元素的集合。
2.數組各元素是按照先后順序連續存儲的。
3.插入慢:無序數組末尾插入快,其余情況需要維護數組地址連續效率都是比較差。
4.查找:支持下標隨機查找快,有序數組也可以用諸如二分法加快查找速度。
5.刪除慢:和插入類似,除了末尾插入快。其余情況需要維護數組地址連續都比較慢。
4.2 鏈表
1.鏈表物理存儲單元上非連續(可以充分利用計算機內存)、非順序的存儲結構。
2.不支持隨機讀取。
3.存儲空間會增大,比如單向鏈表每個節點都會存儲下一個節點的引用。
4.插入快。
5.刪除快。
6.查找慢。
其余比如棧、隊列、二叉樹,紅黑樹,B+樹等等都是這兩種數據結構的單獨變化或組合變化。
4.3 棧
棧只支持兩個基本操作:入棧 push()和出棧 pop()。
典型應用:
①、實現字符串逆序;
②、判斷標簽是否匹配;
③、計算機中的函數調用;
4.4 隊列
和棧類似,也只支持兩個操作:入隊 enqueue(),放一個數據到隊列尾部;出隊 dequeue(),從隊列頭部取一個元素。


①、單向隊列(Queue):只能在一端插入數據,另一端刪除數據。
②、雙向隊列(Deque):每一端都可以進行插入數據和刪除數據操作。
③、優先級隊列(Priority Queue):數據項按照關鍵字進行排序,關鍵字最小(或者最大)的數據項往往在隊列的最前面,而數據項在插入的時候都會插入到合適的位置以確保隊列的有序。
④、阻塞隊列(Block Queue):在隊列為空的時候,從隊頭取數據會被阻塞。因為此時還沒有數據可取,直到隊列中有了數據才能返回;如果隊列已經滿了,那么插入數據的操作就會被阻塞,直到隊列中有空閑位置后再插入數據,然后再返回。
典型的生產者-消費者模型。
⑤、並發隊列
典型應用:
①、線程池
②、數據庫連接池
對於大部分資源有限的場景,當沒有空閑資源時,基本上都可以通過“隊列”這種數據結構來實現請求排隊。
4.5 樹
鏈表的插入和刪除比較快,但是查找卻比較慢,因為不管我們查找什么數據,都需要從鏈表的第一個數據項開始,遍歷到找到所需數據項為止,這個查找也是平均需要比較N/2次。
那么有沒有一種數據結構能同時具備數組查找快的優點以及鏈表插入和刪除快的優點,於是 樹 誕生了。
①、二叉樹
二叉樹的相關介紹:https://www.cnblogs.com/ysocean/p/8032642.html
二叉樹的每個節點最多只能有兩個子節點,如下圖:

如果左節點比根節點小,右節點比根節點大,那就是平衡二叉樹。二叉搜索樹的效率在O(N)和O(logN)之間,取決於樹的不平衡程度。最差也會退化成一個鏈表。
②、紅黑樹
為了避免二叉樹退化成鏈表,需要盡量保證樹的平衡,於是有了 紅黑樹。
紅黑樹的相關介紹:https://www.cnblogs.com/ysocean/p/8004211.html

③、B+樹
上面說的樹每個節點都最多只能有兩個子節點,有些情況下,數據量特別大,會導致樹的高度很大,這會導致我們查找某個數據需要多次IO,要知道 IO 相對而言是很慢的,有沒有可能每個節點能有很多字節點呢?
類似 B+ 樹,2-3-4 樹誕生了。
典型應用:關系型數據庫存儲數據結構。
1.數據很大,不可能全部存儲在內存中,還要持久化,故要存儲到磁盤上。
2.減少查找過程中磁盤I/O的存取次數。
局部性原理:當一個數據被用到時,其附近的數據也通常會馬上被使用。
與磁盤預讀,預讀的長度一般為頁(page)的整倍數,(在許多操作系統中,頁得大小通常為4k)
葉子節點數據多。
3.支持范圍查找
④、LSM 樹
Log Structured Merge Trees
一個日志系統有如下特征:
一、數據量大且持續生成
二、寫入操作會特別頻繁
三、查詢快,且查詢一般不是全范圍的隨機搜索,而是近期檢索。
B+ 樹為什么不行?(葉子節點存儲在磁盤中,需要隨機寫磁盤,數據量大會導致性能急劇下降)
LSM 樹:
內存樹存放近期寫入的數據,有序且支持更新,支持隨時查詢。磁盤樹則通常有多個,順序寫入。
⑤、Trie 樹
字典樹、前綴樹、單詞查找樹。

典型應用:
字符串檢索
百度谷歌搜索框
拼寫檢查
4.6 跳表
鏈表的基礎上增加了多級索引。
Redis 中的有序集合(Sorted Set)就是用跳表來實現的。
4.7 散列表
散列表相關介紹:https://www.cnblogs.com/ysocean/p/8032656.html
通過把關鍵值映射到表中一個位置來訪問記錄,這個映射函數叫做散列函數,存放記錄的數組叫做散列表。
解決哈希沖突:
①、開放尋址法:線性探測、雙重散列
②、鏈表法
散列表設計原則:
①、散列函數
②、初始容量;
③、裝載因子;
④、散列沖突解決辦法;
典型應用:
①、有限的數據集合中快速查詢數據
比如:Word 文檔中單詞拼寫檢查功能是如何實現的?
常用的英文單詞有 20 萬個左右,假設單詞的平均長度是 10 個字母,平均一個單詞占用 10 個字節的內存空間,那 20 萬英文單詞大約占 2MB 的存儲空間,就算放大 10 倍也就是 20MB。
所以可以將全部英文單詞放到散列表,用戶輸入單詞直接去散列表里面查,沒有就報錯。
②、詞頻統計、訪問統計等等。
4.8 布隆過濾器
布隆過濾器相關介紹:https://www.cnblogs.com/ysocean/p/12594982.html
簡單來說就是一個二進制數組。
典型應用:數據海量,不要求一定准確的場景。
①、判斷ID是否已經注冊,即使誤判也能容忍。
②、爬蟲判斷網頁是否已經爬過。
4.9 圖

存儲:
①、鄰接矩陣

②、鄰接表

DFS(Deep First Search)深度優先搜索算法
BFS(Breath First Search)廣度優先搜索算法
飛機航線
電子線路
城市地圖
好友關系
5、算法層
比較好用的查找算法是二分法O(logn),在有序的數據結構中是特別bug的,但是如何進行快速的排序,有如下常用的排序算法:
實際應用:
①、如何根據年齡給100W用戶排序?
利用桶排序,從1歲到150歲(有人會說超過150歲,這里超過三界之外的人不算),建立150個桶,然后遍歷這100W個用戶,依次放入150個桶中,遍歷完,邊排好序了。
②、如何快速查詢每個考生的高考排名?
同樣也是桶排序,高考分數0-750,也就是頂多 750 個桶。
6、業務設計層
6.1 爬蟲系統
通過高性能的爬蟲系統來完成網頁的持續抓取,然后將抓取到的網頁存入存儲平台中。
一般來說是是將抓取到的網頁存放在基於 LSM 的 HBase 中,以便支持數據的高效讀寫。
①、爬取網頁
首先找到權重較高的網頁,比如新浪、騰訊,通過廣度優先搜索算法放入爬取隊列中;
計算網頁權重算法:PageRank
網頁太多,持久化隊列,便於斷點爬取。
如何爬取網頁鏈接:可以獲取到網頁的 HTML 文件,看成一個大的字符串,然后利用字符串匹配算法,獲取 或者 這樣的標簽內容。
②、網頁去重
利用布隆過濾器。
需要注意的是:布隆過濾器是在內存中的,如果機器重啟,布隆過濾器就會被清空,防止網頁重復爬取,需要持久化布隆過濾器,比如定時每半小時持久化一次。
③、原始網頁存儲
便於后面的離線分析,索引構建,需要將海量的原始網頁存儲。
網頁很多,通常的文件系統不適合存儲這么多的文件,而是將多個網頁存儲在一個文件中。

④、網頁編號和鏈接存儲
上一步給每個網頁分配了一個id,在存儲網頁的同時,也將網頁編號和網頁鏈接存儲在一個文件中。
6.2 分析索引系統
①、抽取網頁文本信息
網頁都是遵循 HTML 規范的,只需要去掉JavaScript代碼、CSS代碼,還有比如下拉框的代碼。
在網頁這個大字符串中,一次性查找 , , </option)為止。而這期間遍歷到的字符串連帶着標簽就應該從網頁中刪除。
②、網頁質量分析
去掉低質量的垃圾網頁
③、反作弊
避免一些作弊網頁來干擾搜索結果
④、分詞創建臨時索引
抽取到網頁文本信息之后,對文本信息進行分詞,並創建臨時索引文件。
英文網頁:只需要通過空格、標點符號等分隔符,將每個單詞分割開來就可以了。
中文網頁:借助詞庫並采用最長匹配規則,來對文本進行分詞。
臨時索引文件如下:

注意這里存的是單詞編號,因為單詞很多,為了節省內存,用一個散列表存儲:單詞編號-單詞。
⑤、通過臨時索引創建倒排索引

⑥、記錄單詞編號在倒排索引文件的偏移位置
幫助我們快速地查找某個單詞編號在倒排索引中存儲的位置,進而快速地從倒排索引中讀取單詞編號對應的網頁編號列表。

6.3 查詢
doc_id.bin:記錄網頁鏈接和編號之間的對應關系。
term_id.bin:記錄單詞和編號之間的對應關系。
index.bin:倒排索引文件,記錄每個單詞編號以及對應包含它的網頁編號列表。
term_offsert.bin:記錄每個單詞編號在倒排索引文件中的偏移位置。
①、當用戶在搜索框中,輸入某個查詢文本的時候,我們先對用戶輸入的文本進行分詞處理。假設分詞之后,我們得到 k 個單詞。
然后對這 k 個單詞進行糾錯模型判斷:
②、糾錯完成之后,我們拿這 k 個單詞,去 term_id.bin 對應的散列表中,查找對應的單詞編號。經過這個查詢之后,我們得到了這 k 個單詞對應的單詞編號。
③、我們拿這 k 個單詞編號,去 term_offset.bin 對應的散列表中,查找每個單詞編號在倒排索引文件中的偏移位置。經過這個查詢之后,我們得到了 k 個偏移位置。
④、我們拿這 k 個偏移位置,去倒排索引(index.bin)中,查找 k 個單詞對應的包含它的網頁編號列表。經過這一步查詢之后,我們得到了 k 個網頁編號列表。
⑤、我們針對這 k 個網頁編號列表,統計每個網頁編號出現的次數。具體到實現層面,我們可以借助散列表來進行統計。統計得到的結果,我們按照出現次數的多少,從小到大排序。出現次數越多,說明包含越多的用戶查詢單詞(用戶輸入的搜索文本,經過分詞之后的單詞)。
經過這一系列查詢,我們就得到了一組排好序的網頁編號。我們拿着網頁編號,去 doc_id.bin 文件中查找對應的網頁鏈接,分頁顯示給用戶就可以了。
10、總結
檢索核心思路:通過合理的組織數據,盡可能的快速減少查詢范圍。
①、合理選擇存儲介質、存儲數據結構;
②、合理創建索引,使得索引和數據分離;
③、減少磁盤IO,將頻繁讀取的數據加載到內存中;
④、讀寫分離;
⑤、分層處理;
參考文檔:極客時間《數據結構與算法之美》
數組:https://www.cnblogs.com/ysocean/p/7894448.html
鏈表:https://www.cnblogs.com/ysocean/p/7928988.html
棧:https://www.cnblogs.com/ysocean/p/7911910.html
隊列:https://www.cnblogs.com/ysocean/p/7921930.html
二叉樹:https://www.cnblogs.com/ysocean/p/8032642.html
紅黑樹:https://www.cnblogs.com/ysocean/p/8004211.html
2-3-4樹:https://www.cnblogs.com/ysocean/p/8032648.html
哈希表:https://www.cnblogs.com/ysocean/p/8032656.html