專業(yè)的短鏈接生成工具
鏈接域名
短網(wǎng)址有效期
如何創(chuàng)建一個(gè)短鏈接系統(tǒng)?
更新時(shí)間:2025-4-22 10:18:26 作者:愛短鏈
短鏈接,通俗的講,就是通過(guò)程序計(jì)算等方法,將一個(gè)長(zhǎng)URL轉(zhuǎn)換成一個(gè)短URL字符串。
人們經(jīng)常會(huì)收到一些莫名其妙的營(yíng)銷短信,其中包含一個(gè)非常短的鏈接供您跳轉(zhuǎn)。新浪微博因?yàn)樽謹(jǐn)?shù)限制,經(jīng)常會(huì)看到一些不像網(wǎng)址的網(wǎng)址。短鏈的興起應(yīng)該是微博字?jǐn)?shù)的限制激發(fā)了大家的創(chuàng)造力。
如果創(chuàng)建一個(gè)短鏈接系統(tǒng),我們應(yīng)該怎么做?
把長(zhǎng)鏈接變成短鏈接;訪問(wèn)短鏈接的用戶會(huì)跳轉(zhuǎn)到正確的長(zhǎng)鏈接。
找到對(duì)應(yīng)的長(zhǎng)URL,跳轉(zhuǎn)到對(duì)應(yīng)的頁(yè)面。
短鏈生成方法
短代碼一般由62個(gè)字母或數(shù)字組成[a - z, A - Z, 0 - 9]。短碼的長(zhǎng)度也可以定制,但一般不超過(guò)8位。最常用的是6位創(chuàng)建一個(gè)短鏈系統(tǒng),我們應(yīng)該做什么呢?(圖) ,6位短碼可以有568億個(gè)組合:(26+26+10)^6 = 56800235584,已經(jīng)滿足了大部分使用場(chǎng)景。
目前比較流行的短碼生成方法有:自增id、摘要算法、普通隨機(jī)數(shù)。
自動(dòng)遞增 ID
該方法是一種無(wú)碰撞方法。原理是每次添加一個(gè)短碼,最后一次添加的短碼的id加1,然后將十進(jìn)制的id值轉(zhuǎn)換成62位的id值。細(xì)繩。
一般使用數(shù)據(jù)表中的自增id來(lái)完成:每次查詢數(shù)據(jù)表中自增id的最大值時(shí),對(duì)應(yīng)插入的長(zhǎng)URL的自增id值為max +1, max+1 轉(zhuǎn)換成 62 的短碼短鏈接系統(tǒng)服務(wù),可以通過(guò)十六進(jìn)制得到。
但是,短代碼 id 從一位數(shù)的長(zhǎng)度開始遞增。短碼的長(zhǎng)度不是固定的,但是可以通過(guò)從指定的數(shù)字開始增加id來(lái)處理,保證所有的短碼長(zhǎng)度相同。同時(shí)生成的短碼是有序的,可能存在安全問(wèn)題。生成的短碼id可以和長(zhǎng)URL等其他關(guān)鍵字進(jìn)行md5操作,生成最終的短碼。
摘要算法
摘要算法也稱為哈希算法,是指任意長(zhǎng)度的輸入數(shù)據(jù)和固定長(zhǎng)度的輸出數(shù)據(jù)。相同的輸入數(shù)據(jù)總是得到相同的輸出,不同的輸入數(shù)據(jù)試圖得到不同的輸出。
算法過(guò)程:
從長(zhǎng)URL md5生成一個(gè)32位的簽名串短鏈接系統(tǒng),分為4段,每段8字節(jié);循環(huán)處理這四個(gè)段短鏈接系統(tǒng),取8個(gè)字節(jié),把它當(dāng)作一個(gè)16進(jìn)制字符串和0x3fffffff(30位1)與運(yùn)算,即忽略30位以上的處理;這30位分為6個(gè)段,每個(gè) 5 位數(shù)字作為字母表的索引,得到一個(gè)特定的字符,依次得到 6 位字符串;總共 md5 字符串可以得到 4 個(gè) 6 位字符串;取其中任意一個(gè)作為這個(gè)長(zhǎng)url的短url地址;
雖然這個(gè)算法會(huì)產(chǎn)生 4 個(gè),但仍有重復(fù)的機(jī)會(huì)。
雖然概率很小,但是這種方法還是有沖突的可能,解決沖突會(huì)比較麻煩。但是這種方法生成的短碼數(shù)量是固定的,連續(xù)生成的短碼沒(méi)有先后順序。
普通隨機(jī)數(shù)
方法是從62個(gè)字符串中隨機(jī)選擇一個(gè)6位短碼的組合,然后到數(shù)據(jù)庫(kù)中檢查該短碼是否已經(jīng)存在。如果已經(jīng)存在,則繼續(xù)循環(huán)該方法再次獲取短碼,否則直接返回。
這種方法是最簡(jiǎn)單的實(shí)現(xiàn),但是由于Math.round()方法生成的隨機(jī)數(shù)是偽隨機(jī)數(shù),碰撞的可能性不小。在數(shù)據(jù)量很大的情況下,可能會(huì)重復(fù)多次生成不沖突的短碼。
算法分析
我們將一一分析上述算法的優(yōu)缺點(diǎn)。
如果你使用自增id算法,就會(huì)出現(xiàn)不法分子可以窮舉你的短鏈地址的問(wèn)題。原理是將十進(jìn)制數(shù)轉(zhuǎn)換為62,這樣別人就可以用同樣的方法遍歷你的短鏈,得到對(duì)應(yīng)的原始鏈接。例如:and,這兩個(gè)短鏈網(wǎng)站,分別從a3300-a3399,可以嘗試多次返回正確的url。因此,以這種方式生成的短鏈實(shí)際上對(duì)用戶來(lái)說(shuō)并不安全。
抽象算法實(shí)際上是一種哈希算法。說(shuō)到散列,大家可能覺(jué)得很低,但實(shí)際上散列可能是最優(yōu)解。比如發(fā)現(xiàn)不斷生成的url沒(méi)有規(guī)律,很有可能是用hash算法來(lái)實(shí)現(xiàn)的。
普通隨機(jī)數(shù)算法,這種算法生成的東西和摘要算法一樣,但是碰撞的概率會(huì)更高。因?yàn)檎惴ó吘故菍?duì)URL進(jìn)行hash,隨機(jī)數(shù)算法就是簡(jiǎn)單的隨機(jī)生成,一旦數(shù)字上來(lái),難免會(huì)導(dǎo)致重復(fù)。
基于以上,我選擇了最低的算法:摘要算法。
完成
存儲(chǔ)解決方案
數(shù)據(jù)庫(kù)存儲(chǔ)解決方案
短網(wǎng)址的基礎(chǔ)數(shù)據(jù)以域名和后綴的形式分開存儲(chǔ)。另外,域名需要區(qū)分HTTP和HTTPS,哈希方案對(duì)整個(gè)鏈接進(jìn)行哈希處理,而不是對(duì)域名以外的鏈接進(jìn)行哈希處理。域名單獨(dú)保存,可用于分析當(dāng)前域名下的鏈接使用情況。
添加當(dāng)前鏈接有效性字段。一般來(lái)說(shuō),短鏈需求可能是相關(guān)活動(dòng)或熱點(diǎn)事件。這條短鏈會(huì)在一段時(shí)間內(nèi)非常活躍,一段時(shí)間后景氣度會(huì)繼續(xù)下降。所以沒(méi)有必要永久保留這種鏈接,增加每次查詢的負(fù)擔(dān)。
對(duì)于過(guò)期數(shù)據(jù)的處理,可以在添加新的短鏈時(shí)判斷當(dāng)前短鏈的過(guò)期日期,并為每天到達(dá)過(guò)期日期的數(shù)據(jù)在HBase中創(chuàng)建單獨(dú)的表。每天只處理當(dāng)天HBase表中的無(wú)效數(shù)據(jù)。
數(shù)據(jù)庫(kù)的基本表如下:
base_urlsuffix_urlshot_codetotal_click_countfull_urlexpiration_date
/搜索/12345
edfg3s
/aiCheck/getResult/123
Fe9dq
/文庫(kù)/12354
lcfr53
字段定義:
base_url:域名
suffix_url:鏈接除域名外的后綴
full_url:完整鏈接
shot_code:當(dāng)前suffix_url鏈接的短代碼
expiration_date:到期日期
total_click_count:當(dāng)前鏈接的總點(diǎn)擊次數(shù)
expire_date:當(dāng)前鏈接過(guò)期時(shí)間
緩存方案
個(gè)人覺(jué)得緩存上百G數(shù)據(jù)不適合,所以有一個(gè)折中的方案:把最近3個(gè)月的查詢或者新增的URL放入緩存,使用LRU算法進(jìn)行熱更新. 這樣發(fā)送最近使用的概率會(huì)命中緩存,所以不用去庫(kù)了。找不到的時(shí)候去圖書館更新緩存。
對(duì)于新的鏈接,首先檢查緩存是否存在,如果緩存不存在,再檢查數(shù)據(jù)庫(kù)。數(shù)據(jù)庫(kù)已經(jīng)分表了,查詢效率不會(huì)很低。
查詢要求是用戶持有短鏈查詢對(duì)應(yīng)的真實(shí)地址,那么緩存的key只能是短鏈,可以以KV的形式存儲(chǔ)。
雜耍
其實(shí)也可以考慮其他的存儲(chǔ)方案,比如HBase,作為NOSQL數(shù)據(jù)庫(kù),HBase在性能上僅次于redis,但是存儲(chǔ)成本比redis低很多數(shù)量級(jí)。存儲(chǔ)基于 HDFS。當(dāng)內(nèi)存已滿時(shí),數(shù)據(jù)將被刷新到 HFile。讀取數(shù)據(jù)也更快,因?yàn)樗褂?LSM 樹結(jié)構(gòu)而不是 B 或 B+ 樹。HBase 會(huì)使用 LRU 算法將最近讀取的數(shù)據(jù)放入緩存中。如果想增強(qiáng)讀取能力,可以增加blockCache。
其次,也可以使用ElasticSearch,適當(dāng)?shù)乃饕?guī)則的效果并不遜色于緩存方案。
是否需要分庫(kù)分表?
單條數(shù)據(jù)小于10b,1億條數(shù)據(jù)總?cè)萘考s953G。一張表肯定不能支持這么大的量,所以需要分表。如果你有信心在2年內(nèi)服務(wù)可以達(dá)到這個(gè)規(guī)模,那么你從設(shè)計(jì)之初就可以考慮分表的方案。
那么如何定義分表的規(guī)則呢?
如果按照單表500萬(wàn)條記錄計(jì)算,一共可以分成20張表,那么單表的容量是47G,還是蠻大的,所以考慮分表的key和單表的容量,如果分成100張表,那么單表的容量是10G,通過(guò)數(shù)字后綴路由到表也比較容易。您可以對(duì) short_code 進(jìn)行編碼以生成數(shù)字類型,然后進(jìn)行路由。
怎么跳
當(dāng)我們?cè)跒g覽器中輸入
DNS第一次解析得到的IP地址 當(dāng)DNS得到IP地址時(shí)(例如:12.34.5.32),會(huì)向這個(gè)發(fā)送HTTPGET請(qǐng)求地址,查詢的是短碼a3300【服務(wù)器會(huì)通過(guò)短碼a3300獲取對(duì)應(yīng)的長(zhǎng)URL,通過(guò)HTTP301請(qǐng)求去對(duì)應(yīng)的長(zhǎng)URL, 25197,26089617-5013871,00.html.
這里有個(gè)小知識(shí)點(diǎn),為什么用301跳而不是302跳?
知識(shí)點(diǎn):為什么用302跳而不是301跳?
301是永久重定向,302是臨時(shí)重定向。短地址一旦生成就不會(huì)改變,所以使用 301 符合 http 語(yǔ)義。但是如果使用301、谷歌、百度等搜索引擎,搜索時(shí)會(huì)直接顯示真實(shí)地址,那么我們無(wú)法統(tǒng)計(jì)短地址被點(diǎn)擊的次數(shù),也無(wú)法收集用戶的Cookie、User Agent等信息??梢杂脕?lái)做很多有趣的大數(shù)據(jù)分析,也是短網(wǎng)址系統(tǒng)服務(wù)商的主要利潤(rùn)來(lái)源。
引用自知乎-武林的回答,原文鏈接
附上兩個(gè)算法:
總結(jié)算法:
import org.apache.commons.lang3.StringUtils; import javax.xml.bind.DatatypeConverter; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.concurrent.atomic.AtomicLong; import static com.alibaba.fastjson.util.IOUtils.DIGITS; /** * @author rickiyang * @date 2020-01-07 * @Desc TODO */ public class ShortUrlGenerator { public static void main(String[] args) { String sLongUrl = "http://www.baidu.com/121244/ddd"; for (String shortUrl : shortUrl(sLongUrl)) { System.out.println(shortUrl); } } public static String[] shortUrl(String url) { // 可以自定義生成 MD5 加密字符傳前的混合 KEY String key = "dwz"; // 要使用生成 URL 的字符 String[] chars = new String[]{"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", "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" }; // 對(duì)傳入網(wǎng)址進(jìn)行 MD5 加密 String sMD5EncryptResult = ""; try { MessageDigest md = MessageDigest.getInstance("MD5"); md.update((key + url).getBytes()); byte[] digest = md.digest(); sMD5EncryptResult = DatatypeConverter.printHexBinary(digest).toUpperCase(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } String[] resUrl = new String[4]; //得到 4組短鏈接字符串 for (int i = 0; i < 4; i++) { // 把加密字符按照 8 位一組 16 進(jìn)制與 0x3FFFFFFF 進(jìn)行位與運(yùn)算 String sTempSubString = sMD5EncryptResult.substring(i * 8, i * 8 + 8); // 這里需要使用 long 型來(lái)轉(zhuǎn)換,因?yàn)?Inteper .parseInt() 只能處理 31 位 , 首位為符號(hào)位 , 如果不用 long ,則會(huì)越界 long lHexLong = 0x3FFFFFFF & Long.parseLong(sTempSubString, 16); String outChars = ""; //循環(huán)獲得每組6位的字符串 for (int j = 0; j < 6; j++) { // 把得到的值與 0x0000003D 進(jìn)行位與運(yùn)算,取得字符數(shù)組 chars 索引(具體需要看chars數(shù)組的長(zhǎng)度 以防下標(biāo)溢出,注意起點(diǎn)為0) long index = 0x0000003D & lHexLong; // 把取得的字符相加 outChars += chars[(int) index]; // 每次循環(huán)按位右移 5 位 lHexLong = lHexLong >> 5; } // 把字符串存入對(duì)應(yīng)索引的輸出數(shù)組 resUrl[i] = outChars; } return resUrl; } }
將數(shù)字轉(zhuǎn)換為 base62 算法:
/** * @author rickiyang * @date 2020-01-07 * @Desc TODO ** 進(jìn)制轉(zhuǎn)換工具,最大支持十進(jìn)制和62進(jìn)制的轉(zhuǎn)換 * 1、將十進(jìn)制的數(shù)字轉(zhuǎn)換為指定進(jìn)制的字符串; * 2、將其它進(jìn)制的數(shù)字(字符串形式)轉(zhuǎn)換為十進(jìn)制的數(shù)字 */ public class NumericConvertUtils { public static void main(String[] args) { String str = toOtherNumberSystem(22, 62); System.out.println(str); } /** * 在進(jìn)制表示中的字符集合,0-Z分別用于表示最大為62進(jìn)制的符號(hào)表示 */ private static final char[] digits = {'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'}; /** * 將十進(jìn)制的數(shù)字轉(zhuǎn)換為指定進(jìn)制的字符串 * * @param number 十進(jìn)制的數(shù)字 * @param seed 指定的進(jìn)制 * @return 指定進(jìn)制的字符串 */ public static String toOtherNumberSystem(long number, int seed) { if (number < 0) { number = ((long) 2 * 0x7fffffff) + number + 2; } char[] buf = new char[32]; int charPos = 32; while ((number / seed) > 0) { buf[--charPos] = digits[(int) (number % seed)]; number /= seed; } buf[--charPos] = digits[(int) (number % seed)]; return new String(buf, charPos, (32 - charPos)); } /** * 將其它進(jìn)制的數(shù)字(字符串形式)轉(zhuǎn)換為十進(jìn)制的數(shù)字 * * @param number 其它進(jìn)制的數(shù)字(字符串形式) * @param seed 指定的進(jìn)制,也就是參數(shù)str的原始進(jìn)制 * @return 十進(jìn)制的數(shù)字 */ public static long toDecimalNumber(String number, int seed) { char[] charBuf = number.toCharArray(); if (seed == 10) { return Long.parseLong(number); } long result = 0, base = 1; for (int i = charBuf.length - 1; i >= 0; i--) { int index = 0; for (int j = 0, length = digits.length; j < length; j++) { //找到對(duì)應(yīng)字符的下標(biāo),對(duì)應(yīng)的下標(biāo)才是具體的數(shù)值 if (digits[j] == charBuf[i]) { index = j; } } result += index * base; base *= seed; } return result; } }以上就是關(guān)于《如何創(chuàng)建一個(gè)短鏈接系統(tǒng)?》的全部?jī)?nèi)容了,感興趣的話可以點(diǎn)擊右側(cè)直接使用哦!》》在線短鏈接生成器