短網址服務系統如何設計

廬山美景.jpeg

短網址 顧名思義,就是將長網址縮短到一個很短的網址,用戶訪問這個短網址可以重定向到原本的長網址(還原)。這樣可以達到易于記憶、轉換的目的,常用于有字數限制的微博、二維碼等場景。
開篇先拋出幾個問題,如果大家自己去實現會怎么實現這個看似很簡單的服務呢?

  1. 是否有什么算法可以直接把一百個字符左右的長網址縮短到10位以內,并可以原樣還原,即可逆。
    10倍的壓縮比在無損壓縮算法領域誰介紹下?當然這個比例是基于多樣數據而不是特定的文本,否則文本只有一個字符a,那壓縮比想多少有多少。

  2. 只實現字符壓縮/hash,不需要做到可逆,然后存儲到數據庫中,恢復時只需要查詢數據庫。
    從壓縮的角度和第一條說明沒有區別,不可能無損壓縮,那就有可能出現hash碰撞。不同的長網址縮短成了同一個短網址,那也做不到還原了。

  3. 接著第二條,出現碰撞了可以后面再加隨機字符。
    隨機字符可以緩解碰撞問題,但是無法根治。另外,增加幾位字符呢才可靠呢?這種概率事件無法通過測試來回答,通過運維監控不斷的調整也是一件頭疼和折磨人的事。

  4. 預先在redis/db里異步生成一批短碼,每次從列表里面取不就好了。
    具體是在redis還是db里批量生成其實是截然不同的兩種實現。 若是redis, 那么里面要放入全量的短碼么?否則怎么知道這個短碼到底是不是唯一的?如果全量,那對redis的可用性就要嚴格保證,否則它掛了,就必須全量的預熱,這個過程要做好不是那么的容易; 若是db, 那么就要有大量的并發鎖定,意味著大量讀寫,這個對數據庫也是個考驗。

  5. 短網址的還原跳轉用301還是302呢?
    301是永久重定向,302是臨時重定向。短地址一經生成就不會變化,所以用301是符合http語義的。同時瀏覽器會對301請求保存一個比較長期的緩存,這樣就減輕對服務器的壓力;而且301對于網址的SEO有一定的提升。但是很多情況下我們需要對接口點擊或者用戶行為進行一些業務監控處理的時候,301明顯就不合適了(瀏覽器直接按照緩存數據跳轉了), 所以很多業務場景下還是采用302比較合適。


請結合上述問題后的描述思考5分鐘,然后我們開始正文


首先聲明本文設計思路也只是取自己業務場景可以容忍的情況,而有所取舍.
沒有完美的系統只有適用的系統。

縮短網址的算法

# 初期選型算法

對于算法本人也算是踩了不少的坑,最開始采用的是網上流傳甚廣的微博短網址算法(原理類似16進制與62進制的轉換),加了些許改進:

  1. 將"原始鏈接(長鏈接)+ key(隨機字符串,防止算法泄漏)"MD5后得到32位的一個字符串A
  2. 將上面的A字符串分為4段處理, 每段的長度為8 , 比如四段分別為 M、N、O、P
  3. 將M字符串當作一個16進制格式的數字來處理, 將其轉換為一個Long類型。 比如轉換為L
  4. 此時L的二進制有效長度為32位, 需要將前面兩位去掉,留下30位 , 可以 & 0x3fffffff 進行位與運算得到想要的結果。(30位才能轉換62進制,否則超出)
  5. 此時L的二進制有效長度為30位, 分為6段處理, 每段的長度為5位
  6. 依次取出L的每一段(5位),進行位操作 & 0x0000003D 得到一個 <= 61的數字,來當做index
  7. 根據index 去預定義的62進制字符表里面去取一個字符, 最后能取出6個字符,然后拼裝這6個字符成為一個6位字符串,作為短鏈接碼
  8. 拿出第②步剩余的三段,重復3-7 步
  9. 這樣總共可以獲得 4 個6位字符串,取里面的任意一個就可作為這個長鏈接的短網址
  10. 串碼添加校驗位checksum,用于簡單校驗。所以總共7位碼

算法其實并不太復雜,大家自行解讀。這個算法在服務量不大的情況下hash碰撞的概率尚可以接受,一定量的壓測效果也還算理想。因為有4個hash可選項,即使碰撞到了還有其他3次機會去避免。但是如果作為基礎服務,在使用方調用量級無法估量無法保證短鏈接絕對可用的情況下,這個算法還是有很大的隱患!

# 改進算法

只要有hash就有碰撞的可能,要一勞永逸就得拋棄hash算法。這里我們就用全局自增長的10進制序號 -> 62進制實現。這里繼續拋出問題來了:
1. 字符超長問題
即使到了10億(Billion)轉換而成的62進制也無非是6位字符,所以長度基本不在考慮范圍內,這個范圍足夠使用了。

