原文連接:www.educative.io/courses/gro…php
編者注:本文以一道經典的系統設計面試題:《如何設計TinyURL》的參考答案和解析爲例,幫助讀者更深刻地瞭解在系統需求分析和設計中,須要考慮的各個方面的細節。面試
本文將爲你們詳細講解如何設計一個相似於TinyURL的URL縮短服務。URL縮短服務提供一個很是短小的URL以代替原來的可能較長的URL,將長的URL地址縮短。算法
相似的服務有:bit.ly,goo.gl,qlink.me,等等。數據庫
6、基本系統設計與算法瀏覽器
咱們須要解決一個問題,如何爲給定的URL生成一個較短且惟一的字符串。緩存
在第一節的TinyURL示例中,縮短的URL是:tinyurl.com/jlg8zpc。這個短…服務器
a、編碼實際的URL數據結構
經過哈希算法(例如MD5或SHA256算法等),咱們計算給定URL的哈希值,而後對這個哈希值編碼並顯示,這種編碼方式能夠是Base36 ([a-z ,0-9]) 或者Base62 ([A-Z, a-z, 0-9]),若是咱們但願添加「+」「/」符號類的字符,還可使用Base64編碼。那麼,生成字符串的合理長度是多少呢?6個、8個或者10個字符是比較合適的。併發
若是咱們使用Base64編碼,那麼一個6個字符長度的字符串有64^6≈687億種可能的組合,一個8個字符長度的字符串有64^8≈281萬億種可能的組合。函數
對於咱們將要設計的系統來講,6個字符長度、687億種可能組合的字符串,就足夠使用了。
若是咱們使用MD5算法做爲哈希函數,它將產生一個128bit的哈希值。經過Base64編碼,將獲得一個超過21字符的字符串(由於每一個Base64字符編碼6bit哈希值)。若是,咱們生成字符串的長度只有8個字符的空間,那麼對這超過21個字符的字符串將如何進行選取呢?一種方法,咱們能夠選取前6(或8)個字符。這種方法可能會致使最終生成的字符串重複,爲了解決這個問題,咱們能夠從編碼字符串中選擇一些其餘字符或交換一些字符。
除了剛剛說的生成字符串重複的問題,還會碰到如下兩種問題:
一、當多個用戶輸入相同的URL時,將獲得相同的縮短URL,這是不被容許的。
二、若是URL的一部分是URL編碼的呢?例如http://www.educative.io/distributed.php?id=design和http://www.educative.io/distribu.php%3fid%3ddesign,這兩個URL除了URL編碼以外其餘都是相同的,但獲得的縮短URL不相同。
解決上述問題的方法:向每一個輸入URL添加一個遞增的序列號,使其惟一,而後生成它對應的哈希值。注意,這裏咱們不須要將這個序列號存儲在數據庫中。這種方法可能會致使序列號不斷增長,甚至有可能溢出。而且,附加一個遞增的序列號也會影響服務的性能。
另外一種解決方法是:將用戶id(這個應該是惟一的)附加到輸入URL。可是,若是用戶沒有登陸的話,則必需要求用戶選擇一個惟一的key。在這以後,若是用戶選擇的key有衝突,那系統必須繼續生成其餘的key,直到獲得惟一的結果。
b、離線生成字符串
咱們能夠建立一個獨立的密鑰生成服務(KGS),它預先生成隨機的6個字母長度的key,並將它們存儲在數據庫(咱們稱之爲key-DB)中。不管什麼時候,咱們想要縮短一個URL,都將使用一個已經生成的key。這種方法使得縮短URL變得很是簡單和快速。咱們不只不編碼URL,並且不用擔憂生成的key重複或衝突。KGS將確保插入到key-DB中的全部key都是惟一的。
接下來,咱們來考慮下併發的問題。key-DB中的key一旦被使用,那麼它就應該在key-DB中進行標記,確保不會重複使用。若是多個服務器同時讀取key-DB中的key的話,咱們可能會碰到兩個或者多個服務器試圖從key-DB讀取相同的key。針對這個問題,咱們又要如何解決呢?
服務器可使用KGS讀取/標記數據庫中的key。在KGS中建立兩個表來存儲key:一個用於未使用的,一個用於已使用的。只要KGS向其中一個服務器提供key,它就能夠將這些已使用的key移動到used keys表裏。KGS老是能夠在緩存中保留一些key,以便可以在服務器須要時快速提供key。
爲了簡單一點,只要KGS在緩存中加載一些key,就能夠將這些key移動到used keys表。這確保每一個服務器都有惟一的key。若是KGS在將全部加載的key分配給某個服務器以前宕機了,那麼這些未使用的key就浪費了。不過,這種狀況是能夠接受的,由於咱們有大量的key以供使用。
KGS還必須確保不向多個服務器提供相同的key。爲此,它必須同步(或鎖定)保存key的數據結構,而後從其中刪除該key並將其交給服務器。
key-DB的內存大小應該是多少呢?使用Base64編碼,咱們能夠生成687億個惟一的6個字母長度的key。若是咱們須要一個字節來存儲一個字符,那麼咱們存儲這些key所需的內存大小爲:
6 (字符/key)*687 億 =412 GB
另外,KGS是單點失效的。爲了解決這個問題,咱們能夠有一個KGS的備用副本,不管主服務器什麼時候宕機,備用服務器均可以接管來生成和提供key。
每一個應用服務器均可以從key-DB緩存一些key嗎?是的,這確定能加快運行速度。在這種場景下,若是應用服務器在使用全部key以前宕機了,致使咱們丟失了這部分的key,這也是能夠接受的。由於咱們擁有687億個惟一的key。
如何執行key查找呢?咱們能夠在數據庫或key-value存儲中查找key,以得到原始的URL。若是該key存在,則向瀏覽器發出「HTTP 302 Redirect」狀態,將請求的「Location」字段中存儲的URL傳遞回來。若是該key不在咱們的系統中,發出「HTTP 404 not Found」狀態或將用戶重定向回主頁。
咱們應該對自定義的字符串施加大小長度限制嗎?咱們的服務支持自定義縮短URL。用戶能夠選擇任何他們喜歡的key,但並不強制提供自定義功能。可是,對自定義key的大小長度進行限制是有必要的,這樣能夠確保咱們擁有一致的URL數據庫。假設用戶能夠爲每一個key指定最多16個字符(如前一篇中提到的數據庫Schema所反映的)。
(未完待續)
未經贊成,本文禁止轉載或摘編。