短链接解决方案
「这是我参与11月更文挑战的第9天,活动详情查看:2021最后一次更文挑战」
电商短链接业务场景
场景1:阿里健康大药房短信
浏览器打开链接 c.tb.cn/d5.dJPad
跳转到
detail.tmall.com/item.htm?id…
场景2:京东短信
浏览器打开链接 3.cn/1iGyAa-x 就转变为如下:
pro.m.jd.com/mall/active…
场景3:美团优选短信
还有淘宝商品链接分享等等场景。。。
什么是短链接
就是把普通网址,转换成比较短的网址。
我们先回到生成好的短链上c.tb.cn/d5.dJPad,虽然这个链接看起来有点奇怪,但他终究还是一个链接,从URL的特征我们可以分出:
c.tb.cn是域名d5.dJPad是参数
为什么需要短链接
节省网址长度,便于社交化传播。控制成本,提升体验:比如,文案总字数超过70个字,那就算两条短信计费,超过140个字就算三条短信计费匿名访问:隐藏访问来源,在最终页面通过HTTP的Refer字段获取不到跳转前的来源。密码访问:对某些链接想设置密码访问,但是不能实现直接对原来的链接进行密码控制,就可以通过对原始链接转换后的短链接进行访问控制来实现目的(此时原始链接是没有访问控制的,但是链接的生成没有规律可言,便可以认为没有人能访问到原始链接),同密码访问类似,也可以对某个短链接限定访问次数、访问时段、访问黑白名单来控制访问。访问统计:通过统计对短链接的访问数便可以得到原始链接的访问量,在移动互联网下,可针对不同的客户端(Android、IOS、PC、MAC)生成不同的短链接来统计不同客户端的访问量。简化二维码:二维码图案的复杂度和原始信息的大小成正相关,过长的链接会导致生成的二维码图片过于复杂,降低二维码识别的成功率。降低权重传递:在搜索引擎领域,存在链接权重传递的现实。即如果链接A有很多人访问,通过搜索引擎搜索链接A相关内容时,会把链接A放在搜索结果的前面,若是在一个没有很多访问量的链接B中引入链接A,就让搜索引擎在扫描链接关系时赋予链接B稍高的权重,导致搜索链接B相关内容时会把链接B放在搜索结果的前面。使用短链接就可以打破原有的依赖链(从链接A>链接B变成链接A>短链接>链接B)。
短链接原理
- 将长链接通过一定的
手段生成一个短链接 - 访问短链接时实际访问的是短链接服务器,然后根据短链接的参数找回对应的长链接
- 服务器返回302状态码,将响应头中的Location设置为:长链接
- 浏览器
重定向:长链接 - 长链接服务器返回响应
短链接服务核心部分包括:
- 长链接转短链接的算法
- 对应关系持久化,方便长链接和短链接互相查询
短链接服务提供商
目前有很多提供短链接生成的服务,需要使用短链接的时候,直接调用相关服务进行转化即可。
设计短链接生成服务
首先,也是最重要的,申请一个尽量短的域名。
后续的过程本质就是一个发号的过程,每当对一个长链接生成短链接时,就生成一个唯一标识,附加于短域名后,然后记录下对应关系并返回生成的短链接。
后续有对原始链接的请求时,查询对应表,若是查询到就返回301或302并返回查询到的原始链接即可。
可见整个系统中关键的两点是,唯一标识生成和对应表存储。
关于存储对应表的服务,由于只是存储简单的K-V关系,所以可以采用类似Redis的K-V数据库。
短链接算法
算法特性
- 高可用,服务可以部署到多台服务器上不会出现单点故障问题。
- 长链接和短链接1:1映射。一方面可以解决存储资源,另外也可以方便点击统计等功能实现。
- 少碰撞。生成短链接性能不会随着已生成短链接数量增加而线性增加。
业内常用3种算法
1.发号器 & 进制转化
实现:
发号器(ID自增)+62进制编码
算法思路:
- 首先请求发号器生成
不重复十进制数字 - 十进制数字转化成
62进制
为什么要用62进制转换?64进制转换倒是听得多了
- 62进制转换是因为62进制转换后
只含数字+小写+大写字母。而64进制转换会含有/,+这样的符号(不符合正常URL的字符) - 10进制转62进制可以
缩短字符,如果我们要6位字符的话,62^6=56800235584,已经有560亿个组合了。
算法特点:
- 该算法能够支持6位字符组合62^6=500多亿条长链接。
- 发号器可以选择数据库自增ID,snake算法,redis等诸多实现。
举例:
将https://juejin.cn/user/474627279698541/posts长链接发给它的号id是唯一的10000,然后将10000进行62进制编码得到的结果是:2Bi
那我的短链URL就可以弄成https://3.cn/2Bi,其中3.cn是域名,2Bi是经过62进制转换后的参数。我们保存好2Bi和长链接的关系,下次用短链接请求就能找到长链接。
总结:
使用redis自增命令获取id,转成62进制,在redis用hash保存短链接和长链接的映射关系
2.随机算法&hash算法(MD5)
算法思路:
随机算法原理是根据长链接随机生成字符串,然后将字符串映射到[0-9][a-z][A-Z]集合上。
算法一
- 将长网址md5生成32位签名串,分为4段, 每段8个字节;
- 对这四段循环处理, 取8个字节, 将它转换成16进制串与
0x3FFFFFFF(30位1)与操作, 即超过30位的忽略处理; - 这30位分成6段, 每5位的数字作为字母表的索引取得特定字符, 依次进行获得6位字符串;
- 总的md5串可以获得4个6位串; 取里面的任意一个就可作为这个长url的短url地址;
这种算法,虽然会生成4个,但是仍然存在重复几率.
算法二
62个可选元素再随机选两个组成长度为64的字符数组。
- 随机生成36位 0/1的随机数
- 36位分成6份,通过randomValue>>6以此获取低6位元素。
- (randomValue>>6) & 0x3F 获取对应的字符
算法二冲突的概率为1/64^6=1/2^36。
3.预生成 & 访问分配
算法思路:
[0-9][a-z][A-Z]集合 这62位取6位组合,可产生500多亿个组合数量.
把数字和字符组合(短链接)做一定的映射,就可以产生唯一的字符串
如第62个组合就是aaaaa9,第63个组合就是aaaaba,再利用洗牌算法,把原字符串打乱后保存,那么对应位置的组合字符串就会是无序的组合。
把长网址存入数据库,取返回的id,找出对应的字符串,例如返回ID为1,那么对应上面的字符串组合就是bbb,
同理ID为2时,字符串组合为bba,依次类推,直至到达64种组合后才会出现重复的可能,
算法特点:
- 如果用上面的62个字符,任意取6个字符组合成字符串的话,你的数据存量达到500多亿后才会出现重复的可能。
重定向301还是302?
- 301:重定向是
永久的重定向,搜索引擎在抓取新的内容时,同时也将旧的网站进行替换,替换为重定向后的地址。 搜索引擎只会将新链接加到索引里。会使用浏览器缓存,下次访问老链接时会直接访问新链接,因为不经过服务器解析短链接,不能做短链接访问统计。 - 302:重定向只是
暂时的重定向,搜索引擎会认为重定向后的地址为临时的,并会保存新的地址.搜索引擎会同时索引老链接和新链接. 浏览器会认为临时跳转,新老链接都会被访问到。
因此,为了统计到短链接访问次数,一般使用302状态码进行跳转。
短信的链接直接跳转到APP
实战:SpringBoot+Redis高并发《短链接转换器》
《短链接转换器》的原理:
- 长链接转换为短链接
实现原理:长链接转换为短链接加密串key,然后存储于redis的hash结构中。
- 重定向到原始的url
实现原理:通过加密串key到redis找出原始url,然后重定向出去
本例子基于随机算法一实现短链接转换器
短链接转换器
import org.apache.commons.codec.digest.DigestUtils;
/**
* 将长网址 md5 生成 32 位签名串,分为 4 段, 每段 8 个字节
* 对这四段循环处理, 取 8 个字节, 将他看成 16 进制串与 0x3fffffff(30位1) 与操作, 即超过 30 位的忽略处理
* 这 30 位分成 6 段, 每 5 位的数字作为字母表的索引取得特定字符, 依次进行获得 6 位字符串
* 总的 md5 串可以获得 4 个 6 位串,取里面的任意一个就可作为这个长 url 的短 url 地址
*/
public class ShortUrlGenerator {
//26+26+10=62
public static final 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"};
/**
* 一个长链接URL转换为4个短KEY
*/
public static String[] shortUrl(String url) {
String key = "";
//对地址进行md5
String sMD5EncryptResult = DigestUtils.md5Hex(key + url);
System.out.println(sMD5EncryptResult);
String hex = sMD5EncryptResult;
String[] resUrl = new String[4];
for (int i = 0; i < 4; i++) {
//取出8位字符串,md5 32位,被切割为4组,每组8个字符
String sTempSubString = hex.substring(i * 8, i * 8 + 8);
//先转换为16进账,然后用0x3FFFFFFF进行位与运算,目的是格式化截取前30位
long lHexLong = 0x3FFFFFFF & Long.parseLong(sTempSubString, 16);
String outChars = "";
for (int j = 0; j < 6; j++) {
//0x0000003D代表什么意思?他的10进制是61,61代表chars数组长度62的0到61的坐标。
//0x0000003D & lHexLong进行位与运算,就是格式化为6位,即61内的数字
//保证了index绝对是61以内的值
long index = 0x0000003D & lHexLong;
outChars += chars[(int) index];
//每次循环按位移5位,因为30位的二进制,分6次循环,即每次右移5位
lHexLong = lHexLong >> 5;
}
// 把字符串存入对应索引的输出数组
resUrl[i] = outChars;
}
return resUrl;
}
/**
* 566ab90fc1bb2c6544ac4353f0d0a364
* niIvAj
* B7jQza
* ryqyia
* AzE7ny
* @param args
*/
public static void main(String[] args) {
// 长连接
String longUrl = "https://juejin.cn/user/474627279698541/posts";
// 转换成的短链接后6位码,返回4个短链接
String[] shortCodeArray = shortUrl(longUrl);
for (int i = 0; i < shortCodeArray.length; i++) {
// 任意一个都可以作为短链接码
System.out.println(shortCodeArray[i]);
}
}
}
复制代码
控制器
@RestController
@Slf4j
public class ShortUrlController {
@Autowired
private HttpServletResponse response;
@Autowired
private RedisTemplate redisTemplate;
private final static String SHORT_URL_KEY="short:url";
/**
* 长链接转换为短链接
* 实现原理:长链接转换为短加密串key,然后存储在redis的hash结构中。
*/
@GetMapping(value = "/encode")
public String encode(String url) {
//一个长链接url转换为4个短加密串key
String [] keys= ShortUrlGenerator.shortUrl(url);
//任意取出其中一个,我们就拿第一个
String key=keys[0];
//用hash存储,key=加密串,value=原始url
this.redisTemplate.opsForHash().put(SHORT_URL_KEY,key,url);
log.info("长链接={},转换={}",url,key);
return "http://127.0.0.1:9090/"+key;
}
/**
* 重定向到原始的URL
* 实现原理:通过短加密串KEY到redis找出原始URL,然后重定向出去
*/
@GetMapping(value = "/{key}")
public void decode(@PathVariable String key) {
//到redis中把原始url找出来
String url=(String) this.redisTemplate.opsForHash().get(SHORT_URL_KEY,key);
try {
//重定向到原始的url
response.sendRedirect(url);
} catch (IOException e) {
e.printStackTrace();
}
}
}
复制代码
拾遗-MD5算法特点
- 压缩性:任意长度的数据,算出的MD5值长度都是固定的。
- 容易计算:从原数据计算出MD5值很容易。
- 抗修改性:对原数据进行任何改动,哪怕只修改1个字节,所得到的MD5值都有很大区别。
- 强抗碰撞:已知原数据和其MD5值,想找到一个具有相同MD5值的数据(即伪造数据)是非常困难的。
redis分布式缓存系列
- redis分布式缓存(一)一一 redis安装(linux和docker)
- redis分布式缓存(二)一一 RDB和AOF
- redis分布式缓存(三)一一 SpringBoot集成Mybatis-Plus,Redis和Swagger
- redis分布式缓存(四)一一 SpringCache集成Redis
- redis分布式缓存(五)一一 常用命令(String)
- redis分布式缓存(六)一一 文章的阅读量PV解决方案
- redis分布式缓存(七)一一 分布式全局id解决方案
- redis分布式缓存(八)一一 高并发原子性操作(Redis+Lua)
- redis分布式缓存(九)一一 黑客防刷攻击解决方案
- redis分布式缓存(十)一一 常用命令(Hash)
- redis分布式缓存(十一)一一 储存java bean对象业务剖析
- 文章持续更新中...




近期评论