短链接的简单实现

很久没更新了,加入了创业公司,从无到有的构建一套产品,从一开始的码框架到现在思考如何优化产品架构,适应更多的弹性需求和更大的数据量。时间真的是不够用,几乎快半年没有更新博客。近期产品慢慢走上正轨,又有小伙伴加入,所以抽出点时间总结一下这小半年内遇到的坑和走过的路。

说一说最近做的一个短链服务,短链服务其实很简单,其实就是一张表或者说是一个map:
+——-+———————–+
| 短链 | 长链 |
+——-+———————–+
| 7Y65s | https://www.google.com |
+——-+———————–+

就是这样一个结构,当用户访问 your-domain/7Y65s, 去表里查询一下对应的长链,301到该链接就可以了。

优化

基本的思路确定后,我们一点一点来优化。

首先分析大量查询肯定会出现在通过短链找长链这段逻辑中,如果用数据库肯定影响效率,缓存势在必行,而且一旦短链生成基本是不会变的,所以也不存在失效问题(这里可能会有个批量Archive的处理)。在我的项目中,我选择了redis作为缓存,反正是key-value的其他缓存组件肯定也能做。选用redis的原因是还可以用INCR来统计访问量。

1
2
redisTemplate.opsForHash().put("short-urls:" + slug, "long_url", url);
redisTemplate.opsForHash().increment("short-urls:" + slug, VISITS_FIELD, 1L);

查询这一侧做完,到了生成短链这一块,这里实际上是刚刚说的那张表的create操作。这里有个基本逻辑是:当一个长链还没有短链的时候,我们生成它并返回,如果已经存在,直接获取返回。

生成的算法直接贴代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package io.naza.urlshortener.generator;

import org.apache.commons.codec.digest.DigestUtils;

public class UrlShortHelper {

private final static int LENGTH = 6;

private static char[] DIGITS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".toCharArray();

public static String[] shorten(String url) {
String key = "SECRET"; // 自定义生成MD5加密字符串前的混合KEY
String hex = DigestUtils.md5Hex(key + url);
int hexLen = hex.length();
int subHexLen = hexLen / 8;
String[] shortStr = new String[subHexLen];

for (int i = 0; i < subHexLen; i++) {
StringBuilder outChars = new StringBuilder();
int j = i + 1;
String subHex = hex.substring(i * 8, j * 8);
long idx = Long.valueOf("3FFFFFFF", 16) & Long.valueOf(subHex, 16);
for (int k = 0; k < LENGTH; k++) {
int index = (int) (Long.valueOf("0000003D", 16) & idx);
outChars.append(DIGITS[index]);
idx = idx >> 5;
}
shortStr[i] = outChars.toString();
}
return shortStr;
}

}

项目中按照长链生成一个固定长度为6位的短链接,只要长链是一样的,那么每次生成的短链也一样。

考虑到每次生成短链前要查询一下长链是不是存在,继续加一个小缓存,这次反过来,长链作key,短链做value。

1
redisTemplate.opsForHash().put("short-urls", url, slug);

尾声

用比较简单的方式完成一个短链接服务,在做短链跳转的过程中可以分析用户的各类行为,比如操作系统,浏览器版本,地域等等。最后说两点要注意的:

  1. redis毕竟是作为缓存使用的,建议数据还是要落一次在DB,比如在生成短链时,写入到DB一份。这样当redis挂了,还可以从DB中全量恢复。
  2. 对于长时间没有PV的短链,比如超过1年没有PV,需要做批量清理,一般一个月一次就可以了。