专业的短链接生成工具
链接域名
短网址有效期
如何创建一个短链接系统?
更新时间:2025-5-3 21:52:12 作者:爱短链
短链接,通俗的讲,就是通过程序计算等方法,将一个长URL转换成一个短URL字符串。
人们经常会收到一些莫名其妙的营销短信,其中包含一个非常短的链接供您跳转。新浪微博因为字数限制,经常会看到一些不像网址的网址。短链的兴起应该是微博字数的限制激发了大家的创造力。
如果创建一个短链接系统,我们应该怎么做?
把长链接变成短链接;访问短链接的用户会跳转到正确的长链接。
找到对应的长URL,跳转到对应的页面。
短链生成方法
短代码一般由62个字母或数字组成[a - z, A - Z, 0 - 9]。短码的长度也可以定制,但一般不超过8位。最常用的是6位创建一个短链系统,我们应该做什么呢?(图) ,6位短码可以有568亿个组合:(26+26+10)^6 = 56800235584,已经满足了大部分使用场景。
目前比较流行的短码生成方法有:自增id、摘要算法、普通随机数。
自动递增 ID
该方法是一种无碰撞方法。原理是每次添加一个短码,最后一次添加的短码的id加1,然后将十进制的id值转换成62位的id值。细绳。
一般使用数据表中的自增id来完成:每次查询数据表中自增id的最大值时,对应插入的长URL的自增id值为max +1, max+1 转换成 62 的短码短链接系统服务,可以通过十六进制得到。
但是,短代码 id 从一位数的长度开始递增。短码的长度不是固定的,但是可以通过从指定的数字开始增加id来处理,保证所有的短码长度相同。同时生成的短码是有序的,可能存在安全问题。生成的短码id可以和长URL等其他关键字进行md5操作,生成最终的短码。
摘要算法
摘要算法也称为哈希算法,是指任意长度的输入数据和固定长度的输出数据。相同的输入数据总是得到相同的输出,不同的输入数据试图得到不同的输出。
算法过程:
从长URL md5生成一个32位的签名串短链接系统,分为4段,每段8字节;循环处理这四个段短链接系统,取8个字节,把它当作一个16进制字符串和0x3fffffff(30位1)与运算,即忽略30位以上的处理;这30位分为6个段,每个 5 位数字作为字母表的索引,得到一个特定的字符,依次得到 6 位字符串;总共 md5 字符串可以得到 4 个 6 位字符串;取其中任意一个作为这个长url的短url地址;
虽然这个算法会产生 4 个,但仍有重复的机会。
虽然概率很小,但是这种方法还是有冲突的可能,解决冲突会比较麻烦。但是这种方法生成的短码数量是固定的,连续生成的短码没有先后顺序。
普通随机数
方法是从62个字符串中随机选择一个6位短码的组合,然后到数据库中检查该短码是否已经存在。如果已经存在,则继续循环该方法再次获取短码,否则直接返回。
这种方法是最简单的实现,但是由于Math.round()方法生成的随机数是伪随机数,碰撞的可能性不小。在数据量很大的情况下,可能会重复多次生成不冲突的短码。
算法分析
我们将一一分析上述算法的优缺点。
如果你使用自增id算法,就会出现不法分子可以穷举你的短链地址的问题。原理是将十进制数转换为62,这样别人就可以用同样的方法遍历你的短链,得到对应的原始链接。例如:and,这两个短链网站,分别从a3300-a3399,可以尝试多次返回正确的url。因此,以这种方式生成的短链实际上对用户来说并不安全。
抽象算法实际上是一种哈希算法。说到散列,大家可能觉得很低,但实际上散列可能是最优解。比如发现不断生成的url没有规律,很有可能是用hash算法来实现的。
普通随机数算法,这种算法生成的东西和摘要算法一样,但是碰撞的概率会更高。因为摘要算法毕竟是对URL进行hash,随机数算法就是简单的随机生成,一旦数字上来,难免会导致重复。
基于以上,我选择了最低的算法:摘要算法。
完成
存储解决方案
数据库存储解决方案
短网址的基础数据以域名和后缀的形式分开存储。另外,域名需要区分HTTP和HTTPS,哈希方案对整个链接进行哈希处理,而不是对域名以外的链接进行哈希处理。域名单独保存,可用于分析当前域名下的链接使用情况。
添加当前链接有效性字段。一般来说,短链需求可能是相关活动或热点事件。这条短链会在一段时间内非常活跃,一段时间后景气度会继续下降。所以没有必要永久保留这种链接,增加每次查询的负担。
对于过期数据的处理,可以在添加新的短链时判断当前短链的过期日期,并为每天到达过期日期的数据在HBase中创建单独的表。每天只处理当天HBase表中的无效数据。
数据库的基本表如下:
base_urlsuffix_urlshot_codetotal_click_countfull_urlexpiration_date
/搜索/12345
edfg3s
/aiCheck/getResult/123
Fe9dq
/文库/12354
lcfr53
字段定义:
base_url:域名
suffix_url:链接除域名外的后缀
full_url:完整链接
shot_code:当前suffix_url链接的短代码
expiration_date:到期日期
total_click_count:当前链接的总点击次数
expire_date:当前链接过期时间
缓存方案
个人觉得缓存上百G数据不适合,所以有一个折中的方案:把最近3个月的查询或者新增的URL放入缓存,使用LRU算法进行热更新. 这样发送最近使用的概率会命中缓存,所以不用去库了。找不到的时候去图书馆更新缓存。
对于新的链接,首先检查缓存是否存在,如果缓存不存在,再检查数据库。数据库已经分表了,查询效率不会很低。
查询要求是用户持有短链查询对应的真实地址,那么缓存的key只能是短链,可以以KV的形式存储。
杂耍
其实也可以考虑其他的存储方案,比如HBase,作为NOSQL数据库,HBase在性能上仅次于redis,但是存储成本比redis低很多数量级。存储基于 HDFS。当内存已满时,数据将被刷新到 HFile。读取数据也更快,因为它使用 LSM 树结构而不是 B 或 B+ 树。HBase 会使用 LRU 算法将最近读取的数据放入缓存中。如果想增强读取能力,可以增加blockCache。
其次,也可以使用ElasticSearch,适当的索引规则的效果并不逊色于缓存方案。
是否需要分库分表?
单条数据小于10b,1亿条数据总容量约953G。一张表肯定不能支持这么大的量,所以需要分表。如果你有信心在2年内服务可以达到这个规模,那么你从设计之初就可以考虑分表的方案。
那么如何定义分表的规则呢?
如果按照单表500万条记录计算,一共可以分成20张表,那么单表的容量是47G,还是蛮大的,所以考虑分表的key和单表的容量,如果分成100张表,那么单表的容量是10G,通过数字后缀路由到表也比较容易。您可以对 short_code 进行编码以生成数字类型,然后进行路由。
怎么跳
当我们在浏览器中输入
DNS第一次解析得到的IP地址 当DNS得到IP地址时(例如:12.34.5.32),会向这个发送HTTPGET请求地址,查询的是短码a3300【服务器会通过短码a3300获取对应的长URL,通过HTTP301请求去对应的长URL, 25197,26089617-5013871,00.html.
这里有个小知识点,为什么用301跳而不是302跳?
知识点:为什么用302跳而不是301跳?
301是永久重定向,302是临时重定向。短地址一旦生成就不会改变,所以使用 301 符合 http 语义。但是如果使用301、谷歌、百度等搜索引擎,搜索时会直接显示真实地址,那么我们无法统计短地址被点击的次数,也无法收集用户的Cookie、User Agent等信息。可以用来做很多有趣的大数据分析,也是短网址系统服务商的主要利润来源。
引用自知乎-武林的回答,原文链接
附上两个算法:
总结算法:
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" }; // 对传入网址进行 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 进制与 0x3FFFFFFF 进行位与运算 String sTempSubString = sMD5EncryptResult.substring(i * 8, i * 8 + 8); // 这里需要使用 long 型来转换,因为 Inteper .parseInt() 只能处理 31 位 , 首位为符号位 , 如果不用 long ,则会越界 long lHexLong = 0x3FFFFFFF & Long.parseLong(sTempSubString, 16); String outChars = ""; //循环获得每组6位的字符串 for (int j = 0; j < 6; j++) { // 把得到的值与 0x0000003D 进行位与运算,取得字符数组 chars 索引(具体需要看chars数组的长度 以防下标溢出,注意起点为0) long index = 0x0000003D & lHexLong; // 把取得的字符相加 outChars += chars[(int) index]; // 每次循环按位右移 5 位 lHexLong = lHexLong >> 5; } // 把字符串存入对应索引的输出数组 resUrl[i] = outChars; } return resUrl; } }
将数字转换为 base62 算法:
/** * @author rickiyang * @date 2020-01-07 * @Desc TODO ** 进制转换工具,最大支持十进制和62进制的转换 * 1、将十进制的数字转换为指定进制的字符串; * 2、将其它进制的数字(字符串形式)转换为十进制的数字 */ public class NumericConvertUtils { public static void main(String[] args) { String str = toOtherNumberSystem(22, 62); System.out.println(str); } /** * 在进制表示中的字符集合,0-Z分别用于表示最大为62进制的符号表示 */ 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'}; /** * 将十进制的数字转换为指定进制的字符串 * * @param number 十进制的数字 * @param seed 指定的进制 * @return 指定进制的字符串 */ 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)); } /** * 将其它进制的数字(字符串形式)转换为十进制的数字 * * @param number 其它进制的数字(字符串形式) * @param seed 指定的进制,也就是参数str的原始进制 * @return 十进制的数字 */ 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++) { //找到对应字符的下标,对应的下标才是具体的数值 if (digits[j] == charBuf[i]) { index = j; } } result += index * base; base *= seed; } return result; } }以上就是关于《如何创建一个短链接系统?》的全部内容了,感兴趣的话可以点击右侧直接使用哦!》》在线短链接生成器