短网址系统
文章目錄
- 1. 短網(wǎng)址服務(wù)整體介紹
- 2. 如何通過(guò)哈希算法生成短網(wǎng)址?
- 2.1 如何讓短網(wǎng)址更短
- 2.2 如何解決哈希沖突?
- 2.3 如何優(yōu)化哈希算法生成短網(wǎng)址的性能?
- 3. 如何通過(guò)ID生成器生成短網(wǎng)址?
- 3.1 相同的原始網(wǎng)址可能會(huì)對(duì)應(yīng)不同的短網(wǎng)址
- 3.2 如何實(shí)現(xiàn)高性能的 ID 生成器?
- 4. 總結(jié)
在微博里發(fā)布一條帶網(wǎng)址的信息,微博會(huì)把里面的網(wǎng)址轉(zhuǎn)化成一個(gè)更短的網(wǎng)址。只要訪問(wèn)這個(gè)短網(wǎng)址,就相當(dāng)于訪問(wèn)原始的網(wǎng)址。
原始網(wǎng)址:https://github.com/wangzheng0822/ratelimiter4j 短網(wǎng)址:http://t.cn/EtR9QEG1. 短網(wǎng)址服務(wù)整體介紹
當(dāng)用戶點(diǎn)擊短網(wǎng)址時(shí),短網(wǎng)址服務(wù)會(huì)將瀏覽器重定向?yàn)樵季W(wǎng)址。這個(gè)過(guò)程是如何實(shí)現(xiàn)的呢?
從圖中可看出,瀏覽器會(huì)先訪問(wèn)短網(wǎng)址服務(wù),通過(guò)短網(wǎng)址獲取到原始網(wǎng)址,再通過(guò)原始網(wǎng)址訪問(wèn)到頁(yè)面。這部分功能今天不講。重點(diǎn)來(lái)看,如何將長(zhǎng)網(wǎng)址轉(zhuǎn)化成短網(wǎng)址?
2. 如何通過(guò)哈希算法生成短網(wǎng)址?
哈希算法可以將一個(gè)不管多長(zhǎng)的字符串,轉(zhuǎn)化成一個(gè)長(zhǎng)度固定的哈希值。
在生成短網(wǎng)址這個(gè)問(wèn)題上,我們不需要考慮反向解密的難度,只需關(guān)心哈希算法的計(jì)算速度和沖突概率。
比較著名并且應(yīng)用廣泛的一個(gè)哈希算法,那就是 MurmurHash 算法。盡管這個(gè)哈希算法在2008年才被發(fā)明出來(lái),但現(xiàn)在它已經(jīng)廣泛應(yīng)用到Redis、MemCache、Cassandra、HBase、Lucene等眾多著名的軟件中。
MurmurHash 算法提供了兩種長(zhǎng)度的哈希值,一種是32bits,一種是128bits。為了讓最終生成的短網(wǎng)址盡可能短,可以選擇32bits的哈希值。對(duì)于開(kāi)頭那個(gè)GitHub網(wǎng)址,經(jīng)過(guò)MurmurHash 計(jì)算后,得到的哈希值就是181338494。我們?cè)倨瓷隙叹W(wǎng)址服務(wù)的域名,就變成了最終的短網(wǎng)址 http://t.cn/181338494(其中,http://t.cn是短網(wǎng)址服務(wù)的域名)。
2.1 如何讓短網(wǎng)址更短
通過(guò)MurmurHash 算法得到的短網(wǎng)址還是很長(zhǎng),而且跟開(kāi)頭那個(gè)網(wǎng)址的格式好像也不一樣。
我們將10進(jìn)制的哈希值,轉(zhuǎn)化成更高進(jìn)制的哈希值,這樣哈希值就變短了。我們知道16進(jìn)制中,我們用A~E 來(lái)表示10~15。在網(wǎng)址URL中,常用的合法字符有0~9、a~z,A ~ Z 這樣62個(gè)字符。為了讓哈希值表示起來(lái)盡可能短,可以將10進(jìn)制的哈希值轉(zhuǎn)化成62進(jìn)制。計(jì)算過(guò)程如下。最終用62進(jìn)制表示的短網(wǎng)址就是http://t.cn/cgSqq。
2.2 如何解決哈希沖突?
盡管 MurmurHash 算法,沖突概率非常低。一旦沖突,會(huì)導(dǎo)致兩個(gè)原始網(wǎng)址被轉(zhuǎn)化成同一個(gè)短網(wǎng)址。當(dāng)用戶訪問(wèn)短網(wǎng)址時(shí),就無(wú)從判斷,想要訪問(wèn)的是哪一個(gè)。這個(gè)問(wèn)題該如何解決呢?
一般情況下,我們會(huì)保存短網(wǎng)址跟原始網(wǎng)址之間的對(duì)應(yīng)關(guān)系,以便后續(xù)用戶在訪問(wèn)短網(wǎng)址的時(shí)候,可以根據(jù)對(duì)應(yīng)關(guān)系,查找到原始網(wǎng)址。存儲(chǔ)這種對(duì)應(yīng)關(guān)系的方式有很多,比如自己設(shè)計(jì)存儲(chǔ)系統(tǒng)或者利用現(xiàn)成的數(shù)據(jù)庫(kù)。前面我們講到的數(shù)據(jù)庫(kù)有MySQL、Redis。就拿MySQL來(lái)舉例。假設(shè)短網(wǎng)址與原始網(wǎng)址之間的對(duì)應(yīng)關(guān)系,就存儲(chǔ)在MySQL 數(shù)據(jù)庫(kù)中。
當(dāng)有一個(gè)新的原始網(wǎng)址需要生成短網(wǎng)址的時(shí)候,先利用MurmurHash 算法,生成短網(wǎng)址。然后,拿這個(gè)新生成的短網(wǎng)址,在MySQL 數(shù)據(jù)庫(kù)中查找。
-
如果沒(méi)有找到相同的短網(wǎng)址,表明,這個(gè)新生成的短網(wǎng)址沒(méi)有沖突。于是我們就將這個(gè)短網(wǎng)址返回給用戶(請(qǐng)求生成短網(wǎng)址的用戶),然后將這個(gè)短網(wǎng)址與原始網(wǎng)址之間的對(duì)應(yīng)關(guān)系,存儲(chǔ)到MySQL數(shù)據(jù)庫(kù)中。
-
如果找到了相同的短網(wǎng)址,那也并不一定說(shuō)明就沖突了。我們從數(shù)據(jù)庫(kù)中,將這個(gè)短網(wǎng)址對(duì)應(yīng)的原始網(wǎng)址也取出來(lái)。
- 如果數(shù)據(jù)庫(kù)中記錄的原始網(wǎng)址,跟正在處理的原始網(wǎng)址一樣,說(shuō)明已經(jīng)有人請(qǐng)求過(guò)這個(gè)原始網(wǎng)址的短網(wǎng)址了。就可以拿這個(gè)短網(wǎng)址直接用。
- 如果數(shù)據(jù)庫(kù)中記錄的原始網(wǎng)址,跟正在處理的原始網(wǎng)址不一樣,說(shuō)明哈希算法發(fā)生了沖突。不同的原始網(wǎng)址,經(jīng)過(guò)計(jì)算,得到的短網(wǎng)址重復(fù)了。這個(gè)時(shí)候,怎么辦?
可以給原始網(wǎng)址拼接一串特殊字符,比如“[DUPLICATED]",然后再重新計(jì)算哈希值,兩次哈希計(jì)算都沖突的概率,顯然是非常低的。
假設(shè)出現(xiàn)非常極端的情況,又發(fā)生沖突了,我們可以再換一個(gè)拼接字符串,比如“[OHMYGOD]",再計(jì)算哈希值。然后把計(jì)算得到的哈希值,跟原始網(wǎng)址拼接了特殊字符串之后的文本,一并存儲(chǔ)在MySQL數(shù)據(jù)庫(kù)中。當(dāng)用戶訪問(wèn)短網(wǎng)址的時(shí)候,短網(wǎng)址服務(wù)先通過(guò)短網(wǎng)址,在數(shù)據(jù)庫(kù)中查找到對(duì)應(yīng)的原始網(wǎng)址。如果原始網(wǎng)址有拼接特殊字符(這個(gè)很容易通過(guò)字符串匹配算法找到),我們就先將特殊字符去掉,然后再將不包含特殊字符的原始網(wǎng)址返回給瀏覽器。
2.3 如何優(yōu)化哈希算法生成短網(wǎng)址的性能?
為了判斷生成的短網(wǎng)址是否沖突,需要拿生成的短網(wǎng)址,在數(shù)據(jù)庫(kù)中查找。如果數(shù)據(jù)庫(kù)數(shù)據(jù)非常多,查找會(huì)非常慢,影響短網(wǎng)址服務(wù)的性能。如何優(yōu)化?
- 可以給短網(wǎng)址字段添加B+樹(shù)索引。這樣通過(guò)短網(wǎng)址查詢?cè)季W(wǎng)址的速度就提高了。
- 在短網(wǎng)址生成的過(guò)程中,我們會(huì)跟數(shù)據(jù)庫(kù)打兩次交道,也就是會(huì)執(zhí)行兩條SQL語(yǔ)句。第一個(gè)SQL 語(yǔ)句是通過(guò)短網(wǎng)址查詢短網(wǎng)址與原始網(wǎng)址的對(duì)應(yīng)關(guān)系,第二個(gè)SQL語(yǔ)句是將新生成的短網(wǎng)址和原始網(wǎng)址之間的對(duì)應(yīng)關(guān)系存儲(chǔ)到數(shù)據(jù)庫(kù)。
我們知道,一般情況下,數(shù)據(jù)庫(kù)和應(yīng)用服務(wù)(只做計(jì)算不存儲(chǔ)數(shù)據(jù)的業(yè)務(wù)邏輯部分)會(huì)部署在兩個(gè)獨(dú)立的服務(wù)器或者虛擬服務(wù)器上。那兩條SQL 語(yǔ)句的執(zhí)行就需要兩次網(wǎng)絡(luò)通信。這種IO通信耗時(shí)以及SQL 語(yǔ)句的執(zhí)行,才是整個(gè)短網(wǎng)址服務(wù)的性能瓶頸所在。所以,為了提高性能,需要盡量減少SQL 語(yǔ)句。如何減少SQL 語(yǔ)句呢?
- 可以給數(shù)據(jù)庫(kù)中的短網(wǎng)址字段,添加一個(gè)唯一索引(不止是索引,還要求表中不能有重復(fù)的數(shù)據(jù))。當(dāng)有新的原始網(wǎng)址需要生成短網(wǎng)址的時(shí)候,我們并不會(huì)先拿生成的短網(wǎng)址,在數(shù)據(jù)庫(kù)中查找判重,而是直接將生成的短網(wǎng)址與對(duì)應(yīng)的原始網(wǎng)址,嘗試存儲(chǔ)到數(shù)據(jù)庫(kù)中。如果數(shù)據(jù)庫(kù)能夠?qū)?shù)據(jù)正常寫入,那說(shuō)明并沒(méi)有違反唯一索引,也就是說(shuō),這個(gè)新生成的短網(wǎng)址并沒(méi)有沖突。
- 如果數(shù)據(jù)庫(kù)反饋違反唯一性索引異常,那還得重新執(zhí)行剛剛講過(guò)的“查詢、寫入”過(guò)程,SQL語(yǔ)句執(zhí)行的次數(shù)不減反增。但是,在大部分情況下,我們把新生成的短網(wǎng)址和對(duì)應(yīng)的原始網(wǎng)址,插入到數(shù)據(jù)庫(kù)的時(shí)候,并不會(huì)出現(xiàn)沖突。所以,大部分情況下,只需要執(zhí)行一條寫入的SQL語(yǔ)句就可以了。所以,從整體上看,總的SQL語(yǔ)句執(zhí)行次數(shù)會(huì)大大減少。
我們還有另外一個(gè)優(yōu)化SQL語(yǔ)句次數(shù)的方法,那就是借助布隆過(guò)濾器。
-
把已經(jīng)生成的短網(wǎng)址,構(gòu)建成布隆過(guò)濾器。我們知道,布隆過(guò)濾器是比較節(jié)省內(nèi)存的一種存儲(chǔ)結(jié)構(gòu),長(zhǎng)度是10億的布隆過(guò)濾器,也只需要125MB左右的內(nèi)存。
-
當(dāng)有新的短網(wǎng)址生成的時(shí)候,先拿這個(gè)新生成的短網(wǎng)址,在布隆過(guò)濾器中查找。如果查找不存在,說(shuō)明這個(gè)新生成的短網(wǎng)址沒(méi)有沖突。再執(zhí)行寫入短網(wǎng)址和對(duì)應(yīng)原始網(wǎng)頁(yè)的SQL語(yǔ)句就可以了。通過(guò)先查詢布隆過(guò)濾器,總的SQL語(yǔ)句的執(zhí)行次數(shù)減少了。
3. 如何通過(guò)ID生成器生成短網(wǎng)址?
可以維護(hù)一個(gè)ID自增生成器。它可以生成1、2、3…這樣自增的整數(shù)ID。當(dāng)短網(wǎng)址服務(wù)接收到一個(gè)原始網(wǎng)址轉(zhuǎn)化成短網(wǎng)址的請(qǐng)求之后,它先從ID生成器中取一個(gè)號(hào)碼,然后將其轉(zhuǎn)化成62進(jìn)制表示法,拼接到短網(wǎng)址服務(wù)的域名(比如http://t.cn/)后面,就形成了最終的短網(wǎng)址。最后,我們還是會(huì)把生成的短網(wǎng)址和對(duì)應(yīng)的原始網(wǎng)址存儲(chǔ)到數(shù)據(jù)庫(kù)中。
理論非常簡(jiǎn)單好理解。有幾個(gè)細(xì)節(jié)需要處理。
3.1 相同的原始網(wǎng)址可能會(huì)對(duì)應(yīng)不同的短網(wǎng)址
每次新來(lái)一個(gè)原始網(wǎng)址,就生成一個(gè)新的短網(wǎng)址,會(huì)導(dǎo)致兩個(gè)相同的原始網(wǎng)址生成了不同的短網(wǎng)址。如何處理呢?
-
第一種思路是不做處理。相同的原始網(wǎng)址對(duì)應(yīng)不同的短網(wǎng)址,用戶是可以接受的。用戶只關(guān)心短網(wǎng)址能否正確地跳轉(zhuǎn)到原始網(wǎng)址。短網(wǎng)址長(zhǎng)什么樣,他根本就不關(guān)心。
-
第二種思路是借助哈希算法生成短網(wǎng)址的處理思想,當(dāng)要給一個(gè)原始網(wǎng)址生成短網(wǎng)址的時(shí)候,要先拿原始網(wǎng)址在數(shù)據(jù)庫(kù)中查找,看數(shù)據(jù)庫(kù)中是否已經(jīng)存在相同的原始網(wǎng)址了。如果數(shù)據(jù)庫(kù)中存在,那我們就取出對(duì)應(yīng)的短網(wǎng)址,直接返回給用戶。
不過(guò),這種處理思路有個(gè)問(wèn)題,我們需要給數(shù)據(jù)庫(kù)中的短網(wǎng)址和原始網(wǎng)址這兩個(gè)字段,都添加索引。短網(wǎng)址上加索引是為了提高用戶查詢短網(wǎng)址對(duì)應(yīng)的原始網(wǎng)頁(yè)的速度,原始網(wǎng)址上加索引是為了加快剛剛講的通過(guò)原始網(wǎng)址查詢短網(wǎng)址的速度。這種解決思路雖然能滿足“相同原始網(wǎng)址對(duì)應(yīng)相同短網(wǎng)址”這樣一個(gè)需求,但是是有代價(jià)的:一方面兩個(gè)索引會(huì)占用更多的存儲(chǔ)空間,另一方面索引還會(huì)導(dǎo)致插入、刪除等操作性能的下降。
3.2 如何實(shí)現(xiàn)高性能的 ID 生成器?
實(shí)現(xiàn)ID生成器的方法有很多,比如利用數(shù)據(jù)庫(kù)自增字段。當(dāng)然我們也可以自己維護(hù)一個(gè)計(jì)數(shù)器,不停地加一加一。但是,一個(gè)計(jì)數(shù)器來(lái)應(yīng)對(duì)頻繁的短網(wǎng)址生成請(qǐng)求,顯然是有點(diǎn)吃力的(因?yàn)橛?jì)數(shù)器必須保證生成的ID不重復(fù),籠統(tǒng)概念上講,就是需要加鎖)。如何提高ID生成器的性能呢?
- 第一種思路,可以給ID生成器裝多個(gè)前置發(fā)號(hào)器。我們批量地給每個(gè)前置發(fā)號(hào)器發(fā)送ID號(hào)碼。當(dāng)我們接受到短網(wǎng)址生成請(qǐng)求的時(shí)候,就選擇一個(gè)前置發(fā)號(hào)器來(lái)取號(hào)碼。這樣通過(guò)多個(gè)前置發(fā)號(hào)器,明顯提高了并發(fā)發(fā)號(hào)的能力。
- 第二種思路跟第一種差不多。不再使用一個(gè)ID生成器和多個(gè)前置發(fā)號(hào)器這樣的架構(gòu),直接實(shí)現(xiàn)多個(gè)ID生成器同時(shí)服務(wù)。為了保證每個(gè)ID生成器生成的ID不重復(fù)。我們要求每個(gè)ID生成器按照一定的規(guī)則,來(lái)生成ID號(hào)碼。比如,第一個(gè)ID生成器只能生成尾號(hào)為0的,第二個(gè)只能生成尾號(hào)為1的,以此類推。通過(guò)多個(gè)ID生成器同時(shí)工作,也提高了ID生成的效率。
4. 總結(jié)
短網(wǎng)址服務(wù)的兩種實(shí)現(xiàn)方法。
通過(guò)哈希算法生成短網(wǎng)址。采用計(jì)算速度快、沖突概率小的MurmurHash算法,并將計(jì)算得到的10進(jìn)制數(shù),轉(zhuǎn)化成62進(jìn)制表示法,進(jìn)一步縮短短網(wǎng)址的長(zhǎng)度。對(duì)于哈希算法的哈希沖突問(wèn)題,通過(guò)給原始網(wǎng)址添加特殊前綴字符,重新計(jì)算哈希值的方法來(lái)解決。
通過(guò)ID生成器生成短網(wǎng)址。維護(hù)一個(gè)ID自增的ID生成器,給每個(gè)原始網(wǎng)址分配一個(gè)ID號(hào)碼,并且同樣轉(zhuǎn)成62進(jìn)制表示法,拼接到短網(wǎng)址服務(wù)的域名之后,形成最終的短網(wǎng)址。
總結(jié)
- 上一篇: 公需科目必须学吗_专业技术人员一般公需科
- 下一篇: android操作系统+流量,为什么我的