本文將簡單介紹,如何去完成一個縮短網址的功能。javascript
Node.js + MySQL + Redis版本的源碼地址:githubhtml
Demo地址:www.ecool.fun/shortLink前端
文章閱讀大概須要8分鐘。java
短連接,通俗來講,就是將長的URL網址,經過程序計算等方式,轉換爲簡短的網址字符串。git
你們常常能夠從微博或者各種營銷短信中,看到短連接,形式通常相似於 t.cn/xxxxxx
,點擊後,就能跳轉到對應的頁面。github
早期短連接普遍應用於圖片上傳網站,經過縮短網址URL連接字數,達到減小代碼字符串的目的。更便於使用者引用網址,寫入代碼中,節省字符數空間。常見於網店圖片分類的使用,因有字符限制,運用短連接,達到外鏈圖片的目的,自微博盛行以來,在微博字數有限的特點下,短連接也盛行於微博網站,以節省字數,給博主發佈更多文字的空間。算法
將長連接轉成短連接,通常是爲了方便記憶或者傳播。數據庫
從上面的介紹中,咱們得出,縮短網址須要完成如下兩個功能點:segmentfault
其實上述功能點的原理很簡單,簡單描述一下:緩存
知識點:爲何要使用302跳轉,而不是301跳轉呢?
301是永久重定向,302是臨時重定向。短地址一經生成就不會變化,因此用301是符合http語義的。可是若是用了301, Google,百度等搜索引擎,搜索的時候會直接展現真實地址,那咱們就沒法統計到短地址被點擊的次數了,也沒法收集用戶的Cookie, User Agent 等信息,這些信息能夠用來作不少有意思的大數據分析,也是短網址服務商的主要盈利來源。
引自知乎-武林的回答,原文連接
整個流程的設計以下圖所示:
能夠看到,我用到了MySQL
和Redis
來存儲長網址和短碼之間的映射關係。
用MySQL
想必你們都能理解,可是爲何要用 Redis
呢,直接用數據庫不就行了嗎?
這個主要是考慮到生成短連接,在投放以後的訪問量會比較大,使用 Redis
緩存後,能有效下降數據庫的壓力。
經過上面的全流程設計,會發現主要的問題就是如何經過長網址,去生成對應的短碼。
短碼通常是由 [a - z, A - Z, 0 - 9]
這62 個字母或數字組成,短碼的長度也能夠自定義,但通常不超過8位。比較經常使用的都是6位,6位的短碼已經能有568億種的組合:(26+26+10)^6 = 56800235584,已知足絕大多數的使用場景。
目前比較流行的生成短碼方法有:自增id
、摘要算法
、普通隨機數
。
該方法是一種無碰撞的方法,原理是,每新增一個短碼,就在上次添加的短碼id基礎上加1,而後將這個10進制的id值,轉化成一個62進制的字符串。
通常利用數據表中的自增id來完成:每次先查詢數據表中的自增id最大值max,那麼須要插入的長網址對應自增id值就是 max+1,將max+1轉成62進制便可獲得短碼。
可是短碼 id 是從一位長度開始遞增,短碼的長度不固定,不過能夠用 id 從指定的數字開始遞增的方式來處理,確保全部的短碼長度都一致。同時,生成的短碼是有序的,可能會有安全的問題,能夠將生成的短碼id,結合長網址等其餘關鍵字,進行md5運算生成最後的短碼。
10進制轉成62進制的具體實現:
function string10to62(number) {
const chars = '0123456789abcdefghigklmnopqrstuvwxyzABCDEFGHIGKLMNOPQRSTUVWXYZ';
const charsArr = chars.split('');
const radix = chars.length;
let qutient = +number;
let arr = [];
do{
let mod = qutient % radix;
qutient = (qutient - mod) / radix;
arr.unshift(charsArr[mod]);
}while(qutient);
return arr.join('');
}
複製代碼
摘要算法又稱哈希算法,它表示輸入任意長度的數據,輸出固定長度的數據。相同的輸入數據始終獲得相同的輸出,不一樣的輸入數據儘可能獲得不一樣的輸出。
算法思路:
一、將長網址經過 md5
運算,生成 32 字符的 hex string,分爲 4 段,每段 8 個字符;
二、對這四段循環處理,取 8 個字節,將其當作 16 進制串,並與 0x3fffffff(30位1) 與操做,即超過 30 位的忽略處理;
三、這 30 位分紅 6 段,每 5 位的數字做爲字母表的索引取得特定字符,依次進行得到 6 位字符串。
四、總的 md5
串能夠得到 4 個 6 位串,取裏面的任意一個就可做爲這個長網址的短連接 url 地址。
雖然概率很小,可是該方法依然存在碰撞的可能性,解決衝突會比較麻煩。不過該方法生成的短碼位數,是固定的,也不存在連續生成的短碼有序的狀況。
該方法是從62個字符串中隨機取出一個6位短碼的組合,而後去數據庫中查詢該短碼是否已存在。若是已存在,就繼續循環該方法從新獲取短碼,不然就直接返回。
該方法是最簡單的一種實現,不過因爲Math.round()
方法生成的隨機數屬於僞隨機數,碰撞的可能性也不小。在數據比較多的狀況下,可能會循環不少次,才能生成一個不衝突的短碼。
具體實現:
// 獲取惟一的Link
async getShortLink() {
const shortLink = this.generateShortLink();
// 查詢數據庫中是否存在該連接,若是存在,就直接返回
const searchResult = await this.searchByLinkInMySQL(shortLink);
if (searchResult && searchResult.length > 0) {
// 若是shortLink已經存在,就遍歷從新生成
return this.getShortLink();
}
return shortLink;
}
// 生成隨機的Link
generateShortLink() {
let str = '';
const arr = [
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
];
for (let i = 0; i < 6; i++) {
const pos = Math.round(Math.random() * (arr.length - 1));
str += arr[pos];
}
return str;
}
複製代碼
綜上,比較推薦使用第一種方法來實現短碼的生成。
最後,歡迎你們star咱們的人人貸大前端團隊博客,全部的文章還會同步更新到知乎專欄 和 掘金帳號,咱們每週都會分享幾篇高質量的大前端技術文章。