如何設計短網址系統?

ldzsl發表於2021-09-09

短網址的長度

短網址的長度該設計為多少呢? 當前網際網路上的網頁總數大概是 45億,超過了 232=42949672962^{32}=4294967296232=4294967296,那麼用一個64位整數足夠了。

一個64位整數如何轉化為字串呢?,假設我們只是用大小寫字母加數字,那麼可以看做是62進位制數,log62(264−1)=10.7log_{62} {(2^{64}-1)}=10.7log62(2641)=10.7,即字串最長11就足夠了。

實際生產中,還可以再短一點,比如新浪微博採用的長度就是7,因為 627=352161460620862^7=3521614606208627=3521614606208,這個量級遠遠超過網際網路上的URL總數了,絕對夠用了。

現代的web伺服器(例如Apache, Nginx)大部分都區分URL裡的大小寫了,所以用大小寫字母來區分不同的URL是沒問題的。

因此,正確答案:長度不超過7的字串,由大小寫字母加數字共62個字母組成

一個長網址,對應一個短網址,還是可以對應多個短網址? 這也是個重大選擇問題

一般而言,一個長網址,在不同的地點,不同的使用者等情況下,生成的短網址應該不一樣,這樣,在後端資料庫中,可以更好的進行資料分析。如果一個長網址與一個短網址一一對應,那麼在資料庫中,僅有一行資料,無法區分不同的來源,就無法做資料分析了。

以這個7位長度的短網址作為唯一ID,這個ID下可以掛各種資訊,比如生成該網址的使用者名稱,所在網站,HTTP頭部的 User Agent等資訊,收集了這些資訊,才有可能在後面做大資料分析,挖掘資料的價值。短網址服務商的一大盈利來源就是這些資料。

正確答案:一對多

現在我們設定了短網址是一個長度為7的字串,如何計算得到這個短網址呢?

最容易想到的辦法是雜湊,先hash得到一個64位整數,將它轉化為62進位制整,擷取低7位即可。但是雜湊演算法會有衝突,如何處理衝突呢,又是一個麻煩。這個方法只是轉移了矛盾,沒有解決矛盾,拋棄。

MySQL資料庫有一個自增ID,能不能借鑑這個呢?每來一個長網址,就給它發一個號碼,這個號碼不斷的自增。這個方法跟雜湊相比,好處是沒有衝突,不用考慮處理衝突的問題。如何實現單臺的發號伺服器呢?可以用一臺MySQL伺服器來做(一定要用 REPLACE INTO,不要儲存所有ID),也可用一臺Redis伺服器(用INCR),一行程式碼也不用寫;也可以自己寫一個RESTful API,程式碼也很簡單,就不贅述了。

單臺發號器有什麼缺點呢?它是一個單點故障(SPOF, Single Point Of Failure),也會成為效能瓶頸(其實,如果你的QPS能大到壓垮這臺MySQL,那說明你的短網址服務很成功,可以考慮上市了:D),所以它適合中小型企業,對於超大型企業(以及在面試顯得追求高大上),我們還是要繼續思考更好的方案,請接著往下看。

下面開始講如何打造多臺機器組成的分散式發號器

  1. 使用[UUID]演算法或者MongoDB產生的[ObjectID]。其實MongoDB的ObjectID也算是一種UUID,這類演算法,每臺機器可以獨立工作,天然是分散式的,但是這類演算法產生的ID通常都很長,那短網址服務還有什麼意義呢?所以這個方法不行。

  2. 多臺MySQL伺服器。前面講了單臺MySQL作為發號伺服器,那麼自然可以擴充套件一下,比如用8臺MySQL伺服器協同工作,第一臺MySQL初始值是1,每次自增8,第二臺MySQL初始值是2,每次自增8,一次類推。前面用一個 round-robin load balancer 擋著,每來一個長網址請求,由 round-robin balancer 隨機地將請求發給10臺MySQL中的任意一個,然後返回一個ID。[Flickr用的就是這個方案],僅僅使用了兩臺MySQL伺服器。這個方法僅有的一個缺點是,ID是連續的,容易被爬蟲抓資料,爬蟲基本不用寫程式碼,順著ID一個一個發請求就是了,太方便了(手動斜眼)。

  3. 分散式ID生成器(Distributed Id Generator)。分散式的產生唯一的ID,比如 Twitter 有個成熟的開源專案,就是專門做這個的,[Twitter Snowflake] 。Snowflake的核心演算法如下:

圖片描述

最高位不用,最高位不用,永遠為0,其餘三組bit佔位均可浮動,看具體的業務需求而定。預設情況下41bit的時間戳可以支援該演算法使用到2082年,10bit的工作機器id可以支援1023臺機器,序列號支援1毫秒產生4095個自增序列id。

[Instagram用了類似的方案],41位表示時間戳,13位表示shard Id(一個shard Id對應一臺PostgreSQL機器),最低10位表示自增ID,怎麼樣,跟Snowflake的設計非常類似吧。這個方案用一個PostgreSQL叢集代替了Twitter Snowflake 叢集,優點是利用了現成的PostgreSQL,容易懂,維護方便。

因此,正確答案:分散式發號器(Distributed ID Generator),Flick, Twitter Snowflake 和 Instagram的方案都是不錯的選擇。

如果儲存短網址和長網址的對應關係?以短網址為 primary key, 長網址為value, 可以用傳統的關聯式資料庫存起來,例如MySQL, PostgreSQL,也可以用任意一個分散式KV資料庫,例如Redis, LevelDB。

如果你手癢想要手工設計這個儲存,那就是另一個話題了,你需要完整地造一個KV儲存引擎輪子。當前流行的KV儲存引擎有LevelDB何RockDB,去讀它們的原始碼吧。

這也是一個有意思的問題。這個問題主要是考察你對301和302的理解,以及瀏覽器快取機制的理解。

301是永久重定向,302是臨時重定向。短地址一經生成就不會變化,所以用301是符合http語義的。但是如果用了301, Google,百度等搜尋引擎,搜尋的時候會直接展示真實地址,那我們就無法統計到短地址被點選的次數了,也無法收集使用者的Cookie, User Agent 等資訊,這些資訊可以用來做很多有意思的大資料分析,也是短網址服務商的主要盈利來源。

所以,正確答案是302重定向

可以抓包看看新浪微博的短網址是怎麼做的,使用 Chrome 瀏覽器,訪問這個URL [],是我事先發微博自動生成的短網址。來抓包看看返回的結果是啥,

圖片描述

可見新浪微博用的就是302臨時重定向。

如果一些別有用心的駭客,短時間內向TinyURL伺服器傳送大量的請求,會迅速耗光ID,怎麼辦呢?

首先,限制IP的單日請求總數,超過閾值則直接拒絕服務。

光限制IP的請求數還不夠,因為駭客一般手裡有上百萬臺肉雞的,IP地址大大的有,所以光限制IP作用不大。

可以用一臺Redis作為快取伺服器,儲存的不是 ID->長網址,而是 長網址->ID,僅儲存一天以內的資料,用LRU機制進行淘汰。這樣,如果駭客大量發同一個長網址過來,直接從快取伺服器裡返回短網址即可,他就無法耗光我們的ID了。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/2325/viewspace-2797000/,如需轉載,請註明出處,否則將追究法律責任。

相關文章