目錄
1:什麼是短連結
2:為什麼需要短連結
3:系統的設計與目標
4:系統API的設計
5:資料庫設計
6:系統的實現
7:結語
短連結顧名思義,就是一個比較短的連結,我們平時看到的連結可能由於一些其他的問題導致我們的連結比較長,可能長這樣:
https://www.baidu.com/s?wd=%E4%B8%8A%E6%B5%B7%E8%87%B4%E5%85%A8%E5%B8%82%E4%BA%BA%E6%B0%91%E7%9A%84%E6%84%9F%E8%B0%A2%E4%BF%A1&tn=baidutop10&rsv_idx=2&ie=utf-8&rsv_pq=abb53d500004deb0&oq=%E7%88%86%E6%BB%A1!%E4%B8%8A%E6%B5%B7%E5%B0%8F%E5%90%83%E5%BA%97%E9%81%87%E8%BF%9E%E5%A4%9C%E6%8A%A5%E5%A4%8D%E6%80%A7%E6%B6%88%E8%B4%B9&rsv_t=46b1xbgK2MjS8qj9hu%2B06QusLsL5QfltAGjtfKUgayO%2BvSC0MajShrNGroi3h1s5eQ&rqid=abb53d500004deb0&rsf=33e47e97990129c1c0bb42a6c85e13b9_1_15_1&rsv_dl=0_right_fyb_pchot_20811&sa=0_right_fyb_pchot_20811
如果我們需要將某個連結髮在某個文章或者推廣給別人的時候,這麼長的連結不僅會給人一種喧賓奪主的感覺,而且影響我們整篇文章的排版和美感,而短連結的出現就是用一個很短的URL來替代這個很長的連結,當用戶存取短連結的時候,會重定向到原來的連結。比如縮短之後的連結下面這樣:shorturl.at/fnDQS,我們也可能會注意到一些商業推廣的簡訊都會轉成一些很短的連結的形式。
URL短連結用於為長URL建立較短的別名,我們稱這些縮短的別名為「短連結」;當用戶點選這些短連結時,會被重定向到原始URL;短連結在顯示、列印、傳送訊息時可節省大量空間。我們也可以基於這些短連結來統計連結存取量等資訊。
URL縮寫經常用於優化裝置之間的連結,跟蹤單個連結以分析受眾,衡量廣告活動的表現,或隱藏關聯的原始URL。
如果你以前沒有使用過短連結相關的系統,可以嘗試使用https://www.shorturl.at建立一個短連結並存取,並花一些時間瀏覽一下他們的服務提供的各種選項。可以讓你更好的理解短連結的應用場景。
我們的短連結系統總共需要滿足的目標有兩方面功能性需求和非功能性需求
給定一個URL,我們的服務應該為其生成一個較短且唯一的別名,這叫做短連結,此連結應足夠短,以便於複製和貼上到應用程式中;
當用戶存取短連結時,我們的服務應該將他們重定向到原始連結;
使用者即使輸入的是相同的長連結,生成的短連結也應該是不相同的;
連結可以在指定時間跨度之後過期,使用者應該能夠指定過期時間;
可以統計短連結的存取次數;
系統必須高可用。
URL重定向的延遲情況應該足夠小;
短連結應該是不可猜測的。
本著系統介面易用性和擴充套件性的要求,我們設計介面是應考慮如下幾個問題,介面職責單一性,擴充套件性,安全性,冪等性,可閱讀性。結合以上幾個問題考慮,設計如下兩個API,建立短連結的API和存取短連結的API,根據職責單一性和可閱讀性,我們明確的定義的介面的名稱,參數列,返回值等資訊,介面呼叫失敗返回錯誤資訊和錯誤碼,而且每一個介面都會有明確的使用檔案。由於系統要求即使是相同的長連結,建立短連結也會得到不同的短連結,所以我們建立短連結的介面不做冪等處理,而存取短連結的介面只是做重定向和存取統計功能,天生冪等。在考慮介面的安全性,我們可以在建立短連結的API上加上介面防刷策略等,在存取短連結的API上根據短連結做布隆過濾器,短連結不存在的請求快速失敗等。
class CreateUrlMappingCmd {
/**
* 原始的長連結
*/
private String originUrl;
}
成功生成短連結將返回短連結URL;否則,將返回錯誤程式碼。
建立短連結介面返回的短連結
介面重定向到長連結,無返回值
由於業務邏輯相對簡單,所以資料持久化如果使用關聯式資料庫的話,這裡僅使用一張表就可以解決儲存問題。所以這裡的問題重點是我們要考慮資料量的問題。
create table url_mapping
(
id bigserial not null primary key,
short_url char(6) default '' not null,
origin_url varchar(200) default '' not null,
start_timestamp timestamp default current_timestamp::timestamp without time zone not null,
end_timestamp timestamp default current_timestamp::timestamp without time zone not null
);
create unique index url_mapping_short_url_index on url_mapping (short_url);
comment on table url_mapping is 'short url and origin url mapping';
comment on column url_mapping.id is '主鍵';
comment on column url_mapping.short_url is '短連結';
comment on column url_mapping.origin_url is '原始的連結';
comment on column url_mapping.start_timestamp is '對映有效開始時間';
comment on column url_mapping.end_timestamp is '對映有效結束時間';
假設每秒鐘200個建立短連結的請求,一條資料儲存500位元組,那麼每年的硬碟使用量為
200 * 60 * 60 * 24 * 30 *12 * 500 / 1024 /1024 / 1024 ≈ 2896GB ≈ 3TB
所以如果使用關聯式資料庫的話我們要考慮分庫分表的設計方案比如採用一致性hash演演算法根據key去路由到不同的資料庫去儲存,這樣有利於我們橫向擴充套件;當然也可以採用mongoDB這樣的檔案資料庫來儲存擴充套件的時候相對容易一些。
考慮到系統的API響應時間問題,所以會在介面的存取的同時加上一層快取,降低介面的響應時間,同時也避免請求直接到達資料庫,導致資料庫壓力過大,造成服務不可用。所以為了保證快取的高可用,我們也會在快取這一層做叢集,保證快取的高可用。
建立短連結的實現主要包括以下幾個關鍵點
1:短連結的對映
2:資料的儲存(包括資料儲存資料庫的路由,快取的更新等)
3:建立的短連結要放入布隆過濾器,防止存取短連結的時候快速失敗
建立短連結的對映有以下兩種方式建立短連結,1:通過雜湊對映的方式 2:通過Id自增對映的方式,通過Id對映的方式可以有以下幾種實現,資料庫自增Id、Redis自增Id,UUID。下面會一一介紹這幾種實現方式。
一般的雜湊對映有MD5、SHA、谷歌Murmur等,由於MD5和SHA的實現考慮到加密性的問題,所以效率不如谷歌的Murmur演演算法高效,而且谷歌Murmur的離散性會更好一點。但是還會有以下兩個問題,1:雜湊衝突、2:雜湊值作為短連結依然過長。
雜湊衝突,既然是雜湊演演算法,就不能避免雜湊衝突,所以為了解決HASH衝突的問題我們可以在原來的URL上加上時間戳和亂數的拼接作為鹽值來減少HASH衝突的問題,如果在衝突的話我們可以使用重試的方式再次HASH。
短連結過長,一般我們得到的雜湊值是一個32為的十六進位制數,如果使用這個值作為短連結的話可能還是太長,我們考慮url一般是字母的大小寫和資料組合而成的,所以我們可以考慮使用62進位制數來解決URL過長的問題,如果短連結的長度為6位的話,那麼我們的最大數為62的6次冪。那麼系統可以使用的時間為:pow(62,6)/(200 * 60 * 60 * 24 * 30 *12)≈ 9年,如果時間不夠的話我們可以擴充套件到7位,就可以使用566年了,哈哈哈,足夠用了。由於篇幅問題這裡就不放具體的程式碼實現了,文章末尾會附上具體的程式碼實現的完整連結,具體的實現可以參考com.philosophy.web.domain.generate.GenerateShortUrlByHash
如果使用Id對映的話我們可以使用一個數位和一個長連結,由於數位特別大的時候可能也會特別長,所以我們依然可以使用上述的62進位制樹來解決這個問題。但是我們依然要考慮以下幾個問題,資料庫和Redis的自增Id是連續的,很容易可以猜想到下一個短連結是什麼,由於是高可用,所以我們可以在叢集的每一個節點中每一次獲取一定量的序號(假設每次獲取10000個序號,要保證獲取和更新資料庫的原子性),然後在將這一萬個需要亂序,這樣就不可以猜想到下一個短連結是什麼了,當然如果要使用UUID比如雪花id的實現的話就可以不用考慮這些問題了。自增Id具體實現com.philosophy.web.domain.generate.GenerateShortUrlByIdentity,雪花Id的實現com.philosophy.web.domain.generate.GenerateShortUrlBySnow
存取短連結的實現就比較簡單了,防止惡意存取,這裡加上布隆過濾器,具體的功能我們只需要根據短連結獲取長連結,然後釋出存取事件(可以進行後續的擴充套件,比如統計URL的存取次數,也可以起到非同步解耦和提高介面效能),最後進行請求重定向就可以了,重定向的時候我們要注意一個小點(返回碼301和302的區別),由於我們這裡要統計介面的存取次數,所以返回302。具體實現com.philosophy.web.controller.VisitController#redirect
載入資料到布隆過濾器
系統雖小,五臟俱全,通過對本系統的實現,也學習到了很多新的知識以及一些系統的設計思想;路漫漫其修遠兮,吾將上下而求索。
原始碼地址: