為什麼有的網站連結這麼短?

👉 這是一個或許對你有用的社群
🐱 一對一交流/面試小冊/簡歷最佳化/求職解惑,歡迎加入芋道快速開發平臺知識星球。下面是星球提供的部分資料:
👉這是一個或許對你有用的開源專案
國產 Star 破 10w+ 的開源專案,前端包括管理後臺 + 微信小程式,後端支援單體和微服務架構。
功能涵蓋 RBAC 許可權、SaaS 多租戶、資料許可權、商城、支付、工作流、大屏報表、微信公眾號、ERPCRMAI 大模型等等功能:
  • Boot 多模組架構:https://gitee.com/zhijiantianya/ruoyi-vue-pro
  • Cloud 微服務架構:https://gitee.com/zhijiantianya/yudao-cloud
  • 影片教程:https://doc.iocoder.cn
【國內首批】支援 JDK 17/21 + SpringBoot 3.3、JDK 8/11 + Spring Boot 2.7 雙版本 

引言

今天下午,同事在廁所裡排隊等坑的時候(人多坑少)。想象一下一個場景,我正在一邊排隊,一邊拿著手機撩妹。前面一個同事,拿著手機簡訊轉過頭來和我聊天。
於是,我們就開始討論下面這種短連結的實現原理(沒錯,上廁所也不忘學習!)。
點選其中短連結後,我們會跳到如下地址http://h5.dangdang.com/mix_20191015_or4x本文,我們來討論一下其實現原理!
基於 Spring Boot + MyBatis Plus + Vue & Element 實現的後臺管理系統 + 使用者小程式,支援 RBAC 動態許可權、多租戶、資料許可權、工作流、三方登入、支付、簡訊、商城等功能
  • 專案地址:https://github.com/YunaiV/ruoyi-vue-pro
  • 影片教程:https://doc.iocoder.cn/video/

正文

需求緣起

這裡說一下,為什麼需要短連結?這個簡單,比如大家發微博有字數限制
如果 URL 地址過長,顯然可以寫的關鍵字就越少!
再比如發簡訊如果簡訊內容過長,那麼一條簡訊就要拆成兩條發,浪費錢!
因此採用短連結,不僅節約資源,還十分美觀!

請求流程

首先,我們先看看噹噹的短連結http://dwz.win/nXR
它是由兩個部分組成
  1. http://dwz.win:短連結系統的域名地址
  2. nXR:請求引數
請求http://dwz.win/nXR地址後,返回狀態如下所示
於是,我們可以推斷出,敲下http://dwz.win/nXR地址後,發生了什麼呢?
這裡渣渣煙就要多嘴一句了。上圖所示短連結系統,返回的狀態可以為 301 或者 302,只是噹噹網用的是 301。
這裡我要說一下,大家應該明白30X狀態,在 HTTP 協議中,代表的是重定向的狀態。那麼301和302區別在哪呢,繼續往下看。
301 代表什麼?
301 代表的是永久重定向。什麼意思呢? 對於 GET 請求, 301 跳轉會預設被瀏覽器 cache。也就是說,使用者第一次訪問某個短連結後,如果伺服器返回 301 狀態碼,則這個使用者在後續多次訪問同一短連結地址,瀏覽器會直接請求跳轉地址,而不會再去短連結系統上取!
這麼做優點很明顯,降低了伺服器壓力,但是無法統計到短連結地址的點選次數。
302 代表什麼?
302 代表的是臨時定向。什麼意思呢? 對於 GET 請求, 302 跳轉預設不會被瀏覽器快取,除非在 HTTP 響應中透過 Cache-Control 或 Expires 暗示瀏覽器快取。因此,使用者每次訪問同一短連結地址,瀏覽器都會去短連結系統上取。
這麼做的優點是,能夠統計到短地址被點選的次數了。但是伺服器的壓力變大了。
下面說最關鍵的一段,怎麼將http://h5.dangdang.com/mix_20191015_or4x壓縮為nXR字元

演算法原理

首先呢,我們需要一張表來儲存,長短連結間的對映關係。表結構如下
列名 說明
id BIGINT,自增主鍵
url 長地址,也就是需要跳轉的原地址
好的,假設我們此時表裡的資料如下
id url
1 http://h5.dangdang.com/mix_20191015_or4x
2 http://h5.dangdang.com/mix_20191102_ad3x
我們此時拿自增 id 作為短連結的 key。假設域名http://dwz.win是短連結系統,也就是說請求:
  • (1)http://dwz.win/1會跳轉http://h5.dangdang.com/mix_20191015_or4x;
  • (2)http://dwz.win/2會跳轉http://h5.dangdang.com/mix_20191102_ad3x;
