【原创】这多是东半球最接地气的短连接系统设计

引言

今天下午,烟哥和同事在厕所里排队等坑的时候(人多坑少)。想象一下一个场景,我正在一边排队,一边拿着手机撩妹。前面一个同事,拿着手机短信转过头来和我聊天。
因而,咱们就开始讨论下面这种短连接的实现原理(没错,上厕所也不忘学习!)。
redis

点击其中短连接后,咱们会跳到以下地址
http://h5.dangdang.com/mix_20191015_or4x
本文,咱们来讨论一下其实现原理!算法

正文

需求缘起

这里说一下,为何须要短连接?这个简单,好比你们发微博的时候
数据库

若是URL地址过长,显然能够写的关键字就越少!浏览器

再好比发短信若是短信内容过长,那么一条短信就要拆成两条发,浪费钱!缓存

所以采用短连接,不只节约资源,还十分美观!服务器

请求流程

首先,咱们先看看当当的短连接http://dwz.win/nXR
它是由两个部分组成
http://dwz.win:短连接系统的域名地址
nXR:请求参数
请求http://dwz.win/nXR地址后,返回状态以下所示
app

因而,咱们能够推断出,敲下http://dwz.win/nXR地址后,发生了什么呢?
性能

这里渣渣烟就要多嘴一句了。上图所示短连接系统,返回的状态能够为301或者302,只是当当网用的是301。
这里我要说一下,你们应该明白30X状态,在HTTP协议中,表明的是重定向的状态。
301表明什么?
301表明的是永久重定向。什么意思呢?
对于GET请求, 301跳转会默认被浏览器cache。也就是说,用户第一次访问某个短连接后,若是服务器返回301状态码,则这个用户在后续屡次访问同一短连接地址,浏览器会直接请求跳转地址,而不会再去短连接系统上取!学习

这么作优势很明显,下降了服务器压力,可是没法统计到短连接地址的点击次数。优化

302表明什么?
302表明的是临时定向。什么意思呢?
对于GET请求, 302跳转默认不会被浏览器缓存,除非在HTTP响应中经过 Cache-Control 或 Expires 暗示浏览器缓存。所以,用户每次访问同一短连接地址,浏览器都会去短连接系统上取。

这么作的优势是,可以统计到短地址被点击的次数了。可是服务器的压力变大了。

下面说最关键的一段,怎么将http://h5.dangdang.com/mix_20191015_or4x压缩为nXR字符

算法原理

首先呢,咱们须要一张表来存储,长短连接间的映射关系。表结构以下

列名 说明
id BIGINT,自增主键
url 长地址,也就是须要跳转的原地址

好的,假设咱们此时表里的数据以下

id url
1 http://h5.dangdang.com/mix_20191015_or4x
2 http://h5.dangdang.com/mix_20191102_ad3x

咱们此时拿自增id做为短连接的key。假设域名http://dwz.win是短连接系统,也就是说请求:
(1)http://dwz.win/1会跳转http://h5.dangdang.com/mix_20191015_or4x;
(2)http://dwz.win/2会跳转http://h5.dangdang.com/mix_20191102_ad3x;

这么作,也不是不行,有两个缺点你要评估能不能接受!

  • (1)若是数据比较大,好比几百亿,你的url地址依然过长
  • (2)你的数据具备规律性,别人用一个简单的脚本就能够遍历出你的跳转地址!

为了解决上面的两个缺点,咱们增长一个列,用来存储key值。此时表结构以下

列名 说明
id BIGINT,自增主键
key 短串,须要加惟一索引
url 长地址,也就是须要跳转的原地址

咱们为了缩短id的长度呢,通常能够这么作。因为咱们的短连接是由 a-z、A-Z 和 0-9 共 62 个字符能够选择。所以,咱们能够讲十进制的数字id,转换为一个62进制的数,例如201314就能够转换为Qn0。
算法以下

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返回便可。

细节优化

(1)分库分表
若是这个系统是放在公网,给你们使用的。建议上来就分库分表,数据量过1000万是很容易的。这里涉及到一个问题,拿全局发号器给的自增id作分片健,仍是拿转换后的key作分片键。

显然,用转换后的key作分片键会更容易一些。若是用ID作为分片键,存在两个问题!
(1)用户请求的key,须要作一个逆运算推算回ID,而后根据ID,再去对应表里去找,增长响应时间。
(2)根据选择的随机算法不一样,key不必定可以推算回ID值。这种状况下,只能每张表去查,更慢。

因此用key作分片键,再适合不过了。拿到用户请求的KEY后,直接定位到对应的表里将url取出便可。

(2)读写分离
这种系统显然,读远大于写。建议能够考虑作读写分离。

(3)引入缓存
假设,咱们在一个时间。给手机推送短信连接的短信后。显然,后面的一段时间内,对该短连接的请求量会大大提高。没有必要每次都去数据库查询,所以能够引入redis缓存。

(4)全局发号器用其余算法行不行
能够。这里只是要一个全局惟一ID而已。本身要估算好,使用其余算法所带来的性能影响。以及采用其余算法,会不会形成生成的生成的ID过于规律。

(5)防攻击
作好被恶意攻击的准备,防止自增ID的值,被所有耗光。

总结

很久没写这种设计型的文章了。毕竟是我和同事在厕所蹲坑排队之余,在那闲聊出来的。最后,因为烟哥过于专一和同事聊技术,致使后来我回复那个妹纸的时候已经没有了下文,注孤生!

但愿你们有所收获!

相关文章
相关标签/搜索