面試官:如何設(shè)計一個高并發(fā)的短鏈系統(tǒng)?
短鏈系統(tǒng)的核心功能,通過將長鏈接轉(zhuǎn)換為短字符串的方式,提升鏈接的可讀性和傳播性,適用于消息通知、廣告推廣、社交媒體等場景。
如下圖所示:
圖片
短鏈系統(tǒng)的核心功能有兩個,短鏈跳轉(zhuǎn)和短鏈生成,我們來一一看下具體實現(xiàn),最后再講講如何實現(xiàn)高并發(fā)的短鏈系統(tǒng)。
短鏈跳轉(zhuǎn)的實現(xiàn)原理
短鏈跳轉(zhuǎn)有HTTP 302和HTTP 301兩種實現(xiàn)方式,我們來分別講解一下。
永久重定向(301)
瀏覽器會緩存第一次請求短鏈所重定向的長鏈,之后再有相同的短鏈請求,就可以直接從瀏覽器緩存中獲取了,無須再給短鏈服務(wù)器發(fā)送請求獲取。
StatusCode: 301 Moved Permanently
Location: https://origin.com/long-url?param=value
圖片
臨時重定向(302)
當用戶訪問短鏈接的時候,短鏈服務(wù)器會返回原始的長鏈并進行HTTP重定向,將其轉(zhuǎn)發(fā)到長鏈服務(wù)器上,這一切對于用戶是無感的。
瀏覽器不會對請求短鏈所重定向的長鏈進行緩存,每次都需要請求短鏈服務(wù)器進行獲取。
Status Code: 302 Moved Temporarily
Location: https://origin.com/long-url?param=value
圖片
那問題來了,我們到底應該選擇HTTP 301還是302進行重定向呢?
如果我們的短鏈服務(wù)處于高并發(fā)場景,優(yōu)先選擇HTTP 301永久重定向的方式減少對服務(wù)的請求數(shù),從而降低服務(wù)器的壓力。
反之,在非高并發(fā)場景且對訪問短鏈服務(wù)有計數(shù)訴求,那我們可以選擇HTTP 302臨時重定向。
短鏈生成的實現(xiàn)原理
接下來,我們來講講短鏈生成的實現(xiàn)原理:長鏈哈希、自增序列、隨機字符串和預生成四種方案。
如下圖所示:
圖片
長鏈哈希
(1)目前常見的哈希算法有MD5、SHA1、MurmurHash等,先將長鏈通過這些算法生成一個哈希值。
如:將長鏈“https://www.example.com/very/long/url/with/parameters”進行哈希計算,得到結(jié)果“3a6bd3e2560a3d23eea436fcfb7e44c7”。
(2)將生成的結(jié)果字符串進行截斷,留下前6位短碼,得到結(jié)果“3a6bd3”。
目前,6位短碼已經(jīng)能夠滿足絕大多數(shù)使用場景,因為6位的短碼可以提供568億種的組合。
(3)將截取后得到的短碼和原始的長鏈建立一一映射關(guān)系,并存儲在數(shù)據(jù)庫或緩存中。
這樣,當用戶訪問短碼對應的短鏈時,系統(tǒng)就可以通過查詢映射關(guān)系找到原始的長鏈,并進行重定向。
該方案的優(yōu)點是實現(xiàn)簡單,但存在哈希碰撞的可能性,在碰撞時需要在原來的哈希值上增加隨機數(shù)再進行哈希。
自增序列
(1)目前有兩種主流的實現(xiàn)方式,通過雪花算法或數(shù)據(jù)庫自增ID來生成唯一數(shù)字ID。
前者則強依賴機器時鐘,如果機器上時鐘回撥,會導致ID重復。

