對於哈希算法,在咱們平時的開發中,都是基本上拿來就用就好了,因此這節咱們將重點放在如何使用,並不進行哈希算法的原理剖析和如何設計一個哈希算法的講解.mysql
將任意長度的二進制值串映射爲固定長度的二進制值串,這個映射的規則就是哈希算法
,而經過原始數據映射以後獲得的二進制值串就是哈希值(散列值)
.一個優秀的哈希算法須要知足:nginx
拿mysql
的MD5
舉個例子:
算法
MD5 的哈希值是 128 位的 Bit 長度,爲了方便表示,mysql
把它們轉化成了 16 進制編碼)。能夠看出來,不管要哈希的文本有多長、多短,經過 MD5 哈希以後,獲得的哈希值的長度都是相同的,並且獲得的哈希值看起來像一堆隨機數,徹底沒有規律。sql
兩個很是類似的文本1
和1.
的哈希值也是不一樣的吶 數據庫
通過哈希算法獲得的哈希值,很難反向推導出原始數據。好比上面的例子中,咱們就很難經過哈希值「36c942351ec9cc3ad124e288a5c9cf0b」反推出對應的文本「李四」。後端
哈希算法要處理的文本多是各類各樣的。好比,對於很是長的文本,若是哈希算法的計算時間很長,那就只能停留在理論研究的層面,很難應用到實際的軟件開發中。好比,咱們把今天這篇包含 幾千多個漢字的文章,用 MD5 計算哈希值,仍是用不了 1ms 的時間。緩存
哈希算法的應用很是很是多,最多見的有七個,分別是安全加密、惟一標識、數據校驗、散列函數、負載均衡、數據分片、分佈式存儲。安全
說到哈希算法的應用,最早想到的應該就是安全加密。服務器
最經常使用於加密的哈希算法是MD5(MD5 Message-Digest Algorithm,MD5 消息摘要算法)和SHA(Secure Hash Algorithm,安全散列算法)。網絡
除了這兩個以外,固然還有不少其餘加密算法,好比DES(Data Encryption Standard,數據加密標準)、AES(Advanced Encryption Standard,高級加密標準)。
在前面提到的哈希算法四點要求中,對用於加密的哈希算法來講,有兩點格外重要。
提問:爲何沒法作到徹底不衝突?
答:組合數學中一個很是基礎的理論,鴿巢原理(也叫抽屜原理):這個原理自己很簡單,它是說,若是有 10 個鴿巢,有 11 只鴿子,那確定有 1 個鴿巢中的鴿子數量多於 1 個,換句話說就是,確定有 2 只鴿子在 1 個鴿巢內。
仍是拿MD5來說:
哈希算法產生的哈希值的長度是固定且有限的。好比MD5 ,哈希值是固定的 128 位二進制串,能表示的數據是有限的,最多能表示 2^128 個數據,而咱們要哈希的數據是無窮的。基於鴿巢原理,若是咱們對 2^128+1 個數據求哈希值,就必然會存在哈希值相同的狀況。這裏你應該能想到,通常狀況下,哈希值越長的哈希算法,散列衝突的機率越低。
不過,即使哈希算法存在散列衝突的狀況,可是由於哈希值的範圍很大,衝突的機率極低,因此相對來講仍是很難破解的。像 MD5,有 2^128 個不一樣的哈希值,這個數據已是一個天文數字了,因此散列衝突的機率要小於 1/2^128。
提問:爲何衝突的機率高的話,就會容易破解?
答:首先咱們須要理解破解的意思
,這裏的破解不是指的搞出算法是如何運行的,而是根據哈希值找到原始數據!如何找到,那就窮舉唄.找一堆數據,而後經過該算法計算哈希值,直到找到一個與咱們要破解的哈希值相同的哈希值,那麼咱們窮舉的原始數據就是咱們須要破解的原始數據!!!
若是咱們拿到一個 MD5 哈希值,但願經過毫無規律的窮舉的方法,找到跟這個 MD5 值相同的另外一個數據,那耗費的時間應該是個天文數字。因此,即使哈希算法存在衝突,可是在有限的時間和資源下,哈希算法仍是被很難破解的。
除此以外,沒有絕對安全的加密。越複雜、越難破解的加密算法,須要的計算時間也越長。好比 SHA-256 比 SHA-1 要更復雜、更安全,相應的計算時間就會比較長。密碼學界也一直致力於找到一種快速而且很難被破解的哈希算法。咱們在實際的開發過程當中,也須要權衡破解難度和計算時間,來決定究竟使用哪一種加密算法。
我先來舉一個例子。若是要在海量的圖庫中,搜索一張圖是否存在,咱們不能單純地用圖片的元信息(好比圖片名稱)來比對,由於有可能存在名稱相同但圖片內容不一樣,或者名稱不一樣圖片內容相同的狀況。那咱們該如何搜索呢?
咱們知道,任何文件在計算中均可以表示成二進制碼串,因此,比較笨的辦法就是,拿要查找的圖片的二進制碼串與圖庫中全部圖片的二進制碼串一一比對。若是相同,則說明圖片在圖庫中存在。可是,每一個圖片小則幾十 KB、大則幾 MB,轉化成二進制是一個很是長的串,比對起來很是耗時。有沒有比較快的方法呢?
咱們能夠給每個圖片取一個惟一標識,或者說信息摘要。好比,咱們能夠從圖片的二進制碼串開頭取 100 個字節,從中間取 100 個字節,從最後再取 100 個字節,而後將這 300 個字節放到一塊,經過哈希算法(好比 MD5),獲得一個哈希字符串,用它做爲圖片的惟一標識。經過這個惟一標識來斷定圖片是否在圖庫中,這樣就能夠減小不少工做量。
若是還想繼續提升效率,咱們能夠把每一個圖片的惟一標識,和相應的圖片文件在圖庫中的路徑信息,都存儲在散列表中。當要查看某個圖片是否是在圖庫中的時候,咱們先經過哈希算法對這個圖片取惟一標識,而後在散列表中查找是否存在這個惟一標識。
若是不存在,那就說明這個圖片不在圖庫中;若是存在,咱們再經過散列表中存儲的文件路徑,獲取到這個已經存在的圖片,跟如今要插入的圖片作全量的比對,看是否徹底同樣。若是同樣,就說明已經存在;若是不同,說明兩張圖片儘管惟一標識相同,可是並非相同的圖片。
電驢這樣的 BT 下載軟件你確定用過吧?咱們知道,BT 下載的原理是基於 P2P 協議的。咱們從多個機器上並行下載一個 2GB 的電影,這個電影文件可能會被分割成不少文件塊(好比能夠分紅 100 塊,每塊大約 20MB)。等全部的文件塊都下載完成以後,再組裝成一個完整的電影文件就好了。
咱們知道,網絡傳輸是不安全的,下載的文件塊有多是被宿主機器惡意修改過的,又或者下載過程當中出現了錯誤,因此下載的文件塊可能不是完整的。若是咱們沒有能力檢測這種惡意修改或者文件下載出錯,就會致使最終合併後的電影沒法觀看,甚至致使電腦中毒。如今的問題是,如何來校驗文件塊的安全、正確、完整呢?
具體的 BT 協議很複雜,校驗方法也有不少,我來講其中的一種思路。
咱們經過哈希算法,對 100 個文件塊分別取哈希值,而且保存在種子文件中。咱們在前面講過,哈希算法有一個特色,對數據很敏感。只要文件塊的內容有一丁點兒的改變,最後計算出的哈希值就會徹底不一樣。因此,當文件塊下載完成以後,咱們能夠經過相同的哈希算法,對下載好的文件塊逐一求哈希值,而後跟種子文件中保存的哈希值比對。若是不一樣,說明這個文件塊不完整或者被篡改了,須要再從新從其餘宿主機器上下載這個文件塊。
前面講了不少哈希算法的應用,實際上,散列函數也是哈希算法的一種應用。
咱們前兩節講到,散列函數是設計一個散列表的關鍵。它直接決定了散列衝突的機率和散列表的性能。不過,相對哈希算法的其餘應用,散列函數對於散列算法衝突的要求要低不少。即使出現個別散列衝突,只要不是過於嚴重,咱們均可以經過開放尋址法或者鏈表法解決。
不只如此,散列函數對於散列算法計算獲得的值,是否能反向解密也並不關心。散列函數中用到的散列算法,更加關注散列後的值是否能平均分佈,也就是,一組數據是否能均勻地散列在各個槽中。除此以外,散列函數執行的快慢,也會影響散列表的性能,因此,散列函數用的散列算法通常都比較簡單,比較追求效率。
如下三個都與分佈式有關:
最直接的方法就是,維護一張映射關係表,這張表的內容是客戶端 IP 地址或者會話 ID 與服務器編號的映射關係。客戶端發出的每次請求,都要先在映射表中查找應該路由到的服務器編號,而後再請求編號對應的服務器。這種方法簡單直觀,但也有幾個弊端:
若是藉助哈希算法,這些問題均可以很是完美地解決。咱們能夠經過哈希算法,對客戶端 IP 地址或者會話 ID 計算哈希值,將取得的哈希值與服務器列表的大小進行取模運算,最終獲得的值就是應該被路由到的服務器編號。 這樣,咱們就能夠把同一個 IP 過來的全部請求,都路由到同一個後端服務器上。(這裏是對每個請求都要計算一次hash值,不知道nginx
是否是這樣實現的,還須要進一步瞭解...)
哈希算法還能夠用於數據的分片。我這裏有兩個例子。
假如咱們有 1T 的日誌文件,這裏面記錄了用戶的搜索關鍵詞,咱們想要快速統計出每一個關鍵詞被搜索的次數,該怎麼作呢?
咱們來分析一下。這個問題有兩個難點,第一個是搜索日誌很大,沒辦法放到一臺機器的內存中。第二個難點是,若是隻用一臺機器來處理這麼巨大的數據,處理時間會很長。
針對這兩個難點,咱們能夠先對數據進行分片,而後採用多臺機器處理的方法,來提升處理速度。
具體的思路是這樣的:爲了提升處理的速度,咱們用 n 臺機器並行處理。咱們從搜索記錄的日誌文件中,依次讀出每一個搜索關鍵詞,而且經過哈希函數計算哈希值,而後再跟 n 取模,最終獲得的值,就是應該被分配到的機器編號。 這樣,哈希值相同的搜索關鍵詞就被分配到了同一個機器上。也就是說,同一個搜索關鍵詞會被分配到同一個機器上。每一個機器會分別計算關鍵詞出現的次數,最後合併起來就是最終的結果。 實際上,這裏的處理過程也是 MapReduce
的基本設計思想。
個人疑問:"也就是說,同一個搜索關鍵詞會被分配到同一個機器.上。」這句話個人理解就是一臺機器對應與一-個關鍵字,若是是這樣的話,那麼,若是搜索的詞全都不相同的話,那得用多少臺服務器呢?像這種狀況該如何處理啊?
目前尚未想通 ^-^
如何快速判斷圖片是否在圖庫中?上面介紹了一種方法,即給每一個圖片取惟一標識(或者信息摘要),而後構建散列表。 可是咱們如今要給他加一點需求了
假設如今咱們的圖庫中有 1 億張圖片,很顯然,在單臺機器上構建散列表是行不通的。由於單臺機器的內存有限,而 1 億張圖片構建散列表顯然遠遠超過了單臺機器的內存上限。
咱們一樣能夠對數據進行分片,而後採用多機處理。咱們準備 n 臺機器,讓每臺機器只維護某一部分圖片對應的散列表。咱們每次從圖庫中讀取一個圖片,計算惟一標識,而後與機器個數 n 求餘取模,獲得的值就對應要分配的機器編號,而後將這個圖片的惟一標識和圖片路徑發往對應的機器構建散列表。
當咱們要判斷一個圖片是否在圖庫中的時候,咱們經過一樣的哈希算法,計算這個圖片的惟一標識,而後與機器個數 n 求餘取模。假設獲得的值是 k,那就去編號 k 的機器構建的散列表中查找。
如今,咱們來估算一下,給這 1 億張圖片構建散列表大約須要多少臺機器。
散列表中每一個數據單元包含兩個信息,哈希值和圖片文件的路徑。假設咱們經過 MD5 來計算哈希值,那長度就是 128 比特,也就是 16 字節。文件路徑長度的上限是 256 字節,咱們能夠假設平均長度是 128 字節。若是咱們用鏈表法來解決衝突,那還須要存儲指針,指針只佔用 8 字節。因此,散列表中每一個數據單元就佔用 152 字節(這裏只是估算,並不許確)。
假設一臺機器的內存大小爲 2GB,散列表的裝載因子爲 0.75,那一臺機器能夠給大約 1000 萬(2GB*0.75/152)張圖片構建散列表。因此,若是要對 1 億張圖片構建索引,須要大約十幾臺機器。在工程中,這種估算仍是很重要的,能讓咱們事先對須要投入的資源、資金有個大概的瞭解,能更好地評估解決方案的可行性。
實際上,針對這種海量數據的處理問題,咱們均可以採用多機分佈式處理。藉助這種分片的思路,能夠突破單機內存、CPU 等資源的限制。
如今互聯網面對的都是海量的數據、海量的用戶。咱們爲了提升數據的讀取、寫入能力,通常都採用分佈式的方式來存儲數據,好比分佈式緩存。咱們有海量的數據須要緩存,因此一個緩存機器確定是不夠的。因而,咱們就須要將數據分佈在多臺機器上。
該如何決定將哪一個數據放到哪一個機器上呢?咱們能夠借用前面數據分片的思想,即經過哈希算法對數據取哈希值,而後對機器個數取模,這個最終值就是應該存儲的緩存機器編號。
可是,若是數據增多,原來的 10 個機器已經沒法承受了,咱們就須要擴容了,好比擴到 11 個機器,這時候麻煩就來了。由於,這裏並非簡單地加個機器就能夠了。
原來的數據是經過與 10 來取模的。好比 13 這個數據,存儲在編號爲 3 這臺機器上。可是新加了一臺機器中,咱們對數據按照 11 取模,原來 13 這個數據就被分配到 2 號這臺機器上了。
所以,全部的數據都要從新計算哈希值,而後從新搬移到正確的機器上。這樣就至關於,緩存中的數據一會兒就都失效了。全部的數據請求都會穿透緩存,直接去請求數據庫。這樣就可能發生雪崩效應,壓垮數據庫。
因此,咱們須要一種方法,使得在新加入一個機器後,並不須要作大量的數據搬移。這時候,一致性哈希算法就要登場了。
假設咱們有 k 個機器,數據的哈希值的範圍是 [0, MAX]。咱們將整個範圍劃分紅 m 個小區間(m 遠大於 k),每一個機器負責 m/k 個小區間。當有新機器加入的時候,咱們就將某幾個小區間的數據,從原來的機器中搬移到新的機器中。這樣,既不用所有從新哈希、搬移數據,也保持了各個機器上數據數量的均衡。 一致性哈希算法的基本思想就是這麼簡單。除此以外,它還會藉助一個虛擬的環和虛擬結點,更加優美地實現出來。這裏我就不展開講了,若是感興趣,你能夠看下這個https://www.sohu.com/a/158141377_479559。
拿上面的例子來講:
其實就是將%10以後對應於機器3的數據copy到機器2上去便可,只要是對應機器編號會改變的都須要搬移(這裏我想得是這種海量數據的搬移,難道不須要花費時間和精力嗎?)
除了咱們上面講到的分佈式緩存,實際上,一致性哈希算法的應用很是普遍,在不少分佈式存儲系統中,均可以見到一致性哈希算法的影子。
首先能夠看看:區塊鏈入門
總的來講:
區塊鏈是一塊塊區塊組成的,每一個區塊分爲兩部分:區塊頭和區塊體。
區塊頭
保存着 本身區塊體
和 上一個區塊頭
的哈希值。
由於這種鏈式關係和哈希值的惟一性,只要區塊鏈上任意一個區塊被修改過,後面全部區塊保存的哈希值就不對了。
咱們拿修改一個區塊來一步步看爲何後面全部區塊保存的哈希值就不對了:
區塊鏈使用的是 SHA256 哈希算法,計算哈希值很是耗時,若是要篡改一個區塊,就必須從新計算該區塊後面全部的區塊的哈希值,短期內幾乎不可能作到。
參考自:極客時間 數據結構與算法之美