前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >面试官说:你来设计一个短链接生成系统吧

面试官说:你来设计一个短链接生成系统吧

原创
作者头像
秦怀杂货店
修改2021-12-04 15:38:46
5550
修改2021-12-04 15:38:46
举报
文章被收录于专栏:技术杂货店技术杂货店

引言

相信大家在生活中,特别是最近的双十一活动期间,会收到很多短信,而那些短信都有两个特征,第一个是几乎都是垃圾短信,这个特点此处可以忽略不计,第二个特点是**链接很短**,比如下面这个:

![20211110222405](https://markdownpicture.oss-cn-qingdao.aliyuncs.com/blog/20211110222405.png)

我们知道,短信有些是有字数限制的,直接放一个带满各种参数的链接,不合适,另外一点是,不想暴露参数。好处无非以下:

- 太长的链接容易被限制长度

- 短链接看着简洁,长链接看着容易懵

- 安全,不想暴露参数

- 可以统一链接转换,当然也可以实现统计点击次数等操作

那背后的原理是什么呢?怎么实现的?让你实现这样的系统,你会怎么设计呢?【来自于某鹅场面试官】

短链接的原理

短链接展示的逻辑

这里最重要的知识点是重定向,先复习一下`http`的状态码:

| 分类 | 含义 |

| :--- | :--------------------------------------------- |

| 1** | 服务器收到请求,需要请求者继续执行操作 |

| 2** | 成功,操作被成功接收并处理 |

| 3** | 重定向,需要进一步的操作以完成请求 |

| 4** | 客户端错误,请求包含语法错误或无法完成请求 |

| 5** | 服务器错误,服务器在处理请求的过程中发生了错误 |

那么以 3 开头的状态码都是关于重定向的:

- 300:多种选择,可以在多个位置存在

- 301:永久重定向,浏览器会缓存,自动重定向到新的地址

- 302:临时重定向,客户端还是会继续使用旧的URL

- 303:查看其他的地址,类似于301

- 304:未修改。所请求的资源未修改,服务器返回此状态码时,不会返回任何资源。

- 305:需要使用代理才能访问到资源

- 306:废弃的状态码

- 307:临时重定向,使用Get请求重定向

整个跳转的流程:

- 1.用户访问短链接,请求到达服务器

- 2.服务器将短链接装换成为长链接,然后给浏览器返回重定向的状态码301/302

- 301永久重定向会导致浏览器缓存重定向地址,短链接系统统计访问次数会不正确

- 302临时重定向可以解决次数不准的问题,但是每次都会到短链接系统转换,服务器压力会变大。

- 3.浏览器拿到重定向的状态码,以及真正需要访问的地址,重定向到真正的长链接上。

从下图可以看出,确实链接被`302`重定向到新的地址上去,返回的头里面有一个字段`Location`就是所要重定向的地址:

<img src="https://markdownpicture.oss-cn-qingdao.aliyuncs.com/blog/20211110235518.png" style="zoom:70%;" />

短链接怎么设计的?

全局发号器

肯定我们第一点想到的是压缩,像文件压缩那样,压缩之后再解压还原到原来的链接,重定向到原来的链接,但是很不幸的是,这个是行不通的,你有见过什么压缩方式能把这么长的数字直接压缩到这么短么?事实上不可能。就像是`Huffman`树,也只能对那种重复字符较多的字符串压缩时效率较高,像链接这种,可能带很多参数,而且各种不规则的情况都有,直接压缩算法不现实。

那`https://dx.10086.cn/tzHLFw`与`https://gd.10086.cn/gmccapp/webpage/payPhonemoney/index.html?channel=`之间的装换是怎么样的呢?前面路径不变,变化的是后面,也就是`tzHLFw`与`gmccapp/webpage/payPhonemoney/index.html?channel=`之间的转换。

实际也很简单,就是数据库里面的一条数据,一个`id`对应长链接(相当于全局的发号器,全局唯一的ID):

| id | url |

| :--: | :----------------------------------------------------------: |

| 1 | https://gd.10086.cn/gmccapp/webpage/payPhonemoney/index.html?channel= |

这里用到的,也就是我们之前说过的分布式全局唯一ID,如果我们直接用`id`作为参数,貌似也可以:`https://dx.10086.cn/1`,访问这个链接时,去数据库查询获得真正的url,再重定向。

单机的唯一`ID`很简单,用原子类`AtomicLong`就可以,但是分布式的就不行了,简单点可以用 `redis`,或者数据库自增,或者可以考虑`Zookeeper`之类的。

id 转换策略

但是直接用递增的数字,有两个坏处:

- 数字很大的时候,还是很长

- 递增的数字,不安全,规律性太强了

明显我们平时看到的链接也不是数字的,一般都是大小写字母加上数字。为了缩短链接的长度,我们必须把`id`转换掉,比如我们的短链接由`a-z`,`A-Z`,`0-9`组成,相当于`62`进制的数字,将`id`转换成为`62`进制的数字:

```java

public class ShortUrl {

private static final String BASE = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";

public static String toBase62(long num) {

StringBuilder result = new StringBuilder();

do {

int i = (int) (num % 62);

result.append(BASE.charAt(i));

num /= 62;

} while (num > 0);

return result.reverse().toString();

}

public static long toBase10(String str) {

long result = 0;

for (int i = 0; i < str.length(); i++) {

result = result * 62 + BASE.indexOf(str.charAt(i));

}

return result;

}

public static void main(String[] args) {

// tzHLFw

System.out.println(toBase10("tzHLFw"));

System.out.println(toBase62(27095455234L));

}

}

```

`id`转 `62`位的`key` 或者`key`装换成为`id`都已经实现了,不过计算还是比较耗时的,不如加个字段存起来,于是数据库变成了:

| id | key | url |

| :---------: | :----: | :----------------------------------------------------------: |

| 27095455234 | tzHLFw | https://gd.10086.cn/gmccapp/webpage/payPhonemoney/index.html?channel= |

但是这样还是很容易被猜出这个`id`和`key`的对应关系,要是被遍历访问,那还是很不安全的,如果担心,可以随机将短链接的字符顺序打乱,或者在适当的位置加上一些随机生成的字符,比如第`1,4,5 `位是随机字符,其他位置不变,只要我们计算的时候,将它对应的关系存到数据库,我们就可以通过连接的`key`找到对应的`url`。(值得注意的是,`key`必须是全局唯一的,如果冲突,必须重新生成)

一般短链接都有过期时间,那么我们也必须在数据库里面加上对应的字段,访问的时候,先判断是否过期,过期则不给予重定向。

![](https://markdownpicture.oss-cn-qingdao.aliyuncs.com/blog/20211115003828.png)

性能考虑

如果有很多短链接暴露出去了,数据库里面数据很多,这个时候可以考虑使用缓存优化,生成的时候顺便把缓存写入,然后读取的时候,走缓存即可,因为一般短链接和长链接的关系不会修改,即使修改,也是很低频的事情。

如果系统的`id`用完了怎么办?这种概率很小,如果真的发生,可以重用旧的已经失效的`id`号。

如果被人疯狂请求一些不存在的短链接怎么办?其实这就是缓存穿透,缓存穿透是指,**缓存和数据库都没有的数据**,被大量请求,比如订单号不可能为`-1`,但是用户请求了大量订单号为`-1`的数据,由于数据不存在,缓存就也不会存在该数据,所有的请求都会直接穿透到数据库。如果被恶意用户利用,疯狂请求不存在的数据,就会导致数据库压力过大,甚至垮掉。

针对这种情况,一般可以用布隆过滤器过滤掉不存在的数据请求,但是我们这里`id`本来就是递增且有序的,其实我们范围大致都是已知的,更加容易判断,超出的肯定不存在,或者请求到的时候,缓存里面放一个空对象也是没有问题的。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
短信
腾讯云短信(Short Message Service,SMS)可为广大企业级用户提供稳定可靠,安全合规的短信触达服务。用户可快速接入,调用 API / SDK 或者通过控制台即可发送,支持发送验证码、通知类短信和营销短信。国内验证短信秒级触达,99%到达率;国际/港澳台短信覆盖全球200+国家/地区,全球多服务站点,稳定可靠。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档