設計一個短網址系統

提出問題

如何設計一個像百度短網址同樣的服務,一個生成短網址、將短網址定向到原始URL的服務。redis

解決方案

首先,須要一個一對一的映射表,去獲取短URL,而後根據他恢復出完整的URL。這將會涉及到將這些數據保存到數據庫。數據庫

咱們要考慮下面這些問題:服務器

  1. 短網址轉換時候的長度是多少?
  2. 映射函數是什麼?
  3. 提供服務的是單機仍是集羣?

轉換長度

咱們假設咱們設計的系統能夠提供超過10000億的URL,若是咱們以從62個字符[a-z,A-Z,0-9]中選取n個做爲短網址,咱們能夠存儲62^n個網址。因此,在知足條件的狀況下,咱們選擇最小的n。對於咱們的需求來講,咱們取n=7,這樣能夠提供短網址的個數是62^7 ~= 35000億分佈式

通常方案

一切從簡,咱們假定短網址相似http://dwz.com/<alias_hash>,其中alias_hash是一個計算過得字符串。函數

最開始呢,咱們把全部的映射存到一個單機數據庫,直接生成一個隨機的長度爲7的字符串alias_hash做爲映射中的鍵ID性能

因此,咱們只須要存儲<ID, URL>。當使用者輸入完整網址http://www.google.com,系統生成一個像abcd123同樣的長度爲7的隨機字符串做爲ID,存儲到數據庫中就是<abcd123, http://www.google.com>google

使用的時候,訪問http://dwz.com/abcd123,系統搜索IDabcd123,找到後重定向到http://www.google.comurl

此方案存在的問題

咱們不能保證經過長URL生成alias_hash的惟一性。生成alias_hash的時候可能會發生衝突(2個長URL映射到同一個短URL),但咱們須要每一個長URL生成的短URL都是惟一的,這樣咱們才能根據短URL定位到惟一的長URL,可是計算alias_hash的函數是單向函數設計

這裏是否是單向函數其實不重要,咱們不須要根據短網址再次作計算,只須要搜索,不知道做者爲何這麼寫。

改進方案

一個很簡單但依然高效的方案,創建這樣的數據表:code

Table Tiny_Url(
    ID : int PRIMARY_KEY AUTO_INC,
    Original_url : varchar,
    Short_url : varchar
)

其中自增主鍵ID用來作這樣的轉換:(ID, 10) <==> (short_url, BASE)。使用者隨時插入一個長URL,會返回最新的插入ID,把他轉化爲短URL標識,保存這個短URL標識,並返回給使用者。

轉換方法源碼(用於ID和短URL的互相轉換)

string idToShortURL(long int n)
{
    // Map to store 62 possible characters
    char map[] = "abcdefghijklmnopqrstuvwxyzABCDEF"
                 "GHIJKLMNOPQRSTUVWXYZ0123456789";
  
    string shorturl;
  
    // Convert given integer id to a base 62 number
    while (n)
    {
        shorturl.push_back(map[n%62]);
        n = n/62;
    }
  
    // Reverse shortURL to complete base conversion
    reverse(shorturl.begin(), shorturl.end());
  
    return shorturl;
}
  
// Function to get integer ID back from a short url
long int shortURLtoID(string shortURL)
{
    long int id = 0; // initialize result
  
    // A simple base conversion logic
    for (int i=0; i < shortURL.length(); i++)
    {
        if ('a' <= shortURL[i] && shortURL[i] <= 'z')
          id = id*62 + shortURL[i] - 'a';
        if ('A' <= shortURL[i] && shortURL[i] <= 'Z')
          id = id*62 + shortURL[i] - 'A' + 26;
        if ('0' <= shortURL[i] && shortURL[i] <= '9')
          id = id*62 + shortURL[i] - '0' + 52;
    }
    return id;
}

服務器集羣

若是咱們的服務要處理大量數據,分佈式存儲能夠提升咱們的吞吐量。思路也很簡單,計算出原始URL的hash值,而後去對應的及其存儲,而後就和單機狀況同樣了。一般使用一致性hash路由到集羣中對應的節點。

下面的例子是僞代碼:

獲取短URL

  1. 將原始URL哈希爲2爲數字,假設爲hash_val
  2. hash_val定位一致性hash環上的機器。
  3. 像對應的數據庫中插入原始URL,經過idToShortURL()獲取短URL
  4. 合併hash_val和短URL做爲咱們最終短URL(長度=8),返回給使用者。

根據短URL獲取原始URL

  1. 從最終短URL中獲取前兩個字符做爲機器標識hash_val
  2. 經過hash_val定位到機器。
  3. 經過最終短URL中的剩餘6個字符做爲短URL,去數據庫獲取對應的記錄。
  4. 返回原始URL給使用者。
這裏可能有問題,坐着的意思多是最多62臺機器,應該是第一個字符是標識機器的,值多是 [a-z,A-Z,0-9]中的一個。
看了評論,這裏有個問題,若是某臺機器掛了,摘掉以後,須要將該機器上的數據複製到一致性hash環上的下一臺機器的時候,會發生衝突,因此仍是使用redis之類的生成全局惟一的 ID,直接落到環上對應的機器,這樣設計更好。

其餘考慮

這裏我想深刻討論的是使用GUID(全局惟一標識)做爲ID,與增量ID相比有哪些優缺點?

若是你深刻insert/query處理語句的話,你會發現使用隨機字符串做爲ID可能會犧牲一小部分性能。特別是當已經有無數的記錄的時候,插入是很昂貴的操做,數據庫須要尋找合適的頁存儲插入的ID。可是,使用增量的ID,插入會簡單不少,直接找到最後的頁就好了。

相關文章
相關標籤/搜索