前面咱們說海量數據處理提到,從算法的角度去考慮處理海量數據。算法
【Bloom Filter】
Bloom Filter(BF)是一種空間效率很高的隨機數據結構,它利用位數組很簡潔地表示一個集合,並能判斷一個元素是否屬於這個集合。它是一個判斷元素是否存在集合的快速的機率算法。Bloom Filter有可能會出現錯誤判斷,但不會漏掉判斷。也就是Bloom Filter判斷元素再也不集合,那確定不在。若是判斷元素存在集合中,有必定的機率判斷錯誤。所以,Bloom Filter不適合那些「零錯誤」的應用場合。數據庫
而在能容忍低錯誤率的應用場合下,Bloom Filter比其餘常見的算法(如hash,折半查找)極大節省了空間。 數組
Bloom Filter的詳細介紹:海量數據處理之Bloom Filter詳解緩存
【適用範圍】
能夠用來實現數據字典,進行數據的判重,或者集合求交集 安全
【基本原理及要點】 性能優化
原理要點:一是位數組, 而是k個獨立hash函數。服務器
1)位數組:網絡
假設Bloom Filter使用一個m比特的數組來保存信息,初始狀態時,Bloom Filter是一個包含m位的位數組,每一位都置爲0,即BF整個數組的元素都設置爲0。數據結構
2)k個獨立hash函數架構
爲了表達S={x1, x2,…,xn}這樣一個n個元素的集合,Bloom Filter使用k個相互獨立的哈希函數(Hash Function),它們分別將集合中的每一個元素映射到{1,…,m}的範圍中。
當咱們往Bloom Filter中增長任意一個元素x時候,咱們使用k個哈希函數獲得k個哈希值,而後將數組中對應的比特位設置爲1。即第i個哈希函數映射的位置hashi(x)就會被置爲1(1≤i≤k)。 注意,若是一個位置屢次被置爲1,那麼只有第一次會起做用,後面幾回將沒有任何效果。在下圖中,k=3,且有兩個哈希函數選中同一個位置(從左邊數第五位,即第二個「1「處)。
3)判斷元素是否存在集合
在判斷y是否屬於這個集合時,咱們只須要對y使用k個哈希函數獲得k個哈希值,若是全部hashi(y)的位置都是1(1≤i≤k),即k個位置都被設置爲1了,那麼咱們就認爲y是集合中的元素,不然就認爲y不是集合中的元素。下圖中y1就不是集合中的元素(由於y1有一處指向了「0」位)。y2或者屬於這個集合,或者恰好是一個false positive。
顯然這 個判斷並不保證查找的結果是100%正確的。
Bloom Filter的缺點:
1)Bloom Filter沒法從Bloom Filter集合中刪除一個元素。由於該元素對應的位會牽動到其餘的元素。因此一個簡單的改進就是 counting Bloom filter,用一個counter數組代替位數組,就能夠支持刪除了。 此外,Bloom Filter的hash函數選擇會影響算法的效果。
2)還有一個比較重要的問題,如何根據輸入元素個數n,肯定位數組m的大小及hash函數個數,即hash函數選擇會影響算法的效果。當hash函數個數k=(ln2)*(m/n)時錯誤率最小。在錯誤率不大於E的狀況 下,m至少要等於n*lg(1/E) 才能表示任意n個元素的集合。但m還應該更大些,由於還要保證bit數組裏至少一半爲0,則m應 該>=nlg(1/E)*lge ,大概就是nlg(1/E)1.44倍(lg表示以2爲底的對數)。
舉個例子咱們假設錯誤率爲0.01,則此時m應大概是n的13倍。這樣k大概是8個。
注意:
這裏m與n的單位不一樣,m是bit爲單位,而n則是以元素個數爲單位(準確的說是不一樣元素的個數)。一般單個元素的長度都是有不少bit的。因此使用bloom filter內存上一般都是節省的。
通常BF能夠與一些key-value的數據庫一塊兒使用,來加快查詢。因爲BF所用的空間很是小,全部BF能夠常駐內存。這樣子的話,對於大部分不存在的元素,咱們只須要訪問內存中的BF就能夠判斷出來了,只有一小部分,咱們須要訪問在硬盤上的key-value數據庫。從而大大地提升了效率。
【擴展】
【問題實例】
給你A,B兩個文件,各存放50億條URL,每條URL佔用64字節,內存限制是4G,讓你找出A,B文件共同的URL。若是是三個乃至n個文件呢?
根據這個問題咱們來計算下內存的佔用,4G=2^32大概是40億*8大概是340億bit,n=50億,若是按出錯率0.01算須要的大概是650億個bit。 如今可用的是340億,相差並很少,這樣可能會使出錯率上升些。另外若是這些urlip是一一對應的,就能夠轉換成ip,則大大簡單了。
【什麼是Hash】
Hash,通常翻譯作「散列」,也有直接音譯爲「哈希」的,就是把任意長度的輸入(又叫作預映射, pre-image),經過散列算法,變換成固定長度的輸出,該輸出就是散列值。這種轉換是一種壓縮映射,也就是,散列值的空間一般遠小於輸入的空間,不一樣的輸入可能會散列成相同的輸出,而不可能從散列值來惟一的肯定輸入值。簡單的說就是一種將任意長度的消息壓縮到某一固定長度的消息摘要的函數。
HASH主要用於信息安全領域中加密算法,它把一些不一樣長度的信息轉化成雜亂的128位的編碼,這些編碼值叫作HASH值. 也能夠說,hash就是找到一種數據內容和數據存放地址之間的映射關係。
數組的特色是:尋址容易,插入和刪除困難;而鏈表的特色是:尋址困難,插入和刪除容易。那麼咱們能不能綜合二者的特性,作出一種尋址容易,插入刪除也容易的數據結構?答案是確定的,這就是咱們要提起的哈希表,哈希表有多種不一樣的實現方法,我接下來解釋的是最經常使用的一種方法——拉鍊法,(也是樹的一種存儲結構,稱爲二叉鏈表)咱們能夠理解爲「鏈表的數組」,如圖:
左邊很明顯是個數組,數組的每一個成員包括一個指針,指向一個鏈表的頭,固然這個鏈表可能爲空,也可能元素不少。咱們根據元素的一些特徵把元素分配到不一樣的鏈表中去,也是根據這些特徵,找到正確的鏈表,再從鏈表中找出這個元素。
元素特徵轉變爲數組下標的方法就是散列法。
散列法固然不止一種,下面列出三種比較經常使用的:
1,除法散列法 (求模數)
最直觀的一種,上圖使用的就是這種散列法,公式:
index = value % 16
學過彙編的都知道,求模數實際上是經過一個除法運算獲得的,因此叫「除法散列法」。
2,平方散列法
求index是很是頻繁的操做,而乘法的運算要比除法來得省時(對如今的CPU來講,估計咱們感受不出來),因此咱們考慮把除法換成乘法和一個位移操做。公式:
index = (value * value) >> 28
若是數值分配比較均勻的話這種方法能獲得不錯的結果,但我上面畫的那個圖的各個元素的值算出來的index都是0——很是失敗。也許你還有個問題,value若是很大,value * value不會溢出嗎?答案是會的,但咱們這個乘法不關心溢出,由於咱們根本不是爲了獲取相乘結果,而是爲了獲取index。
3,斐波那契(Fibonacci)散列法
平方散列法的缺點是顯而易見的,因此咱們能不能找出一個理想的乘數,而不是拿value自己看成乘數呢?答案是確定的。
1,對於16位整數而言,這個乘數是40503
2,對於32位整數而言,這個乘數是2654435769
3,對於64位整數而言,這個乘數是11400714819323198485
這幾個「理想乘數」是如何得出來的呢?這跟一個法則有關,叫黃金分割法則,而描述黃金分割法則的最經典表達式無疑就是著名的斐波那契數列,若是你還有興趣,就到網上查找一下「斐波那契數列」等關鍵字,我數學水平有限,不知道怎麼描述清楚爲何,另外斐波那契數列的值竟然和太陽系八大行星的軌道半徑的比例出奇吻合,很神奇,對麼?
對咱們常見的32位整數而言,公式:
i ndex = (value * 2654435769) >> 28
若是用這種斐波那契散列法的話,那我上面的圖就變成這樣了:
很明顯,用斐波那契散列法調整以後要比原來的取摸散列法好不少。
【適用範圍】
快速查找,刪除的基本數據結構,一般須要總數據量能夠放入內存。
【基本原理及要點】
hash函數選擇,針對字符串,整數,排列,具體相應的hash方法。
碰撞處理:
一種是open hashing,也稱爲拉鍊法;
另外一種就是closed hashing,也稱開地址法,opened addressing。
【擴展】
d-left hashing中的d是多個的意思,咱們先簡化這個問題,看一看2-left hashing。2-left hashing指的是將一個哈希表分紅長度相等的兩半,分別叫作T1和T2,給T1和T2分別配備一個哈希函數,h1和h2。在存儲一個新的key時,同 時用兩個哈希函數進行計算,得出兩個地址h1[key]和h2[key]。這時須要檢查T1中的h1[key]位置和T2中的h2[key]位置,哪個 位置已經存儲的(有碰撞的)key比較多,而後將新key存儲在負載少的位置。若是兩邊同樣多,好比兩個位置都爲空或者都存儲了一個key,就把新key 存儲在左邊的T1子表中,2-left也由此而來。在查找一個key時,必須進行兩次hash,同時查找兩個位置。
【問題實例】
1).海量日誌數據,提取出某日訪問百度次數最多的那個IP。
IP的數目仍是有限的,最多2^32個,因此能夠考慮使用hash將ip直接存入內存,而後進行統計。
【什麼是Bit-map】
所謂的Bit-map就是用一個bit位來標記某個元素對應的Value, 而Key便是該元素。因爲採用了Bit爲單位來存儲數據,所以在存儲空間方面,能夠大大節省。
若是說了這麼多還沒明白什麼是Bit-map,那麼咱們來看一個具體的例子,假設咱們要對0-7內的5個元素(4,7,2,5,3)排序(這裏假設這些元素沒有重複)。那麼咱們就能夠採用Bit-map的方法來達到排序的目的。要表示8個數,咱們就只須要8個Bit(1Bytes),首先咱們開闢1Byte的空間,將這些空間的全部Bit位都置爲0(以下圖:)
而後遍歷這5個元素,首先第一個元素是4,那麼就把4對應的位置爲1(能夠這樣操做 p+(i/8)|(0x01<<(i%8)) 固然了這裏的操做涉及到Big-ending和Little-ending的狀況,這裏默認爲Big-ending),由於是從零開始的,因此要把第五位置爲一(以下圖):
而後再處理第二個元素7,將第八位置爲1,,接着再處理第三個元素,一直到最後處理完全部的元素,將相應的位置爲1,這時候的內存的Bit位的狀態以下:
而後咱們如今遍歷一遍Bit區域,將該位是一的位的編號輸出(2,3,4,5,7),這樣就達到了排序的目的。下面的代碼給出了一個BitMap的用法:排序。
C代碼
【適用範圍】
可進行數據的快速查找,判重,刪除,通常來講數據範圍是int的10倍如下
【基本原理及要點】
使用bit數組來表示某些元素是否存在,好比8位電話號碼
【擴展】
Bloom filter能夠看作是對bit-map的擴展
【問題實例】
1)已知某個文件內包含一些電話號碼,每一個號碼爲8位數字,統計不一樣號碼的個數。
8位最多99 999 999,大概須要99m個bit(1024*1024 *99個bit ),大概10幾m字節的內存便可。
申請內存空間的大小爲:int a[1 + N/32] =((99 999 999/32 +1)*4 個字節/1024/1024 = 12M
(能夠理解爲從0-99 999 999的數字,每一個數字對應一個Bit位,因此只須要99M個Bit==12MBytes,這樣,就用了小小的12M左右的內存表示了全部的8位數的電話)
2)2.5億個整數中找出不重複的整數的個數,內存空間不足以容納這2.5億個整數。
將bit-map擴展一下,用2bit表示一個數便可,0表示未出現,1表示出現一次,2表示出現2次及以上,在遍歷這些數的時候,若是對應位置的值是0,則將其置爲1;若是是1,將其置爲2;若是是2,則保持不變。或者咱們不用2bit來進行表示,咱們用兩個bit-map便可模擬實現這個2bit-map,都是同樣的道理。
【什麼是堆】
在八大排序裏面有堆 的詳細介紹:八大排序算法
概念:堆是一種特殊的二叉樹,具有如下兩種性質
1)每一個節點的值都大於(或者都小於,稱爲最小堆)其子節點的值
2)樹是徹底平衡的,而且最後一層的樹葉都在最左邊
這樣就定義了一個最大堆。以下圖用一個數組來表示堆:
那麼下面介紹二叉堆:二叉堆是一種徹底二叉樹,其任意子樹的左右節點(若是有的話)的鍵值必定比根節點大,上圖其實就是一個二叉堆。
你必定發覺了,最小的一個元素就是數組第一個元素,那麼二叉堆這種有序隊列如何入隊呢?看圖:
假設要在這個二叉堆裏入隊一個單元,鍵值爲2,那隻需在數組末尾加入這個元素,而後儘量把這個元素往上挪,直到挪不動,通過了這種複雜度爲Ο(logn)的操做,二叉堆仍是二叉堆。
那如何出隊呢?也不難,看圖
出隊必定是出數組的第一個元素,這麼來第一個元素之前的位置就成了空位,咱們須要把這個空位挪至葉子節點,而後把數組最後一個元素插入這個空位,把這個「空位」儘可能往上挪。這種操做的複雜度也是Ο(logn)。
【適用範圍】
海量數據前n大,而且n比較小,堆能夠放入內存
【基本原理及要點】
最大堆求前n小,最小堆求前n大。方法,好比求前n小,咱們比較當前元素與最大堆裏的最大元素,若是它小於最大元素,則應該替換那個最大元 素。這樣最後獲得的n個元素就是最小的n個。適合大數據量,求前n小,n的大小比較小的狀況,這樣能夠掃描一遍便可獲得全部的前n元素,效率很高。
【擴展】
雙堆,一個最大堆與一個最小堆結合,能夠用來維護中位數。
【問題實例】
1)100w個數中找最大的前100個數。
用一個100個元素大小的最小堆便可。
【什麼是雙層桶】
事實上,與其說雙層桶劃分是一種數據結構,不如說它是一種算法設計思想。面對一堆大量的數據咱們沒法處理的時候,咱們能夠將其分紅一個個小的單元,而後根據必定的策略來處理這些小單元,從而達到目的。
【適用範圍】
第k大,中位數,不重複或重複的數字
【基本原理及要點】
由於元素範圍很大,不能利用直接尋址表,因此經過屢次劃分,逐步肯定範圍,而後最後在一個能夠接受的範圍內進行。能夠經過屢次縮小,雙層只是一個例子,分治纔是其根本(只是「只分不治」)。
【擴展】
當有時候須要用一個小範圍的數據來構造一個大數據,也是能夠利用這種思想,相比之下不一樣的,只是其中的逆過程。
【問題實例】
1).2.5億個整數中找出不重複的整數的個數,內存空間不足以容納這2.5億個整數。
有 點像鴿巢原理,整數個數爲2^32,也就是,咱們能夠將這2^32個數,劃分爲2^8=256個區域(好比用單個文件表明一個區域),而後將數據分離到不一樣的區 域,而後不一樣的區域在利用bitmap就能夠直接解決了。也就是說只要有足夠的磁盤空間,就能夠很方便的解決。 固然這個題也能夠用咱們前面講過的BitMap方法解決,正所謂條條大道通羅馬~~~
2).5億個int找它們的中位數。
這個例子比上面那個更明顯。首先咱們將int劃分爲2^16個區域,而後讀取數據統計落到各個區域裏的數的個數,以後咱們根據統計結果就能夠判斷中位數落到那個區域,同時知道這個區域中的第幾大數恰好是中位數。而後第二次掃描咱們只統計落在這個區域中的那些數就能夠了。
實 際上,若是不是int是int64,咱們能夠通過3次這樣的劃分便可下降到能夠接受的程度。便可以先將int64分紅2^24個區域,而後肯定區域的第幾 大數,在將該區域分紅2^20個子區域,而後肯定是子區域的第幾大數,而後子區域裏的數的個數只有2^20,就能夠直接利用direct addr table進行統計了。
3).如今有一個0-30000的隨機數生成器。請根據這個隨機數生成器,設計一個抽獎範圍是0-350000彩票中獎號碼列表,其中要包含20000箇中獎號碼。
這個題恰好和上面兩個思想相反,一個0到3萬的隨機數生成器要生成一個0到35萬的隨機數。那麼咱們徹底能夠將0-35萬的區間分紅35/3=12個區 間,而後每一個區間的長度都小於等於3萬,這樣咱們就能夠用題目給的隨機數生成器來生成了,而後再加上該區間的基數。那麼要每一個區間生成多少個隨機數呢?計 算公式就是:區間長度*隨機數密度,在本題目中就是30000*(20000/350000)。最後要注意一點,該題目是有隱含條件的:彩票,這意味着你 生成的隨機數裏面不能有重複,這也是我爲何用雙層桶劃分思想的另一個緣由。
索引是對數據庫表中一列或多列的值進行排序的一種結構,使用索引可快速訪問數據庫表中的特定信息。
數據庫索引比如是一本書前面的目錄,能加快數據庫的查詢速度。
例如這樣一個查詢:select * from table1 where id=44。若是沒有索引,必須遍歷整個表,直到ID等於44的這一行被找到爲止;有了索引以後(必須是在ID這一列上創建的索引),直接在索引裏面找44(也就是在ID這一列找),就能夠得知這一行的位置,也就是找到了這一行。可見,索引是用來定位的。
索引分爲聚簇索引和非聚簇索引兩種,聚簇索引 是按照數據存放的物理位置爲順序的,而非聚簇索引就不同了;聚簇索引能提升多行檢索的速度,而非聚簇索引對於單行的檢索很快。
創建索引的目的是加快對錶中記錄的查找或排序。
爲表設置索引要付出代價的:一是增長了數據庫的存儲空間,二是在插入和修改數據時要花費較多的時間(由於索引也要隨之變更)。
建立索引能夠大大提升系統的性能。
第一,經過建立惟一性索引,能夠保證數據庫表中每一行數據的惟一性。
第二,能夠大大加快數據的檢索速度,這也是建立索引的最主要的緣由。
第三,能夠加速表和表之間的鏈接,特別是在實現數據的參考完整性方面特別有意義。
第四,在使用分組和排序子句進行數據檢索時,一樣能夠顯著減小查詢中分組和排序的時間。
第五,經過使用索引,能夠在查詢的過程當中,使用優化隱藏器,提升系統的性能。
也許會有人要問:增長索引有如此多的優勢,爲何不對錶中的每個列建立一個索引呢?由於,增長索引也有許多不利的方面。
第一,建立索引和維護索引要耗費時間,這種時間隨着數據量的增長而增長。
第二,索引須要佔物理空間,除了數據表佔數據空間以外,每個索引還要佔必定的物理空間,若是要創建聚簇索引,那麼須要的空間就會更大。
第三,當對錶中的數據進行增長、刪除和修改的時候,索引也要動態的維護,這樣就下降了數據的維護速度。
索引是創建在數據庫表中的某些列的上面。在建立索引的時候,應該考慮在哪些列上能夠建立索引,在哪些列上不能建立索引。通常來講,應該在這些列上建立索引:
在常常須要搜索的列上,能夠加快搜索的速度;
在做爲主鍵的列上,強制該列的惟一性和組織表中數據的排列結構;
在常常用在鏈接的列上,這些列主要是一些外鍵,能夠加快鏈接的速度;在常常須要根據範圍進行搜索的列上建立索引,由於索引已經排序,其指定的範圍是連續的;
在常常須要排序的列上建立索引,由於索引已經排序,這樣查詢能夠利用索引的排序,加快排序查詢時間;
在常用在WHERE子句中的列上面建立索引,加快條件的判斷速度。
一樣,對於有些列不該該建立索引。通常來講,不該該建立索引的的這些列具備下列特色:
第一,對於那些在查詢中不多使用或者參考的列不該該建立索引。這是由於,既然這些列不多使用到,所以有索引或者無索引,並不能提升查詢速度。相反,因爲增長了索引,反而下降了系統的維護速度和增大了空間需求。
第二,對於那些只有不多數據值的列也不該該增長索引。這是由於,因爲這些列的取值不多,例如人事表的性別列,在查詢的結果中,結果集的數據行佔了表中數據行的很大比例,即須要在表中搜索的數據行的比例很大。增長索引,並不能明顯加快檢索速度。
第三,對於那些定義爲text, image和bit數據類型的列不該該增長索引。這是由於,這些列的數據量要麼至關大,要麼取值不多,不利於使用索引。
第四,當修改性能遠遠大於檢索性能時,不該該建立索引。這是由於,修改性能和檢索性能是互相矛盾的。當增長索引時,會提升檢索性能,可是會下降修改性能。當減小索引時,會提升修改性能,下降檢索性能。所以,當修改操做遠遠多於檢索操做時,不該該建立索引。
此外,除了數據庫索引以外,在LAMP結果如此流行的今天,數據庫(尤爲是MySQL)性能優化也是海量數據處理的一個熱點。下面就結合本身的經驗,聊一聊MySQL數據庫優化的幾個方面。
首先,在數據庫設計的時候,要可以充分的利用索引帶來的性能提高,至於如何創建索引,創建什麼樣的索引,在哪些字段上創建索引,上面已經講的很清楚了,這裏不在贅述。另外就是設計數據庫的原則就是儘量少的進行數據庫寫操做(插入,更新,刪除等),查詢越簡單越好。以下:
數據庫設計:
. 建立索引
. 查詢語句
1)查詢越簡單越好:單表查詢 > inner join >其餘
2)更新越少越好
其次,配置緩存是必不可少的,配置緩存能夠有效的下降數據庫查詢讀取次數,從而緩解數據庫服務器壓力,達到優化的目的,必定程度上來說,這算是一個「圍魏救趙」的辦法。可配置的緩存包括索引緩存(key_buffer),排序緩存(sort_buffer),查詢緩存(query_buffer),表描述符緩存(table_cache),以下:
配置緩存:
. 索引緩存(key_buffer)
. 排序緩存 (sort_buffer)
. 查詢緩存 (query_buffer)
. 表描述符緩存(table_cache)
第三,切表,切表也是一種比較流行的數據庫優化法。分表包括兩種方式:橫向分表和縱向分表,其中,橫向分表比較有使用意義,故名思議,橫向切表就是指把記錄分到不一樣的表中,而每條記錄仍舊是完整的(縱向切表後每條記錄是不完整的),例如原始表中有100條記錄,我要切成2個表,那麼最簡單也是最經常使用的方法就是ID取摸切表法,本例中,就把ID爲1,3,5,7。。。的記錄存在一個表中,ID爲2,4,6,8,。。。的記錄存在另外一張表中。雖然橫向切表能夠減小查詢強度,可是它也破壞了原始表的完整性,若是該表的統計操做比較多,那麼就不適合橫向切表。橫向切表有個很是典型的用法,就是用戶數據:每一個用戶的用戶數據通常都比較龐大,可是每一個用戶數據之間的關係不大,所以這裏很適合橫向切表。最後,要記住一句話就是:分表會形成查詢的負擔,所以在數據庫設計之初,要想好是否真的適合切表的優化:
切表分表:
. 縱向 :字段較多時能夠考慮,通常用處不到
. 橫向 :1)能有效下降表的大小,減小因爲枷鎖致使的等待
2)查詢會變得複雜,尤爲是須要排序的查詢
第四,日誌分析,在數據庫運行了較長一段時間之後,會積累大量的LOG日誌,其實這裏面的蘊涵的有用的信息量仍是很大的。經過分析日誌,能夠找到系統性能的瓶頸,從而進一步尋找優化方案。
數據庫性能分析:
. 查詢吞吐量,數據量監控
. 慢查詢分析:索引,I/O,cpu等。
以上講的都是單機MySQL的性能優化的一些經驗,可是隨着信息大爆炸,單機的數據庫服務器已經不能知足咱們的需求,因而,多多節點,分佈式數據庫網絡出現了,其通常的結構以下:
分佈式數據庫結構
這種分佈式集羣的技術關鍵就是「同步複製」。。。
引言:
在信息大爆炸的今天,有了搜索引擎的幫助,使得咱們可以快速,便捷的找到所求。提到搜索引擎,就不得不說VSM模型,說到VSM,就不得不聊倒排索引。能夠絕不誇張的講,倒排索引是搜索引擎的基石。
VSM檢索模型
VSM全稱是Vector Space Model(向量空間模型),是IR(Information Retrieval信息檢索)模型中的一種,因爲其簡單,直觀,高效,因此被普遍的應用到搜索引擎的架構中。98年的Google就是憑藉這樣的一個模型,開始了它的瘋狂擴張之路。廢話很少說,讓咱們來看看到底VSM是一個什麼東東。
在開始以前,我默認你們對線性代數裏面的向量(Vector)有必定了解的。向量是既有大小又有方向的量,一般用有向線段表示,向量有:加、減、倍數、內積、距離、模、夾角的運算。
文檔(Document):一個完整的信息單元,對應的搜索引擎系統裏,就是指一個個的網頁。
標引項(Term):文檔的基本構成單位,例如在英文中能夠看作是一個單詞,在中文中能夠看做一個詞語。
查詢(Query):一個用戶的輸入,通常由多個Term構成。
那麼用一句話概況搜索引擎所作的事情就是:對於用戶輸入的Query,找到最類似的Document返回給用戶。而這正是IR模型所解決的問題:
信息檢索模型是指如何對查詢和文檔進行表示,而後對它們進行類似度計算的框架和方法。
舉個簡單的例子:
如今有兩篇文章(Document)分別是 「春風來了,春天的腳步近了」 和 「春風不度玉門關」。而後輸入的Query是「春風」,從直觀上感受,前者和輸入的查詢更相關一些,由於它包含有2個春,但這只是咱們的直觀感受,如何量化呢,要知道計算機是門嚴謹的學科^_^。這個時候,咱們前面講的Term和VSM模型就派上用場了。
首先咱們要肯定向量的維數,這時候就須要一個字典庫,字典庫的大小,便是向量的維數。在該例中,字典爲{春風,來了,春天, 的,腳步,近了,不度,玉門關} ,文檔向量,查詢向量以下圖:
PS:爲了簡單起見,這裏分詞的粒度很大。
將Query和Document都量化爲向量之後,那麼就能夠計算用戶的查詢和哪一個文檔類似性更大了。簡單的計算結果是D1和D2同Query的內積都是1,囧。固然了,若是分詞粒度再細一些,查詢的結果就是另一個樣子了,所以分詞的粒度也是會對查詢結果(主要是召回率和準確率)形成影響的。
上述的例子是用一個很簡單的例子來講明VSM模型的,計算文檔類似度的時候也是採用最原始的內積的方法,而且只考慮了詞頻(TF)影響因子,而沒有考慮反詞頻(IDF),而如今比較經常使用的是cos夾角法,影響因子也很是多,據傳Google的影響因子有100+之多。
大名鼎鼎的Lucene項目就是採用VSM模型構建的,VSM的核心公式以下(由cos夾角法演變,此處省去推導過程)
從上面的例子不難看出,若是向量的維度(對漢語來將,這個值通常在30w-45w)變大,並且文檔數量(一般都是海量的)變多,那麼計算一次相關性,開銷是很是大的,如何解決這個問題呢?不要忘記了咱們這節的主題就是 倒排索引,主角終於粉墨登場了!!!
倒排索引很是相似咱們前面提到的Hash結構。如下內容來自維基百科:
倒排索引(英語:Inverted index),也常被稱爲反向索引、置入檔案或反向檔案,是一種索引方法,被用來存儲在全文搜索下某個單詞在一個文檔或者一組文檔中的存儲位置的映射。它是文檔檢索系統中最經常使用的數據結構。
有兩種不一樣的反向索引形式:
後者的形式提供了更多的兼容性(好比短語搜索),可是須要更多的時間和空間來建立。
由上面的定義能夠知道,一個倒排索引包含一個字典的索引和全部詞的列表。其中字典索引中包含了全部的Term(通俗理解爲文檔中的詞),索引後面跟的列表則保存該詞的信息(出現的文檔號,甚至包含在每一個文檔中的位置信息)。下面咱們還採用上面的方法舉一個簡單的例子來講明倒排索引。
例如如今咱們要對三篇文檔創建索引(實際應用中,文檔的數量是海量的):
文檔1(D1):中國移動互聯網發展迅速
文檔2(D2):移動互聯網將來的潛力巨大
文檔3(D3):中華民族是個勤勞的民族
那麼文檔中的詞典集合爲:{中國,移動,互聯網,發展,迅速,將來,的,潛力,巨大,中華,民族,是,個,勤勞}
建好的索引以下圖:
在上面的索引中,存儲了兩個信息,文檔號和出現的次數。創建好索引之後,咱們就能夠開始查詢了。例如如今有一個Query是」中國移動」。首先分詞獲得Term集合{中國,移動},查倒排索引,分別計算query和d1,d2,d3的距離。有沒有發現,倒排表創建好之後,就不須要在檢索整個文檔庫,而是直接從字典集合中找到「中國」和「移動」,而後遍歷後面的列表直接計算。
對倒排索引結構咱們已經有了初步的瞭解,但在實際應用中還有些須要解決的問題(主要是由海量數據引發的)。筆者列舉一些問題,並給出相應的解決方案,拋磚以引玉,但願你們能夠展開討論:
1.左側的索引表如何創建?怎麼作才能最高效?
可能有人不假思索回答:左側的索引固然要採起hash結構啊,這樣能夠快速的定位到字典項。可是這樣問題又來了,hash函數如何選取呢?並且hash是有碰撞的,可是倒排表彷佛又是不容許碰撞的存在的。事實上,雖然倒排表和hash異常的相思,可是二者仍是有很大區別的,其實在這裏咱們能夠採用前面提到的Bitmap的思想,每一個Term(單詞)對應一個位置(固然了,這裏不是一個比特位),並且是一一對應的。如何可以作到呢,通常在文字處理中,有不少的編碼,漢字中的GBK編碼基本上就能夠包含全部用到的漢字,每一個漢字的GBK編碼是肯定的,所以一個Term的」ID」也就肯定了,從而能夠作到快速定位。注:獲得一個漢字的GBK號是很是快的過程,能夠理解爲O(1)的時間複雜度。
2.如何快速的添加刪除更新索引?
有經驗的碼農都知道,通常在系統的「作加法」的代價比「作減法」的代價要低不少,在搜索引擎中中也不例外。所以,在倒排表中,遇到要刪除一個文檔,其實不是真正的刪除,而是將其標記刪除。這樣一個減法操做的代價就比較小了。
3.那麼多的海量文檔,若是存儲呢?有麼有什麼備份策略呢?
固然了,一臺機器是存儲不下的,分佈式存儲是採起的。通常的備份保存3份就足夠了。
好了,倒排索引終於完工了,不足的地方請指正。謝謝
適用範圍:
大數據的排序,去重
基本原理及要點:
外部排序的兩個獨立階段:
1)首先按內存大小,將外存上含n個記錄的文件分紅若干長度L的子文件或段。依次讀入內存並利用有效的內部排序對他們進行排序,並將排序後獲得的有序字文件從新寫入外存,一般稱這些子文件爲歸併段。
2)對這些歸併段進行逐趟歸併,使歸併段逐漸由小到大,直至獲得整個有序文件爲之。
外排序的歸併方法,置換選擇 敗者樹原理,最優歸併樹
擴展:
問題實例:
1).有一個1G大小的一個文件,裏面每一行是一個詞,詞的大小不超過16個字節,內存限制大小是1M。返回頻數最高的100個詞
這個數據具備很明顯的特色,詞的大小爲16個字節,可是內存只有1m作hash有些不夠,因此能夠用來排序。內存能夠當輸入緩衝區使用。
適用範圍:
數據量大,重複多,可是數據種類小能夠放入內存
基本原理及要點:
實現方式,節點孩子的表示方式
擴展:
壓縮實現。
問題實例:
1).有10個文件,每一個文件1G, 每一個文件的每一行都存放的是用戶的query,每一個文件的query均可能重複。要你按照query的頻度排序 。
2).1000萬字符串,其中有些是相同的(重複),須要把重複的所有去掉,保留沒有重複的字符串。請問怎麼設計和實現?
3).尋找熱門查詢:查詢串的重複度比較高,雖然總數是1千萬,但若是除去重複後,不超過3百萬個,每一個不超過255字節。
基本原理及要點:
將數據交給不一樣的機器去處理,數據劃分,結果歸約。
擴 展: