短URL系统的核心是将长的 URL 转化成短的 URL;在访问系统时,先使用短地址A访问短URL系统,由短URL系统映射到对应的长地址B,然后客户端再重定向(301或者302)到B网址,如下图所示:
1、链接变短,对于有长度限制的平台发文,可编辑的文字就变多了
2、短链接生成的二维码更易于识别,而长链接的二维码密集难识别
3、短链接更加简洁好看且安全,不暴露访问参数。
4、能规避关键词、域名屏蔽等手段
5、链接太长在有些平台上无法自动识别为超链接
系统核心实现思路:使用发号器发号 => 为每个长地址分配一个号码ID => 将号码与长地址存放在DB中 => 将号码转化成62进制,用于表示最终的短地址并返回给用户(假设短地址长度为8位,62的8次方足够一般系统使用了) => 用户使用62进制的短地址请求服务 => 将62进制的数转化成10进制 => 在DB中寻找对应的长地址 => 将用户请求重定向到对应的地址上
(1)如果是小型系统,可使用MySQL的自增主键,但是这种方式存在在高并发情况下性能问题,要解决该问题,可以通过批量发号来解决,提前为每台机器发放一个ID区间 tmp_start_num - tmp_end_num,然后由机器在自己内存中使用 AtomicLong 原子类去保证自增,减少对DB的依赖,等到区间即将满了,再向 DB 请求下一个区段的号码,然后等区间满了,再一次性将记录保存到DB中,这样就可以将对数据库持续的操作移到代码中进行,并且异步进行获取和写入操作,保证服务的持续高并发。比如可以每次从数据库获取10000个号码,然后在内存中进行发放,当剩余的号码不足1000时,重新向MySQL请求下10000个号码,在上一批号码发放完了之后,批量进行写入数据库。
(2)如果是大型系统,则可以使用分布式自增主键
(3)雪花算法:依赖于系统时钟的一致性。如果某台机器的系统时钟回拨,有可能造成 ID 冲突,或者 ID 乱序。
可以使用多个发号器,例如实现两个发号器,一个发单号,一个发双号。依次类推,我们也可以实现 1000 个发号器,分别发尾号为0到999的号。每发一个号,每个发号器加1000,而不是加1。这些发号器独立工作,互不干扰即可,这种方式还能解决单点发号器性能瓶颈。
无论是前面提到的哪种方式,都存在的问题就是同个长址多次请求得到的短址是不一样,因为每次都会重新为该长址生成一个短址。为了实现长短链接真正意义上的一对一,我们可以建立一个长对短的 Hashmap,但是需要付出很大的空间代价。因此我们对这种方式做一些改变来实现部分地址的一对一,比如将最近/最热门的对应关系存储在K-V数据库中,在 Hashmap 中只存储最近生成的长对短的对应关系,并采用过期机制实现 LRU 淘汰,从而保证频繁使用的 URL 的总是对应同一个短址的,但是不保证不频繁使用的URL的对应关系,从而大大减少了空间上的消耗。
此外,使用了缓存,也可以加快程序处理速度,将热门的长链接(需要对长链接进来的次数进行计数)、最近的长链接(可以使用 Redis 保存最近一个小时的数据)等等进行一个缓存,如果请求的长URL命中了缓存,那么直接获取对应的短URL进行返回,不需要再进行生成操作
301永久重定向和 302 临时重定向。
使用 301 虽然可以减少服务器的压力,但是无法在 server 层获取到短网址的访问次数了,如果链接刚好是某个活动的链接,就无法分析此活动的效果以及用于大数据分析了。而 302 虽然会增加服务器压力,但便于在 server 层统计访问数,所以如果对这些数据有需求,可以采用 302,因为这点代价是值得的,但是具体采用哪种跳转方式,还是要结合实际情况进行选型。