后者的ID生成涉及到數(shù)據(jù)庫操作,性能相對不高,且引入數(shù)據(jù)庫會導致鏈路變長,增加出錯概率。
圖片
(2)以進制轉(zhuǎn)換的方式將數(shù)字ID進行縮短,如:十進制數(shù)字1234567890的六十二進制為1l3MoK。
(3)將截取得到的短碼和原始的長鏈建立一一映射關(guān)系,并存儲在數(shù)據(jù)庫或緩存中。
該方案的優(yōu)點是不存在長鏈哈希的碰撞問題,但如上文所述,也會根據(jù)實現(xiàn)方式不同引入其他問題。
隨機字符串
(1)可以通過取UUID前8位字符的方式生成短碼,將實現(xiàn)起來非常簡單。
import java.util.UUID;
public class GenerateUUID {
    public static void main(String[] args) {
        // 生成一個隨機的 UUID
        UUID uuid = UUID.randomUUID();
        // 輸出生成的 UUID
        String uuidString = uuid.toString();       
        // 截取前八位
        String firstEightChars = uuidString.substring(0, 8);
        // 輸出截取的前八位
        System.out.println("First eight characters: " + firstEightChars);
    }
}(2)以進制轉(zhuǎn)換的方式將數(shù)字ID進行縮短,如:十六進制數(shù)字5ca877b6的六十二進制為k3v6bO。
(3)將截取得到的短碼和原始的長鏈建立一一映射關(guān)系,并存儲在數(shù)據(jù)庫或緩存中。
該方案的優(yōu)點是實現(xiàn)簡單,通過JDK自帶的工具類即可實現(xiàn),連jar包都不需要引入,但會存在數(shù)字ID重復的問題。
預生成
該方案主要解決上述方案中,臨時生成哈希值存在哈希碰撞,或臨時生成數(shù)字ID存在重復的問題。
預先生成一批短碼并存儲在數(shù)據(jù)庫中,當需要生成短鏈時,直接將未使用的短碼與長鏈接進行關(guān)聯(lián)即可。
當然,該方案相比于其他三種方案,實現(xiàn)起來更復雜一些。
實現(xiàn)高并發(fā)的短鏈系統(tǒng)
以阿里、字節(jié)、拼多多等互聯(lián)網(wǎng)大廠的電商鏈接、短視頻分享為例,每天會產(chǎn)生幾億條短鏈,訪問短鏈的QPS會達到幾百萬。
如此高的并發(fā)量,對于短鏈系統(tǒng)承載力是個巨大的考驗。
高并發(fā)短鏈系統(tǒng)架構(gòu)設(shè)計如下:
圖片
(1)如上文所說,如果我們的短鏈服務(wù)處于高并發(fā)場景,優(yōu)先選擇HTTP 301永久重定向的方式減少對服務(wù)的請求數(shù),從而降低服務(wù)器壓力。
(2)雪花算法,每個服務(wù)器節(jié)點理論上可生成4096000個不重復的數(shù)字ID,然后再轉(zhuǎn)換為62進制的短鏈。
由于雪花算法不會出現(xiàn)重復數(shù)字ID的情況,可以在代碼中省去短鏈判重步驟,提升生成短鏈的吞吐量。
(3)采用Redis + Caffine雙緩存機制存儲熱點短鏈,由于短鏈不會出現(xiàn)變更的情況,所以不用考慮數(shù)據(jù)一致性的問題。
(4)每天產(chǎn)生幾億條短鏈,換算下來每秒鐘生成短鏈的峰值TPS可達到幾萬,采用分庫分表的方案來均攤寫操作帶來的性能瓶頸,Sharding Key為短鏈值。
分片算法:
數(shù)據(jù)庫ID = 短鏈碼哈希值 % 數(shù)據(jù)庫數(shù)量
數(shù)據(jù)表ID = 短鏈碼哈希值 / 數(shù)據(jù)庫數(shù)量 % 數(shù)據(jù)表數(shù)量
另外,短鏈映射表中的數(shù)據(jù)是冷熱分明的,可通過三個月或半年進行歸檔的方式降低數(shù)據(jù)庫的存儲壓力。















 
 
 















 
 
 
 