1. 程式人生 > 其它 >神奇而又有趣的短鏈服務系統

神奇而又有趣的短鏈服務系統

技術標籤:程式語言javapython面試分散式

大家在刷微博或者在接收簡訊時應該都會注意過類似這樣的連結url:

weibo.com/4yBWU
weibo.com/42Ipf
weibo.com/2BmFL
weibo.com/23rwx

這些連結通常非常簡短,僅此被稱為短鏈,但是當你點選這些短鏈後卻會發現真實的url其實是一個很長的連結,類似這樣:

weibo.com/prd/direction.html?pageId=51e3d088bbfa489e818462a9d84167ff&pwId=dedb75e7516e46b29f181754ecffadb6&channelId=10010001005

效果就是明明你在瀏覽器中輸入的是一個短連結,但是回車後這個連結變成了一個很長的連結。你有沒有好奇過為什麼微博或者簡訊中的連結都非常短,這樣有什麼好處,這一切又是如何實現的呢?

為什麼要使用短鏈?

我們知道發微博是有字數限制的,當博主試圖轉發一個連結時如果連結本身很長就像上面這個連結的話那麼博主的內容會受到很大的限制,因此為了節省連結本身佔用的空間微博中大量使用了短鏈。

而對於簡訊來說,我們通常收到的垃圾簡訊其實是有商家希望推廣某種商品發出來的,傳送簡訊當然要收商家的錢,這些都是按字數收費的,當然字數越少費用就越低,因此短鏈也被大量應用於推廣簡訊當中。

那麼這一切都是如何實現的呢?

如何實現短鏈?

短鏈服務的功能非常簡單,無非就是把類似:

weibo.com/prd/direction.html?pageId=51e3d088bbfa489e818462a9d84167ff&pwId=dedb75e7516e46b29f181754ecffadb6&channelId=10010001005

轉為類似:

weibo.com/4yBWU

其實也就是把一個“長字串轉為短字串”,當用戶點選短鏈的時候我們只需要查詢該短鏈對應的真實連結並把使用者重定向過去就可以了。

那麼這裡的問題是該如何把一個長字串轉為唯一的一個短字串呢?

可能大家第一個想到的就是hash,沒錯,通過將長字串進行hash可以實現上述目的,但是不要忘了hash會存在衝突的可能性,也就是有可能兩個長鏈都對映到了某個短鏈上,有的同學也許會想這樣的概率很低吧,但是不要忘了像微博體量的內容,其短鏈的數量可能會有上百億,在這種情況下使用hash顯然就不滿足要求了。

在這裡我們需要一個短鏈和長鏈是唯一對應的,那麼最簡單的方法就是給每個長鏈唯一編號,我們可以從1開始給每個長鏈依次編號,這樣短鏈的形式就是這了:

weibo.com/1
weibo.com/2
weibo.com/3
weibo.com/4

但是這樣的方案會存在很多問題,比如這樣簡單的方法就給了爬蟲很大的便利,只需要簡單寫個指令碼就可以拿到微博幾乎所有的內容;而且該方案對於編號很大的長連結來說依然會佔用較多的字元,像十億(1000000000)這樣的數字依然佔用了10位數,因此簡單的編號不可用。

我們看到了簡單採用計數的方法有很多缺點,那麼該如何改進呢?

本質上這其實就是需要對:

1,2,3,4,5,6,7,8,9,10......

這樣的依次遞增計數方法換一種不是那麼直觀而且又節省長度的計數方式。


62進位制

說到節省數字本身的長度,有的同學第一反應可能就是使用更大的進位制來計數:

對於二進位制數來說,6個二進位制數字可以表示2^6,也就是64個數;

對於十進位制數來說,6個十進位制數字可以表示10^6,也就是一百萬個數;

對於十六進位制來說,6個十六進位制數字可以表示16^6,也就是超過一千六百萬個數;

而對於人類熟悉的數字(0~9)、小寫字母(a~z)、大寫字母(A-Z),這些字元加起來有62個數字,因此我們可以使用62進位制數,那麼6個62進位制數字可以表示62^6個數,也就是超過500億,我們只需要6位62進位制數就可以表示超過500億個短鏈。

那麼我們該如何把一個10進位制的id轉為一個由數字(0~9)、小寫字母(a~z)、大寫字母(A-Z)表示的62進位制數呢?相信這應該難不倒聰明的你吧,這應該是學習C語言時入門級別的練習題難度,C++版如下所示:

