简介
前面已经预告(MurmurHash算法初探)过了,现在也是完成了,但是确实时没时间写,只能抽空“填坑”了🥱
关于这次从打算做到做完,应该是执行力最强的一次,仅仅不到一周时间就完成了,要是在过去,估计还要拖个几周甚至个把月。
参考:
高性能短链设计
BloomFilter布隆过滤器使用
说明
关于这篇高性能短链设计(下面提到的文章都是这个),写的非常好,非常建议有兴趣的认认真真看完。看完了这篇文章,解答了我对现在出现频次越来越高的短链接的很多疑惑。
本次做的简易短链接项目就是基于文章中说的哈希算法做的,使用的便是MurmurHash
算法,并使用布隆过滤器优化。
流程如下(图片来源于上面的文章)
要用到的工具:
计算布隆过滤器大小
在线进制转换
数据库设计
1 2 3 4 5 6 7
| CREATE TABLE `short_url_map` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `lurl` varchar(160) DEFAULT NULL COMMENT '长地址', `surl` varchar(10) DEFAULT NULL COMMENT '短地址', `gmt_create` int(11) DEFAULT NULL COMMENT '创建时间', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
文章中数据库是这样设计的,可以看到其中主键类型为
unsigned int
,即无符号数,这样的话就可以与MurmurHash
算法的32
位对应上了,但是Java
不支持无符号数,如果使用int/Integer
类型就有可能会出现溢出情况(虽然可能性很小,因为毕竟至少要有\(2^{32}\)条数据,也就是要有\(2^{32}\)种链接,很难的啦),为了避免有可能发生的问题,我的数据库设计如下
1 2 3 4 5 6 7 8 9 10
| CREATE TABLE `url_map` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键id', `surl` varchar(10) NOT NULL COMMENT '短链接', `lurl` varchar(160) NOT NULL COMMENT '长链接', `valid` int(1) NOT NULL DEFAULT '0' COMMENT '是否有效', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), UNIQUE KEY `uk_surl` (`surl`) USING BTREE COMMENT '短链接唯一索引' ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
|
把id
改为了bigint
,加上了我习惯使用的三字段(valid
、create_time
、update_time
),还有短链接的唯一索引。
环境搭建
主要的依赖还是这个,不仅有必要的MurmurHash
算法,还有布隆过滤器可直接使用(重点说明!!!这种布隆过滤器的使用是无效的,因为毕竟是内存布隆过滤器(重启即无效了),并不适用分布式场景,所以这样使用不怎么严谨)
1 2 3 4 5
| <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>30.1-jre</version> </dependency>
|
其他的依赖我就不粘了。为了加快开发,使用了Mybatis-Plus-Generator快速生成代码,所以工作量就减少很多了,剩下的只是MurMurHash
算法、BloomFilter
、62进制转换这些了。
代码
接口
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
|
@Controller @RequestMapping("/urlMap") @Slf4j public class UrlMapController {
private static final String REDIRECT = "redirect:";
@Autowired private UrlMapService urlMapService;
@GetMapping("/{sUrl}") public String redirect(@PathVariable("sUrl") String sUrl, HttpServletResponse response) { log.info("重定向{}", sUrl); return REDIRECT + urlMapService.redirect(sUrl); }
@PostMapping @ResponseBody public R<String> urlMap(@RequestBody Url url) { log.info("生成短链接{}", url); return urlMapService.urlMap(url.getUrl()); } }
|
一共两个接口,一个生成短链接,一个短链接重定向。
核心代码
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 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
|
@Service @Slf4j public class UrlMapServiceImpl extends ServiceImpl<UrlMapMapper, UrlMap> implements UrlMapService {
@Autowired private UrlMapMapper urlMapMapper;
private static final HashFunction HASH_FUNCTION = Hashing.murmur3_32();
private static final BloomFilter<String> BLOOM_FILTER = BloomFilter.create(Funnels.stringFunnel(Charset.defaultCharset()), Encoder62.SIZE);
private static final String STR = "ZRCTGD";
@Override public String redirect(String sUrl) { UrlMap urlMap = urlMapMapper.selectOne(new QueryWrapper<UrlMap>().eq("surl", sUrl)); return urlMap.getLurl().replaceAll(STR, ""); }
@Override @Transactional(rollbackFor = Exception.class) public R<String> urlMap(String url) { String sUrl = ""; while (true) { log.info("url:{}", url); HashCode hashCode = HASH_FUNCTION.hashString(url, StandardCharsets.UTF_8);
try { sUrl = Encoder62.encode62(hashCode.padToLong()); } catch (Exception e) { e.printStackTrace(); } log.info("sUrl:{}", sUrl);
boolean contain = BLOOM_FILTER.mightContain(sUrl);
UrlMap urlMap = new UrlMap(); urlMap.setSurl(sUrl); urlMap.setLurl(url); if (contain) { url += STR; log.info("contain,new url:{}", url); } else { urlMapMapper.insert(urlMap); BLOOM_FILTER.put(sUrl); log.info("not contain,put({})", sUrl); break; } } return R.ok(sUrl); } }
|
这里就是接口的业务实现
62进制转换
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
|
public class Encoder62 {
public static final long SIZE = 1L << 16;
public static final int SCALE = 62;
private static final char[] ARRAYS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();
public static String encode62(long num) { StringBuilder sb = new StringBuilder();
int remain = 0; while (num > 0) { remain = (int) (num % SCALE); sb.append(ARRAYS[remain]); num /= SCALE; }
return sb.reverse().toString(); } }
|
这里说明一下,BloomFilter
理论上应该设置\(2^{32}\),但是通过计算可知
需要3.6G内存,我第一次这样设置好,直接堆溢出,太可笑了😂
所以我最后设置的是\(2^{16}\),还可以接受
测试
就两个接口,简单测试一下就好了,还是比较顺利的
总结
关于生成短链接,除了Hash算法还有自增序列算法等方法,文章中还给出了“高性能短链的架构设计”,我还不是太懂,可以后续回看一下😯