這麼做,也不是不行,有兩個缺點你要評估能不能接受!
  • (1)如果資料比較大,比如幾百億,你的 url 地址依然過長
  • (2)你的資料具有規律性,別人用一個簡單的指令碼就可以遍歷出你的跳轉地址!
為了解決上面的兩個缺點,我們增加一個列,用來儲存 key 值。此時表結構如下
列名 說明
id BIGINT,自增主鍵
key 短串,需要加唯一索引
url 長地址,也就是需要跳轉的原地址
我們為了縮短 id 的長度呢,一般可以這麼做。由於我們的短連結是由 a-z、A-Z 和 0-9 共 62 個字元可以選擇。因此,我們可以將十進位制的數字 id,轉換為一個 62 進位制的數,例如 201314 就可以轉換為 Qn0。演算法如下
privatestaticfinal

 String BASE = 

"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

;

publicstatic String toBase62(long num)

{

    StringBuilder sb = 

new

 StringBuilder();

do

 {

int

 i = (

int

) (num % 

62

);

        sb.append(BASE.charAt(i));

        num /= 

62

;

    } 

while

 (num > 

0

);

return

 sb.reverse().toString();

}

另外,我們需要引入一個全域性發號器,一直返回全域性自增的 ID。相當於,我們的短連結系統先去請求這個全域性自增 ID,然後將全域性自增 ID 轉換為 62 進位制的數,作為 key。
接下來,解決第二個問題!資料具有規律性的問題。畢竟你轉換為 62 進位制後,只是解決了資料過長的問題,資料規律性問題還是沒解決。因此,我們需要引入一個隨機演算法。
那麼此時,你的考慮點在於,你是否要根據 key 值,反推出全域性 id 值!來抉擇不同的隨機演算法!
(1)不希望反推出全域性 ID
OK,那就用一個洗牌演算法,打亂算出的值。比如十進位制的 201314 就可以轉換為 Qn0。然後再使用洗牌演算法,可以返回 n0Q、Q0n….其中之一。但是會有一定機率衝突,多洗幾次就行。
(2)希望反推出全域性 ID
OK,那就在得到 Qn0 這個數字後,將其轉換為二進位制數。然後在固定位,第五位,第十位…(等等)插入一個隨機值即可。至於如何反推也很簡單,你拿到短連結 key 後,將固定位的數字去除,再轉換為十進位制即可。
講到這裡,就基本將 key 如何生成的邏輯講清楚了。那麼使用者在點選短連結的時候,例如地址http://dwz.win/nXR,短連結系統解析出 key 為 nXR,根據唯一索引去表中將 nXR 對應的 url 返回即可。

細節最佳化

(1)分庫分表
如果這個系統是放在公網,給大家使用的。建議上來就分庫分表,資料量過 1000 萬是很容易的。這裡涉及到一個問題,拿全域性發號器給的自增 id 做分片健,還是拿轉換後的 key 做分片鍵。
顯然,用轉換後的 key 做分片鍵會更容易一些。如果用 ID 做為分片鍵,存在兩個問題!
  1. 使用者請求的 key,需要做一個逆運算推算回 ID,然後根據 ID,再去對應表裡去找,增加響應時間。
  2. 根據選擇的隨機演算法不同,key 不一定能夠推算回 ID 值。這種情況下,只能每張表去查,更慢。
所以用 key 做分片鍵,再適合不過了。拿到使用者請求的 KEY 後,直接定位到對應的表裡將 url 取出即可。
(2)讀寫分離
這種系統顯然,讀遠大於寫。建議可以考慮做讀寫分離。
(3)引入快取
假設,我們在一個時間,給手機推送簡訊連結的簡訊後。顯然,後面的一段時間內,對該短連結的請求量會大大提升。沒有必要每次都去資料庫查詢,因此可以引入 redis 快取。
(4)全域性發號器
用其他演算法行不行 ?可以。這裡只是要一個全域性唯一 ID 而已。自己要估算好,使用其他演算法所帶來的效能影響。以及採用其他演算法,會不會造成生成的生成的 ID 過於規律。
(5)防攻擊
做好被惡意攻擊的準備,防止自增 ID 的值,被全部耗光

歡迎加入我的知識星球,全面提升技術能力。
👉 加入方式,長按”或“掃描”下方二維碼噢
星球的內容包括:專案實戰、面試招聘、原始碼解析、學習路線。
文章有幫助的話,在看,轉發吧。
謝謝支援喲 (*^__^*)

相關文章