1 背景

前段时间收到极客时间的短信,对里头的短网址产生了兴趣。
本人在随手有开发过一个文件网关,它的职责是透明分布式文件存储实现,产生一个可以在公网访问的链接。但这个链接的问题有两个问题:
第一是太长了,不适合对文字长度敏感的场景,比如短信,按字数收费;
第二是不安全,把分布式ID直接暴露在链接里,有被暴力迭代的隐患;
假以短网址系统,将解决以上两个问题。

2 短网址系统

各类业务中普遍存在长网址,而且增量多,并发高。如果要生成对应的短网址,那么短网址系统的性能必须足够优秀。
生成短网址主要两个方案:哈希方案、自增序列方案。

2.1 哈希方案

短网址 = toBase62(MurmurHash(长网址))
该方案的关键是 哈希函数的选择 以及 哈希冲突的处理。
哈希函数的选择,虽然已经有 MD5、SHA1 等加密型哈希函数,但 MurmurHash 表现出更快的的运算速度(10倍),更均匀的分布特征,当之无愧拔得头筹。
解决哈希冲突,有两个途径:

  1. 在原有长网址的末尾添加1~N个自定义字符串,一直尝试到没有冲突为止;
  2. 洗牌算法,打乱原有的值,一直洗牌到没有冲突为止;

以上两个途径都要求多次尝试,直至冲突解决,那么这个查重的算法就很重要。对于低并发场景,最直接就是利用数据库索引保持唯一性,对于高并发场景,需要引入布隆过滤器。

2.2 自增序列方案

短网址 = toBase62(自增序列发号器(长网址))
该方案的实现有4个:UUID、Snowflake、Redis、MySql发号表。
前两个方案都是算法实现,其中UUID随机无序,不是一个真正意义上的自增序列,在入库时添加索引会引起页分裂。
Snowflake 在随手有现成的实现,针对时钟回拨问题有预案,详见参考6。
Redis 实现我不喜欢,除了要做额外的持久化以外,每次发号必定产生一次网络IO,Redis响应再快,也没有CPU运算快。
MySql 发号表天然持久化,虽然有网络IO,但只要申请一次,号段内的发号均为ID自增操作,比算法实现更快。缺点是发了1000万个号段后原则上要分表分库(实际上随手的用户表5千万行没做分表也没问题),但仔细想想,发号表几乎没有查询场景,所以这个缺点很小。

3 总结

Snowflake在解决了时钟回拨问题后,可以保证不产生重复ID,不需要持久化,不需要布隆过滤器,是最理想的短网址系统方案。
参考中提到的 OpenResty,从原理来看减少了开发成本、维护成本,同时提升了性能,像短网址系统这种一次性买卖,可以考虑上 OpenResty。

Reference