string long_url_2_short_url(long int id){
    string base = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
    string url;
    while(id) {
        url = (char)(base[id % 62]) + url;
        id /= 62;
    }
    return url;
}

程式碼非常簡單,核心僅有6行程式碼,輸入60000000該函式會返回43kjw,顯然weibo.com/43kjw 比weibo.com/60000000好多了。

現在我們已經成功的將十進位制id轉為62進位制的短鏈了,那麼給定一個短鏈我們該如何獲取其id呢?程式碼也非常簡單,無非就是把62進位制數轉為10進位制:

long int short_url_2_id(string url) {
    long int id = 0;
    for(int i=0; i<url.length(); i++){
        if ('0'<=url[i] && url[i]<='9')
            id = id*62 + url[i] - '0';
        else if ('A'<=url[i] && url[i]<='Z')
            id = id*62 + url[i] - 'A' + 10;
        else
            id = id*62 + url[i] - 'a' + 36;
    }
    return id;
}


實現短鏈服務

現在我們已經成功的將長鏈轉為了短鏈,也可以根據短鏈獲取到長鏈的id,那麼接下來的任務就是實現重定向。

當用戶點通過瀏覽器點選短鏈時,瀏覽器會向我們的短鏈服務傳送請求,短鏈服務接收到情況後根據短鏈計算出長鏈的id,查詢資料庫後得到長鏈比如叫做real_url,然後告訴使用者瀏覽器“hey,你跳轉到的real_url吧”,那麼短鏈服務該如何告訴使用者瀏覽器跳轉到真實的連結地址呢?

很簡單,我們只需要將返回給瀏覽器的長連結加一個302的http返回碼就可以了,使用者瀏覽器接收到請求後發現是一個302的返回資料,這時瀏覽器就去請求該真實長連結了。

而我們之所以將其稱為短鏈服務,是因為有一堆伺服器,這些服務只做一件事那就是提供短鏈服務,包括:

  • 將長鏈轉為短鏈並放入資料庫

  • 將使用者請求的短鏈重定向到真實的長鏈

也就是說對於短鏈,瀏覽器至少要傳送兩次請求才能獲得真實的內容,一次傳送給短鏈服務獲取真實的長鏈,再一次請求真實的長鏈。

總結

在本篇中我們看到了一種很有意思的網際網路服務,也就是短鏈服務,這種服務的目的就在於將一個長連結轉為一個很簡短的短鏈,當用戶請求短鏈時將使用者瀏覽器重定向到長鏈,這裡涉及的原理非常簡單,就是我們熟悉的進位制轉換。

但是在這裡不想給大家一種網際網路服務很easy的感覺,對於真實的短鏈服務來說還有很多要考慮的因素:

  • 這裡的短鏈實現方式其實依然沒有解決很容易被爬蟲爬取的缺點,因為62進位制的43kj6的下一個數很直觀就是43kj7,有沒有更好的辦法?

  • 由於我們將所有長鏈都轉為了短鏈,那麼當用戶請求到來時首先第一步就是請求短鏈服務,短鏈服務就成了一個關鍵點,一旦短鏈服務不可用影響極大,因此高併發、高可用是短鏈服務必須具備的,你知道這個短鏈服務的效能瓶頸會出現在哪裡嗎,你該如何優化系統性能,又該如何保證高可用呢?

這些都留給大家來思考。

PS:微信公眾號從去年開始限制了留言功能,如果你有任何問題歡迎直接在公眾號留言。


為你推薦

1,三次握手與Socket API
2,四次揮手與Socket API
3,一個耗時4小時的記憶體洩漏問題

作業系統系列

基礎篇
1,什麼是程式

2,程式?程序?傻傻分不清

3,程式設計師應如何理解記憶體:上篇
4,程式設計師應如何理解記憶體:中篇
5,程式設計師應如何理解記憶體:下篇
6,程式設計師應如何理解CPU:上篇
7,程式設計師應如何理解CPU:下篇
系統呼叫篇
8,作業系統是如何看待程序的
9,系統呼叫是如何實現的
10,程式設計師應如何理解系統呼叫:上篇
11,程式設計師應如何理解系統呼叫:下篇
12,以發展的眼光來看待作業系統