2. 短碼安全問題
按照算法從0-61都是1位字符,然后2位、3位...這樣的話很容易被人發現規律并進行攻擊,當然防御手段很多,請求簽名之類的安全驗證手段不在本文討論范圍內。
首先計數器可以從一個比較大的隨機中間值開始,比如從10000開始計數,他的62進制是 2Bi 3位的字符串;
然后采用一些校驗位算法(比如Luhn改進一下),計算出1位校驗位拼接起來,4位短碼,這樣可以排除一定的安全風險;
再加點安全料的話,可以在62進制的轉換過程中把排序好的62個字母數字隨機打亂,比如ABCD1234打亂成1BC43A2D, 轉換的62進制也就更難hack了;
最后如果仍不放心,還可以在某些位置(比如1,3,5)插入隨機數,讓人無法看出規律來也可以達到良好的效果。

3. 同一長網址短碼是否應該相同問題
這個問題按照碼農的完美主義原則,基本上回答是Yes。做到也不麻煩,比如對長網址進行sha1生成的hash值存入hashtable或者redis,在縮短之前進行hash值比對,如果相同就查詢出之前生成的短碼即可。
但是緩存內預熱多少sha1值讓其比對,多少會穿透到數據庫進行比對,就是你自己需要對熱點數據如何預熱和緩存命中的問題了,這里不展開。

4. 自增算法是否完美無缺
相比上面的hash分段算法,自增算法能已經基本可以保證碼唯一、同址同碼的目標了。但是它也有缺陷,那就是隨著序號的自增,碼越來越長,到了很大的數值后沒有辦法循環往復,讓碼重新變短!而hash分段就沒有這個問題。
所以這兩個算法其實各有優劣,如果業務所需的短網址有效期相對較短,通過批處理定期清洗掉,那第一種算法不失為一種可選方案;
而自增算法對于無差別的業務短網址,可以保證任何的請求量都不會出現沖突,權衡下來不失為最佳之選。碼無非分為永久碼或臨時碼,結合業務的話其實大部分的碼都是臨時碼,只是或長或短而已,所以甚至可以設計永久的碼序號區間是0-10000,0-5天的碼10001-20000,5-30天的碼20001-30000,30天+的碼30001-無窮。這樣就可以實現序號的重復使用了。當然如果你對自己設定的區間沒有自信的話(溢出),請千萬不要這么做。

Redis/DB 如何設計

# DB設計

只需要一張表,存放短碼與原網址的映射關系,其他一些屬性比如原網址的sha1碼,過期時間等保存好即可。當然短碼和sha1字段都要加上唯一索引,保證唯一性的同時提高查詢效率。

# Redis設計

若想短鏈接服務達到低延遲高并發的目標,Redis在很多環節都可以起到關鍵作用。
1. 自增長序列
通過Redis的 incr 方法可以很容易的實現全局自增長序列,但前提是Redis的高可用,如果Redis掛了序列從哪里開始呢?當然是從DB中拿咯,怎么拿?
方式一:DB表中新增一個字段,存入最新的短碼基于的序號值,然后Redis在此基礎上+1即可。這部分代碼務必做好同步; 方式二:直接從DB中獲取最新的短碼,然后逆向計算出序號值,+1后繼續;

2. 長網址的Hash表
在Redis中存入熱點網址的hash映射數據,注意,這里說的是熱點網址而且不是全量網址,實現者需要有所取舍。或者沒有命中的就產生新的短碼(會導致同址不同碼),或者沒有命中就到數據庫查詢,保證強一致的同址同碼。

3. 短碼與長網址的映射表
同樣,在Redis中存入熱點網址的映射,在短網址還原的請求處理中可以快速的查詢到原網址。所以這個點的緩存是必須的。

其他一些說明

  • 原網址的Hash方法文中是sha1是選擇之一,類似Murmur64等更快更優的hash算法,可以自行采納
  • 類似的服務已經有比較成熟的開源解決方案,比如YOURLS,不想重復造輪子的同學可以直接拿來用
  • 如果要對短鏈接做一些監控,比如訪問量,可以通過Redis自行實現。目前公司應用集成了點評開源CAT,所以訪問量監控都不是問題

文終。
任何疏漏或者意見,歡迎討論。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,923評論 6 535
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,740評論 3 420
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,856評論 0 380
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,175評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,931評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,321評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,383評論 3 443
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,533評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,082評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,891評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,067評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,618評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,319評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,732評論 0 27
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,987評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,794評論 3 394
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,076評論 2 375

推薦閱讀更多精彩內容

  • 本文將從Redis的基本特性入手,通過講述Redis的數據結構和主要命令對Redis的基本能力進行直觀介紹。之后概...
    kelgon閱讀 61,213評論 23 625
  • 一、背景分析 二維碼的出現使資源傳輸由原來的USB拷貝轉變為二維碼掃描訪問或下載。為下載資源提供短網址服務,需將短...
    duanwangzhi閱讀 321評論 0 0
  • linux 啟動 redis:cd /usr/local/redis-3.2.0src/redis-server ...
    晏子小七閱讀 9,851評論 0 11
  • Redis的內存優化 聲明:本文內容來自《Redis開發與運維》一書第八章,如轉載請聲明。 Redis所有的數據都...
    meng_philip123閱讀 18,918評論 2 29
  • 那天,瑤瑤哭了。跟客戶對罵了幾句,甩手不干了,嘴里嘟嘟著“辭職”,轉身沖進小屋開始抽抽。我坐在那兒,不知所措,因為...
    嗄嗄嘻嘻閱讀 257評論 0 0