很久没更新了,加入了创业公司,从无到有的构建一套产品,从一开始的码框架到现在思考如何优化产品架构,适应更多的弹性需求和更大的数据量。时间真的是不够用,几乎快半年没有更新博客。近期产品慢慢走上正轨,又有小伙伴加入,所以抽出点时间总结一下这小半年内遇到的坑和走过的路。
说一说最近做的一个短链服务,短链服务其实很简单,其实就是一张表或者说是一个map:
+——-+———————–+
| 短链 | 长链 |
+——-+———————–+
| 7Y65s | https://www.google.com |
+——-+———————–+
就是这样一个结构,当用户访问 your-domain/7Y65s, 去表里查询一下对应的长链,301到该链接就可以了。
优化
基本的思路确定后,我们一点一点来优化。
首先分析大量查询肯定会出现在通过短链找长链这段逻辑中,如果用数据库肯定影响效率,缓存势在必行,而且一旦短链生成基本是不会变的,所以也不存在失效问题(这里可能会有个批量Archive的处理)。在我的项目中,我选择了redis作为缓存,反正是key-value的其他缓存组件肯定也能做。选用redis的原因是还可以用INCR来统计访问量。
redisTemplate.opsForHash().put("short-urls:" + slug, "long_url", url); redisTemplate.opsForHash().increment("short-urls:" + slug, VISITS_FIELD, 1L);
查询这一侧做完,到了生成短链这一块,这里实际上是刚刚说的那张表的create操作。这里有个基本逻辑是:当一个长链还没有短链的时候,我们生成它并返回,如果已经存在,直接获取返回。
生成的算法直接贴代码:
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。
redisTemplate.opsForHash().put("short-urls", url, slug);
尾声
用比较简单的方式完成一个短链接服务,在做短链跳转的过程中可以分析用户的各类行为,比如操作系统,浏览器版本,地域等等。最后说两点要注意的:
redis毕竟是作为缓存使用的,建议数据还是要落一次在DB,比如在生成短链时,写入到DB一份。这样当redis挂了,还可以从DB中全量恢复。
对于长时间没有PV的短链,比如超过1年没有PV,需要做批量清理,一般一个月一次就可以了。