基于Java設(shè)計(jì)一個(gè)短鏈接生成系統(tǒng)
引言
相信大家在生活中,特別是最近的雙十一活動(dòng)期間,會(huì)收到很多短信,而那些短信都有兩個(gè)特征,第一個(gè)是幾乎都是垃圾短信,這個(gè)特點(diǎn)此處可以忽略不計(jì),第二個(gè)特點(diǎn)是鏈接很短,比如下面這個(gè):
我們知道,短信有些是有字?jǐn)?shù)限制的,直接放一個(gè)帶滿各種參數(shù)的鏈接,不合適,另外一點(diǎn)是,不想暴露參數(shù)。好處無(wú)非以下:
- 太長(zhǎng)的鏈接容易被限制長(zhǎng)度
- 短鏈接看著簡(jiǎn)潔,長(zhǎng)鏈接看著容易懵
- 安全,不想暴露參數(shù)
- 可以統(tǒng)一鏈接轉(zhuǎn)換,當(dāng)然也可以實(shí)現(xiàn)統(tǒng)計(jì)點(diǎn)擊次數(shù)等操作
那背后的原理是什么呢?怎么實(shí)現(xiàn)的?讓你實(shí)現(xiàn)這樣的系統(tǒng),你會(huì)怎么設(shè)計(jì)呢?【來(lái)自于某鵝場(chǎng)面試官】
短鏈接的原理
短鏈接展示的邏輯
這里最重要的知識(shí)點(diǎn)是重定向,先復(fù)習(xí)一下http的狀態(tài)碼:
分類 | 含義 |
---|---|
1** | 服務(wù)器收到請(qǐng)求,需要請(qǐng)求者繼續(xù)執(zhí)行操作 |
2** | 成功,操作被成功接收并處理 |
3** | 重定向,需要進(jìn)一步的操作以完成請(qǐng)求 |
4** | 客戶端錯(cuò)誤,請(qǐng)求包含語(yǔ)法錯(cuò)誤或無(wú)法完成請(qǐng)求 |
5** | 服務(wù)器錯(cuò)誤,服務(wù)器在處理請(qǐng)求的過(guò)程中發(fā)生了錯(cuò)誤 |
那么以 3 開(kāi)頭的狀態(tài)碼都是關(guān)于重定向的:
- 300:多種選擇,可以在多個(gè)位置存在
- 301:永久重定向,瀏覽器會(huì)緩存,自動(dòng)重定向到新的地址
- 302:臨時(shí)重定向,客戶端還是會(huì)繼續(xù)使用舊的URL
- 303:查看其他的地址,類似于301
- 304:未修改。所請(qǐng)求的資源未修改,服務(wù)器返回此狀態(tài)碼時(shí),不會(huì)返回任何資源。
- 305:需要使用代理才能訪問(wèn)到資源
- 306:廢棄的狀態(tài)碼
- 307:臨時(shí)重定向,使用Get請(qǐng)求重定向
整個(gè)跳轉(zhuǎn)的流程:
1.用戶訪問(wèn)短鏈接,請(qǐng)求到達(dá)服務(wù)器
2.服務(wù)器將短鏈接裝換成為長(zhǎng)鏈接,然后給瀏覽器返回重定向的狀態(tài)碼301/302
301永久重定向會(huì)導(dǎo)致瀏覽器緩存重定向地址,短鏈接系統(tǒng)統(tǒng)計(jì)訪問(wèn)次數(shù)會(huì)不正確
302臨時(shí)重定向可以解決次數(shù)不準(zhǔn)的問(wèn)題,但是每次都會(huì)到短鏈接系統(tǒng)轉(zhuǎn)換,服務(wù)器壓力會(huì)變大。
3.瀏覽器拿到重定向的狀態(tài)碼,以及真正需要訪問(wèn)的地址,重定向到真正的長(zhǎng)鏈接上。
從下圖可以看出,確實(shí)鏈接被302重定向到新的地址上去,返回的頭里面有一個(gè)字段Location就是所要重定向的地址:
短鏈接怎么設(shè)計(jì)的
全局發(fā)號(hào)器
肯定我們第一點(diǎn)想到的是壓縮,像文件壓縮那樣,壓縮之后再解壓還原到原來(lái)的鏈接,重定向到原來(lái)的鏈接,但是很不幸的是,這個(gè)是行不通的,你有見(jiàn)過(guò)什么壓縮方式能把這么長(zhǎng)的數(shù)字直接壓縮到這么短么?事實(shí)上不可能。就像是Huffman樹(shù),也只能對(duì)那種重復(fù)字符較多的字符串壓縮時(shí)效率較高,像鏈接這種,可能帶很多參數(shù),而且各種不規(guī)則的情況都有,直接壓縮算法不現(xiàn)實(shí)。
那https://dx.10086.cn/tzHLFw與https://gd.10086.cn/gmccapp/webpage/payPhonemoney/index.html?channel=之間的裝換是怎么樣的呢?前面路徑不變,變化的是后面,也就是tzHLFw與gmccapp/webpage/payPhonemoney/index.html?channel=之間的轉(zhuǎn)換。
實(shí)際也很簡(jiǎn)單,就是數(shù)據(jù)庫(kù)里面的一條數(shù)據(jù),一個(gè)id對(duì)應(yīng)長(zhǎng)鏈接(相當(dāng)于全局的發(fā)號(hào)器,全局唯一的ID):
id | url |
---|---|
1 | gd.10086.cn/gmccapp/web… |
這里用到的,也就是我們之前說(shuō)過(guò)的分布式全局唯一ID,如果我們直接用id作為參數(shù),貌似也可以:https://dx.10086.cn/1,訪問(wèn)這個(gè)鏈接時(shí),去數(shù)據(jù)庫(kù)查詢獲得真正的url,再重定向。
單機(jī)的唯一ID很簡(jiǎn)單,用原子類AtomicLong就可以,但是分布式的就不行了,簡(jiǎn)單點(diǎn)可以用 redis,或者數(shù)據(jù)庫(kù)自增,或者可以考慮Zookeeper之類的。
id 轉(zhuǎn)換策略
但是直接用遞增的數(shù)字,有兩個(gè)壞處:
- 數(shù)字很大的時(shí)候,還是很長(zhǎng)
- 遞增的數(shù)字,不安全,規(guī)律性太強(qiáng)了
明顯我們平時(shí)看到的鏈接也不是數(shù)字的,一般都是大小寫(xiě)字母加上數(shù)字。為了縮短鏈接的長(zhǎng)度,我們必須把id轉(zhuǎn)換掉,比如我們的短鏈接由a-z,A-Z,0-9組成,相當(dāng)于62進(jìn)制的數(shù)字,將id轉(zhuǎn)換成為62進(jìn)制的數(shù)字:
public class ShortUrl { private static final String BASE = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; public static String toBase62(long num) { StringBuilder result = new StringBuilder(); do { int i = (int) (num % 62); result.append(BASE.charAt(i)); num /= 62; } while (num > 0); return result.reverse().toString(); } public static long toBase10(String str) { long result = 0; for (int i = 0; i < str.length(); i++) { result = result * 62 + BASE.indexOf(str.charAt(i)); } return result; } public static void main(String[] args) { // tzHLFw System.out.println(toBase10("tzHLFw")); System.out.println(toBase62(27095455234L)); } }
id轉(zhuǎn) 62位的key 或者key裝換成為id都已經(jīng)實(shí)現(xiàn)了,不過(guò)計(jì)算還是比較耗時(shí)的,不如加個(gè)字段存起來(lái),于是數(shù)據(jù)庫(kù)變成了:
id | key | url |
---|---|---|
27095455234 | tzHLFw | gd.10086.cn/gmccapp/web… |
但是這樣還是很容易被猜出這個(gè)id和key的對(duì)應(yīng)關(guān)系,要是被遍歷訪問(wèn),那還是很不安全的,如果擔(dān)心,可以隨機(jī)將短鏈接的字符順序打亂,或者在適當(dāng)?shù)奈恢眉由弦恍╇S機(jī)生成的字符,比如第1,4,5 位是隨機(jī)字符,其他位置不變,只要我們計(jì)算的時(shí)候,將它對(duì)應(yīng)的關(guān)系存到數(shù)據(jù)庫(kù),我們就可以通過(guò)連接的key找到對(duì)應(yīng)的url。(值得注意的是,key必須是全局唯一的,如果沖突,必須重新生成)
一般短鏈接都有過(guò)期時(shí)間,那么我們也必須在數(shù)據(jù)庫(kù)里面加上對(duì)應(yīng)的字段,訪問(wèn)的時(shí)候,先判斷是否過(guò)期,過(guò)期則不給予重定向。
性能考慮
如果有很多短鏈接暴露出去了,數(shù)據(jù)庫(kù)里面數(shù)據(jù)很多,這個(gè)時(shí)候可以考慮使用緩存優(yōu)化,生成的時(shí)候順便把緩存寫(xiě)入,然后讀取的時(shí)候,走緩存即可,因?yàn)橐话愣替溄雍烷L(zhǎng)鏈接的關(guān)系不會(huì)修改,即使修改,也是很低頻的事情。
如果系統(tǒng)的id用完了怎么辦?這種概率很小,如果真的發(fā)生,可以重用舊的已經(jīng)失效的id號(hào)。
如果被人瘋狂請(qǐng)求一些不存在的短鏈接怎么辦?其實(shí)這就是緩存穿透,緩存穿透是指,緩存和數(shù)據(jù)庫(kù)都沒(méi)有的數(shù)據(jù),被大量請(qǐng)求,比如訂單號(hào)不可能為-1,但是用戶請(qǐng)求了大量訂單號(hào)為-1的數(shù)據(jù),由于數(shù)據(jù)不存在,緩存就也不會(huì)存在該數(shù)據(jù),所有的請(qǐng)求都會(huì)直接穿透到數(shù)據(jù)庫(kù)。如果被惡意用戶利用,瘋狂請(qǐng)求不存在的數(shù)據(jù),就會(huì)導(dǎo)致數(shù)據(jù)庫(kù)壓力過(guò)大,甚至垮掉。
針對(duì)這種情況,一般可以用布隆過(guò)濾器過(guò)濾掉不存在的數(shù)據(jù)請(qǐng)求,但是我們這里id本來(lái)就是遞增且有序的,其實(shí)我們范圍大致都是已知的,更加容易判斷,超出的肯定不存在,或者請(qǐng)求到的時(shí)候,緩存里面放一個(gè)空對(duì)象也是沒(méi)有問(wèn)題的。
到此這篇關(guān)于基于Java設(shè)計(jì)一個(gè)短鏈接生成系統(tǒng)的文章就介紹到這了,更多相關(guān)Java短鏈接生成系統(tǒng)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
javacv開(kāi)發(fā)詳解之調(diào)用本機(jī)攝像頭視頻
這篇文章主要介紹了javacv開(kāi)發(fā)詳解之調(diào)用本機(jī)攝像頭視頻,對(duì)javacv感興趣的同學(xué),可以參考下2021-04-04Java實(shí)現(xiàn)File轉(zhuǎn)換MultipartFile格式的例子
本文主要介紹了Java實(shí)現(xiàn)File轉(zhuǎn)換MultipartFile格式的例子,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-07-07帶你了解Java數(shù)據(jù)結(jié)構(gòu)和算法之鏈表
這篇文章主要為大家介紹了Java數(shù)據(jù)結(jié)構(gòu)和算法之鏈表 ,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來(lái)幫助2022-01-01SpringMvc切換Json轉(zhuǎn)換工具的操作代碼
SpringBoot切換使用goolge的Gson作為SpringMvc的Json轉(zhuǎn)換工具,本文給大家講解SpringMvc切換Json轉(zhuǎn)換工具的操作代碼,感興趣的朋友一起看看吧2024-02-02Java代碼規(guī)范與質(zhì)量檢測(cè)插件SonarLint的使用
本文主要介紹了Java代碼規(guī)范與質(zhì)量檢測(cè)插件SonarLint的使用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-08-08java中LinkedList使用迭代器優(yōu)化移除批量元素原理
本文主要介紹了java中LinkedList使用迭代器優(yōu)化移除批量元素原理,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-10-10springboot-2.3.x最新版源碼閱讀環(huán)境搭建(基于gradle構(gòu)建)
這篇文章主要介紹了springboot-2.3.x最新版源碼閱讀環(huán)境搭建(基于gradle構(gòu)建),需要的朋友可以參考下2020-08-08