• 简易短链接项目


    简介

    前面已经预告(MurmurHash算法初探)过了,现在也是完成了,但是确实时没时间写,只能抽空“填坑”了🥱

    关于这次从打算做到做完,应该是执行力最强的一次,仅仅不到一周时间就完成了,要是在过去,估计还要拖个几周甚至个把月。

    参考:

    高性能短链设计

    BloomFilter布隆过滤器使用

    说明

    关于这篇高性能短链设计(下面提到的文章都是这个),写的非常好,非常建议有兴趣的认认真真看完。看完了这篇文章,解答了我对现在出现频次越来越高的短链接的很多疑惑。

    本次做的简易短链接项目就是基于文章中说的哈希算法做的,使用的便是MurmurHash算法,并使用布隆过滤器优化。

    流程如下(图片来源于上面的文章)

    image

    要用到的工具:

    计算布隆过滤器大小

    在线进制转换

    数据库设计

    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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    文章中数据库是这样设计的,可以看到其中主键类型为 unsigned int,即无符号数,这样的话就可以与MurmurHash算法的32位对应上了,但是Java不支持无符号数,如果使用int/Integer类型就有可能会出现溢出情况(虽然可能性很小,因为毕竟至少要有 2 32 2^{32} 232条数据,也就是要有 2 32 2^{32} 232种链接,很难的啦),为了避免有可能发生的问题,我的数据库设计如下

    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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    id改为了bigint,加上了我习惯使用的三字段(validcreate_timeupdate_time),还有短链接的唯一索引。

    环境搭建

    主要的以来还是这个,不仅有必要的MurmurHash算法,还有布隆过滤器可直接使用(重点说明!!!这种布隆过滤器的使用是无效的,因为毕竟是内存布隆过滤器(重启即无效了),并不适用分布式场景,所以这样使用不怎么严谨)

    <dependency>
        <groupId>com.google.guavagroupId>
        <artifactId>guavaartifactId>
        <version>30.1-jreversion>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    其他的依赖我就不粘了。为了加快开发,使用了Mybatis-Plus-Generator快速生成代码,所以工作量就减少很多了,剩下的只是MurMurHash算法、BloomFilter、62进制转换这些了。

    代码

    接口

    /**
     * 

    * 前端控制器 *

    * * @author wnhyang * @since 2022-08-22 02:52:47 */
    @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

    一共两个接口,一个生成短链接,一个短链接重定向。

    核心代码

    /**
     * 

    * 服务实现类 *

    * * @author wnhyang * @since 2022-08-22 02:52:47 */
    @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); // 1、MurmurHash加密 HashCode hashCode = HASH_FUNCTION.hashString(url, StandardCharsets.UTF_8); // 2、短链 try { sUrl = Encoder62.encode62(hashCode.padToLong()); } catch (Exception e) { e.printStackTrace(); } log.info("sUrl:{}", sUrl); //3、布隆过滤器 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); } }
    • 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

    这里就是接口的业务实现

    • 重定向比较简单,通过短链接查询长链接,移除可能存在的自定义字符串(理论上有可能会误删,关键在于自定义字符串定义)。

    • 生成短链接流程如上图,对应代码看即可

    62进制转换

    /**
     * @author: wnhyang
     * @create: 2022-08-22 15:47
     **/
    public class Encoder62 {
    
        /**
         * BloomFilter预期插入次数
         * 1L << 16 << 16
         */
        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) {
            // if (num > SIZE) {
            //     throw new RuntimeException("num:" + 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();
        }
    }
    
    • 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

    这里说明一下,BloomFilter理论上应该设置 2 32 2^{32} 232,但是通过计算可知

    image

    需要3.6G内存,我第一次这样设置好,直接堆溢出,太可笑了😂

    所以我最后设置的是 2 16 2^{16} 216,还可以接受

    测试

    就两个接口,简单测试一下就好了,还是比较顺利的

    总结

    关于生成短链接,除了Hash算法还有自增序列算法等方法,文章中还给出了“高性能短链的架构设计”,我还不是太懂,可以后续回看一下😯

  • 相关阅读:
    C语言练习---【求素数】(一篇带你掌握素数求解)
    Linux 指令
    孙凝晖院士:集成芯片引领高性能计算革命
    基于SSM的超市管理系统
    libc和glibc有什么区别
    实战案例(MDL语句)
    MATLAB - excel 读取
    3.3.k8s搭建-rancher RKE2
    【Python】split()方法简介
    如何设计元宇宙展厅,元宇宙展厅的展示和交互形式有哪些?
  • 原文地址:https://blog.csdn.net/weixin_44783934/article/details/126506391