简易短链接项目

简介

前面已经预告(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;

文章中数据库是这样设计的,可以看到其中主键类型为 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;

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

环境搭建

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

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>30.1-jre</version>
</dependency>

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

代码

接口

/**
 * <p>
 * 前端控制器
 * </p>
 *
 * @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());
    }
}

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

核心代码

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @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);
    }
}

这里就是接口的业务实现

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

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

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();
    }
}

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

image

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

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

测试

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

总结

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

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

无奈何杨

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值