天天看點

長連結轉短連結的一次嘗試

偶然的一次業務需求,需要使用到這樣的功能。雖然很多大平台提供了這樣的接口(新浪,百度等等)。但是還是對其中的原理想在梳理一下。

我們不妨先來看一下短連結服務的整個流程,以前面提到的微網誌短網址服務為例。使用者輸入想要縮短的長網址,轉化後得到一個以

http://t.cn

開頭的短網址,然後使用者将該連結通過微信或者微網誌等方式分享給朋友,其他人點選之後即可進入原本長網址所對應的頁面。整個流程如下圖所示:

長連結轉短連結的一次嘗試

從圖中可以很清楚地看到,實作短連結服務的關鍵是兩個步驟:1、如何把一個任意長的字元串轉化成一個較短的字元串;2、從短網址如何還原出長網址。第一個問題很容易讓人想到雜湊演算法,通過一定的方式将任意長的文本轉化成一個固定長度的字元串,隻要目标字元串的長度适當,那麼不同的輸入幾乎不可能對應同一個字元串。不過這麼做有個缺點就是無法從得到的結果還原輸入的字元串,是以不适用于我們的場景。但基于雜湊演算法的思想,我們可以設計一種以多進制為基礎的算法完成這個任務。

具體而言,我們可以建立一個用于儲存長網址的資料表,比如就叫Url,這張表很簡單,隻需要兩個字段,一個主鍵用于儲存id,一個url字段用于存放原始的長網址,每個長網址都在這張表有一條記錄。當進行長網址轉換時,先檢查資料表中是否存在該長網址,若是直接擷取該記錄的id,否則在資料表中建立一條新記錄,并傳回其id。對于這個id,我們可以得到一個多進制表示下的新值,比如在以“0-9a-z”這36個字元表示的36進制中,一億這個數字可以被表示成1njchs,隻需要6個字元即可,将這6個字元拼接到準備好的域名後即可得到一個對應的短網址傳回給使用者。由于一億個網址隻需要6個字元,是以這種方式足夠滿足大部分網站的需求。

而當使用者點選了我們生成的短網址後,隻需要将代表多進制的這部分提取出來,還原成十進制的數字後查表即可得到原始的長網址,再根據網址做一個重定向即可讓使用者通路到原始的網頁。具體的實作可以參考下面的

typescript

代碼

// 将原始的長連結通過36進制轉化為短連結
export async function long2short(url: string) {
    if (!url.startsWith('http://') && !url.startsWith('https://')) {
        throw new Error('Invalid url');
    }
    if (url.startsWith(config.shortLinkBaseUrl)) {
        return url;
    }
    let item = await Url.getByUrl(url);
    if (!item) {
        item = await Url.create(url);
    }
    return config.shortLinkBaseUrl + item.id.toString(36);
}

// 将短連結還原為真實的長連結
export async function short2long(url: string) {
    let item = await Url.select(Number.parseInt(url, 36));
    if (!item) {
        throw new Error('Invalid url');
    }
    return item.url;
}
           

這裡的config.shortLinkBaseUrl也就是我們用來做短連結服務的域名,在前面的例子中就是http://t.cn,我們需要在這個域名對應的伺服器内實作短連結的服務,同時這個域名本身不能太長,否則就失去了它的意義。另外還有一點值得注意,就是在根據長網址去資料表查找它是否存在時,因為長網址可以任意長,是以直接用它作為索引在資料表中查找的話效率較低,可以考慮在表中增加一個hash字段,儲存長網址的哈希值,并通過查找哈希值來判斷條目是否存在,提高查找的效率。

以上就是短連結服務的基本實作方法,最核心的其實就是多進制的使用。

當然網上還有一種對于這種功能實作最優的方法。

最優的算法

通過發号原理

顧名思義這個系統的第一個請求過來了,我們以微網誌為例,短連結系統的第一個請求我們可以給變為t.cn/0,第二個t.cn/1等等;

實作的方式也會很簡單

1、小型的系統用MySQL的自增索引就可以滿足。

2、大型系統可以考慮分布式key-value系統。

存儲原理

發号政策是這樣的,當一個新的連結過來時,發号器發一個号與之對應。往後隻要有新連結過來,發号器不停發号就好。舉個例子,第一個進來的連結發号器發0号,對應的短連結為 xx.xxx/0,第二個進來的連結發号器發1号,對應的短連結為 xx.xxx/1,以此類推。

發号器發出的10進制号需要轉換成62進制,這樣可以大大縮短号碼轉換成字元串後的長度。比如發号器發出 10,000,000,000 這個号碼,如果不轉換成62進制,直接拼接在域名後面,得到這樣一個連結 xx.xxx/10000000000。将上面的号碼轉換成62進制,結果為AOYKUa,長度隻有6位,拼接得到的連結為 xx.xxx/AOYKUa。可以看得出,進制轉換後得到的短連結長度變短了一些。6位62進制數,對應的号碼空間為626,約等于568億,是以基本上不用擔心發号器無号可發的情況。

高并發場景下

上面設計看起來有一個單點,那就是發号器。如果做成分布式的,那麼多節點要保持同步加1,多點同時寫入。這樣難以避免産生單點性能瓶頸。是以我們可以考慮将單點變為多點。我們可以引入多個機器,我們可以設定機器A發号隻發向100取餘等于0的數字100n,同理機器B隻發向100取餘等于1數字 100n+1,以此類推,各個機器互相獨立互不幹擾,我們可以随時擴充我們的機器了。

同一長連結,每次轉成的短連結是否一樣

同一長連結,每次轉成的短連結不一定一樣,原因在于如果查詢緩存時,如果未命中,發号器會發新号給這個連結。需要說明的是,緩存應該緩存經常轉換的熱門連結,假設設定緩存過期時間為一小時,如果某個連結很活躍的話,緩存查詢命中後,緩存會重新整理這個連結的存活時間,重新計時,這個連結就會長久存在緩存中。

我們也可以引入LRU算法。進行淘汰我們不經常使用到的連結。

重定向問題

選取301,還是302?

301是永久重定向,302是臨時重定向。

如果選擇301:短位址生成以後就不會變化,是以用301是符合http語義的。同時對伺服器壓力也會有一定減少。這樣一來,我們就無法統計到短位址被點選的次數了。

如果選擇302:選擇302雖然會增加伺服器壓力,但是可以統計到短位址被點選的次數了,我可以針對點選的次數來進行後期的大資料處理,機器學習,以及推薦算法。

選擇302還是301,想必讀者心中有肯定有數了。

以上就是我看到網上的一下文章。特此放在一起。也算做個參考吧。雖然是一個很小的需求,但是還是有的深究的。

繼續閱讀