1.常常收到某东的短信,并且附带一个链接,点击这个链接会跳转到某个指定的网址,这是怎么实现的呢?
首先,我们先看看当当的短链接http://3.n/XilLrd6
它是由两个部分组成
http://3.n/
:短链接系统的域名地址
XilLrd6
:请求参数
请求http://3.n/XilLrd6
地址后,返回状态如下所示
2.短链跳转的基本原理
用户访问短链地址然后重定向到原来的地址。
在HTTP协议中,30X状态代表的是重定向的状态。其中可以是301 也可以是302。
301 代表永久重定向。对于GET请求, 301跳转会默认被浏览器cache。也就是说,用户第一次访问某个短链接后,如果服务器返回301状态码,则这个用户在后续多次访问同一短链接地址,浏览器会直接请求跳转地址,而不会再去短链接系统上取!
这么做优点很明显,降低了服务器压力,但是无法统计到短链接地址的点击次数。
302代表临时重定向。对于GET请求, 302跳转默认不会被浏览器缓存,除非在HTTP响应中通过 Cache-Control 或 Expires 暗示浏览器缓存。因此,用户每次访问同一短链接地址,浏览器都会去短链接系统上取。
这么做的优点是,能够统计到短地址被点击的次数了。但是服务器的压力变大了。
下面说最关键的一段,怎么将http://ihelp.jd.com压缩为XilLrd6
字符
3.算法原理
首先呢,我们需要一张表来存储,长短链接间的映射关系。表结构如下
列名 | 说明 |
---|---|
id | BIGINT,自增主键 |
url | 长地址,也就是需要跳转的原地址 |
好的,假设我们此时表里的数据如下
id | url |
---|---|
1 | http://ihelp.jd.com/1 |
2 | http://ihelp.jd.com/2 |
我们此时拿自增id作为短链接的key。假设域名http://dwz.win
是短链接系统,也就是说请求:
(1)
会跳转http://ihelp.jd.com/1;http://3.n
/1
(2
会跳转http://ihelp.jd.com/2;http://3.n
/2
这么做,也不是不行,有两个缺点你要评估能不能接受!
- (1)如果数据比较大,比如几百亿,你的url地址依然过长
- (2)你的数据具有规律性,别人用一个简单的脚本就可以遍历出你的跳转地址!
为了解决上面的两个缺点,我们增加一个列,用来存储key值。此时表结构如下
列名 | 说明 |
---|---|
id | BIGINT,自增主键 |
key | 短串,需要加唯一索引 |
url | 长地址,也就是需要跳转的原地址 |
我们为了缩短id的长度呢,一般可以这么做。由于我们的短链接是由 a-z、A-Z 和 0-9 共 62 个字符可以选择。因此,我们可以讲十进制的数字id,转换为一个62进制的数,例如201314就可以转换为Qn0。
算法如下
public class Demo {
public static void main(String[] args) {
String s = toBase62(201314);
System.out.println(s);
}
private static final String BASE = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
public static String toBase62(long num) {
StringBuilder sb = new StringBuilder();
do {
int i = (int) (num % 62);
sb.append(BASE.charAt(i));
num /= 62;
} while (num > 0);
return sb.reverse().toString();
}
另外,我们需要引入一个全局发号器,一直返回全局自增的ID。相当于,我们的短链接系统先去请求这个全局自增ID,然后将全局自增ID转换为62进制的数,作为key。
接下来,解决第二个问题!数据具有规律性的问题。毕竟你转换为62进制后,只是解决了数据过长的问题,数据规律性问题还是没解决。
因此,我们需要引入一个随机算法。那么此时,你的考虑点在于,你是否要根据key值,反推出全局id值!来抉择不同的随机算法!
(1)不希望反推出全局ID
OK,那就用一个洗牌算法,打乱算出的值。比如十进制的201314就可以转换为Qn0。然后再使用洗牌算法,可以返回n0Q、Q0n....其中之一。但是会有一定几率冲突,多洗几次就行。
(2)希望反推出全局ID
OK,那就在得到Qn0这个数字后,将其转换为二进制数。然后在固定位,第五位,第十位...(等等)插入一个随机值即可。
至于如何反推也很简单,你拿到短链接key后,将固定位的数字去除,再转换为十进制即可。
讲到这里,就基本将key如何生成的逻辑讲清楚了。那么用户在点击短链接的时候,例如地址http://dwz.win/nXR
,短链接系统解析出key为nXR,根据唯一索引去表中将nXR对应的url返回即可。
4.细节优化
(1)分库分表
如果这个系统是放在公网,给大家使用的。建议上来就分库分表,数据量过1000万是很容易的。这里涉及到一个问题,拿全局发号器给的自增id做分片健,还是拿转换后的key做分片键。
显然,用转换后的key做分片键会更容易一些。如果用ID做为分片键,存在两个问题!
(1)用户请求的key,需要做一个逆运算推算回ID,然后根据ID,再去对应表里去找,增加响应时间。
(2)根据选择的随机算法不同,key不一定能够推算回ID值。这种情况下,只能每张表去查,更慢。
所以用key做分片键,再适合不过了。拿到用户请求的KEY后,直接定位到对应的表里将url取出即可。
(2)读写分离
这种系统显然,读远大于写。建议可以考虑做读写分离。
(3)引入缓存
假设,我们在一个时间。给手机推送短信链接的短信后。显然,后面的一段时间内,对该短链接的请求量会大大提升。没有必要每次都去数据库查询,因此可以引入redis缓存。
(4)全局发号器用其他算法行不行
可以。这里只是要一个全局唯一ID而已。自己要估算好,使用其他算法所带来的性能影响。以及采用其他算法,会不会造成生成的生成的ID过于规律。
(5)防攻击
做好被恶意攻击的准备,防止自增ID的值,被全部耗光。