极低成本构建一个可靠的短链系统

1,328 阅读6分钟

原始需求

最近接到一个需求:运营需要对某些H5页面发短信进行推广,但是一条短信会限制字数,所以需要想办法将H5链接的URL变得短一些。短信限制字符在70个以内,也就是说URL+运营话术的长度必须控制在70个字符以内,如果URL太长,则会挤占运营的话术空间,容易表达不清楚。

需求分析

简单看一眼,这就是要做一个短链服务。短链接是什么?就是访问一个短链接的时候自动跳转至某个配置好的地址。网上这样的短链接工具很多,比如短链接bitly这个工具,网址是bitly.com/,可以体验一下。 ​

关于短链接,网上的文章比较多,有些还涉及到比较复杂的短链接算法。但是我们这里的场景其实相对简单,用不了太复杂的技术。我们需要认真分析需求,不能生搬硬套别人的方案。这里我们的需求有两个特点: ​

1. 生成短链接的频率很低

因为是运营手动生成需要推广的短链接,即使全公司的运营一起生成短链,频率也很低,所以生成短链的性能要求很低。 ​

2. 短链生成的量很少

由于都是人去手工生成短链,那么生成的短链的总量肯定是很少的,所以我们的数据量也是比较小的。 ​

3. 短链接解析需要尽可能高性能

因为运营发一波短信下去,刚发的时候肯定是一个打开的高峰期,很快打开的量就会降下来,整个过程的量都集中在前面几分钟。所以短链接还原应该尽可能快速的响应,不应该消耗太多资源。

短链接系统设计

通过对上面的需求分析,frank做出了如下设计:

1. 存储设计

数据存储方面只使用Mysql,建立一张链接表,表只包含两个字段,一个是自增ID,另一个是长链接URL,给长链接URL加上唯一索引,确保同一个长链接不会生成两个短链接。 ​

2. 长链接如何变短

基于上面的存储设计,每一条长链接记录都会有一个唯一ID,用这个ID就能唯一定位到长链接。 唯一的问题在于这个ID可能稍微有些长,比如运营已经对10000条URL进行了推广,那么后面的ID就至少要占用5个字符了,怎么样优化优化?答案是把10进制的数字转化为62进制,为什么是62进制,26个大写字母 + 26个小写字母 + 10个数字 = 62。为什么不选其他自符?URL里的特殊字符都会重新编码,这62个字符不会。这样选的话能将字符缩短多少呢?先看下表:

62进制数对应10进制数
1位62进制最大数Z61
2位62进制最大数ZZ3843
3位62进制最大数ZZZ238327
4位62进制最大数ZZZZ14776335

从上表可以看出,对于23万条范围以内的ID都限制在3个字符的范围内,基本已经够运营用了。即使再多,预计运营需要的长链接也绝对不会超过1400万条,也就是说最多最多短链的ID也就用4个字符表示就足够了,相对于1000万的8个字符,只需要4个字符就够了,足足压缩了一倍。 ​

3. 进制转化

进制转化怎么做呢?首先有对应关系如下表:

10进制数62进制数
00
......
99
a10
......
z35
A36
......
Z61

按照上面的表先构建两个map: 一个将map将10进制转为62进制,叫map_to_62, 另一个map将62进制转为10进制,叫map_to_10。构建两个map的python代码如下:

import string
chars = string.digits + string.ascii_lowercase + string.ascii_uppercase
map_to_62 = dict(zip(range(62), chars))
map_to_10 = dict(zip(chars, range(62)))

10进制转62进制python实现代码如下:

def int_to_62chars(n):
    chars = []
    while True:
        c = map_to_62[n % 62] // 上面生成的10进制数字转62进制字符的map
        chars.append(c)
        n = n // 62
        if n == 0:
            break
    chars.reverse()
    return ''.join(chars)

62进制转10进制python实现代码如下:

def chars_to_10int(chars):
    rv = 0
    for c in chars:
        rv = rv * 62 + map_to_10[c] // 上面生成的62进制字符转10进制数字的map
    return rv

4.短链生成

短链生成接口的调用场景:运营在管理后台填上要转换的h5,调用短链生成接口,如果这条长链接没有人生成过短链,则往数据库插入一条记录,并通过其ID构造短链;若已经有人生成过,则用生成好的ID构建短链。 伪代码如下:

def generate_short_url(url):
    try:
        id = insert(url) // 尝试写入
    except DuplicateKeyError:
        id = find_by_url(url) // 这个错误说明唯一索引冲突了,也就是已经插入过了,直接查id即可
        
    short_id = int_to_62chars(id) // 上面实现的10进制数字转62进制字符的函数
    return short_domain + '/' + short_id

5.短链还原

用户点击短链接之后,服务端将短链ID转化为数据库的ID,再从mysql查出长链接,然后302重定向到长链接即可。伪代码如下:

def parse_short_url(short_id):
    id = chars_to_10int(short_id) // 前面实现的62进制字符串转10进制数字
    url = find(id) // 查数据库获取长链接
    redirect(url)   // 302重定向 

总结分析

优点

  1. 这个实现非常简单,依赖的东西非常少,只需要一个mysql,且数据库库表也很简单
  2. 短链还原的时候只需执行一次mysql查询,考虑到实际业务场景中,很多人会同时查一个短链,考虑到Mysql的内存缓存,Mysql的性能应该是非常好的。预估几千qps的查询量完全无压力。

缺点

  1. 查询量太大的话一台mysql不够(但是这一点其实大多情况下也遇不到)
  2. 由于是主键ID构造的短链,所以是可以猜出全部url的,但是这个其实是否重要要看场景,比如frank碰到的场景就无所谓。

总评

这个实现无疑是一个成本非常低的短链系统,但是对于大多数公司来说也完全够用了,我们在实际工作中一定要结合实际业务场景来设计系统,不能盲目投入资源做一个非常牛逼但不实用的系统。

旧文推荐

  1. 微信小程序又双叒叕改重要接口了!
  2. 微信标准版交易组件使用入门
  3. 如何利用小程序广告赚钱?

PS: 欢迎关注我的商城项目, Github, Gitee