侧边栏壁纸
博主头像
木易成

聊聊软件、聊聊金融、聊聊篮球

  • 累计撰写 29 篇文章
  • 累计创建 18 个标签
  • 累计收到 3 条评论

目 录CONTENT

文章目录

短链服务实现逻辑

木易成
2022-12-15 / 0 评论 / 0 点赞 / 2,912 阅读 / 1,159 字

引言

当运营人员想给用户发送一条活动短信,需要附上一个跳转链接。此时开发给到运营的地址是www.xxxxx.com/redict?type=xxx&dict=xxxx&position=1,运营把这个连接在短信里一复制发现字数超了。需要怎么解决?

此时短链就派上了用场,我们可以把上面那么长的连接转化成:www.xxxxx.com/4df12,方便运营可以放更多的文案内容;

实现方式

方式一

可以通过购买成熟的产品,比如百度云的,当然这种方式你需要花钱

方式二

自己实现一个短链服务,主要介绍下这种方式的实现逻辑:

  1. 将长链使用 MurmurHash 算法将原始长链接 hash 为 32 位散列值,将散列值转为 62 进制字符串,最长6个字符
  2. 随着短链数量的增多,可能发生hash碰撞几率增加即不同的长链生成了同一个短链,需要采用布隆过滤器来做一个高性能的过滤。单机版的服务可以采用hutool提供的BitMapBloomFilter;集群版可以采用redisson的RBloomFilter;
  3. 转换成功的需要放到redis,提供高性能访问;

技术选型逻辑:

  1. MurmurHash:长链转短链自然需要一个哈希算法,应用的类型决定了我们并不需要解密,而是关心运算速度和冲突概率,MurmurHash 就是一种非加密型哈希算法,与 MD5、SHA 等常见哈希函数相比,性能与随机分布特征都要更佳。MurmurHash 有 32 bit、64 bit、128 bit 的实现,32 bit 已经足够表示近 43 亿个短链接。
  2. base62:MurmurHash 生成的哈希值最长有 10 位十进制数,为了进一步缩短短链接长度,可以将哈希值转为 62 进制,最长为 6 个字符。

代码

已Springboot项目为例

  1. pom依赖中引入redisson
	<dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson-spring-boot-starter</artifactId>
            <version>3.15.6</version>
        </dependency>
  1. 将布隆过滤器添加到容器中
@Configuration
public class BloomFilterConfig {

    @Bean
    public RBloomFilter dwzBloomFilter(RedissonClient redissonClient){
        RBloomFilter bloomFilter = redissonClient.getBloomFilter("dwz");
        bloomFilter.tryInit(100000000L, 0.03);
        return bloomFilter;
    }
}
  1. 长连接转换成base62
public class HashUtils {
	private static char[] CHARS = new char[]{
			'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
			'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
			'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'
	};
	private static int SIZE = CHARS.length;

	private static String convertDecToBase62(long num) {
		StringBuilder sb = new StringBuilder();
		while (num > 0) {
			int i = (int) (num % SIZE);
			sb.append(CHARS[i]);
			num /= SIZE;
		}
		return sb.reverse().toString();
	}

	public static String hashToBase62(String str) {
		int i = MurmurHash.hash32(str);
		long num = i < 0 ? Integer.MAX_VALUE - (long) i : i;
		return convertDecToBase62(num);
	}
}
  1. 短链生成
String shortUrl = HashUtils.hashToBase62(longURL)
  1. 生成短链链接的处理逻辑
public String saveUrlMap(String shortURL, String longURL, String originalURL) {
		//保留长度为1的短链接
		if (shortURL.length() == 1) {
			longURL += DUPLICATE;
			shortURL = saveUrlMap(HashUtils.hashToBase62(longURL), longURL, originalURL);
		}
		//在布隆过滤器中查找是否存在
		else if (dwzBloomFilter.contains(shortURL)) {
			//存在,从Redis中查找是否有缓存
			String redisLongURL = redisTemplate.opsForValue().get(shortURL);
			if (redisLongURL != null && originalURL.equals(redisLongURL)) {
				//Redis有缓存,重置过期时间
				redisTemplate.expire(shortURL, TIMEOUT, TimeUnit.MINUTES);
				return shortURL;
			}
			//没有缓存,在长链接后加上指定字符串,重新hash
			longURL += DUPLICATE;
			shortURL = saveUrlMap(HashUtils.hashToBase62(longURL), longURL, originalURL);
		} else {
			//不存在,直接存入数据库
			try {
				urlMapper.saveUrlMap(new UrlMap(shortURL, originalURL));
				dwzBloomFilter.add(shortURL);
				//添加缓存
				redisTemplate.opsForValue().set(shortURL, originalURL, TIMEOUT, TimeUnit.MINUTES);
			} catch (Exception e) {
				if (e instanceof DuplicateKeyException) {
					//数据库已经存在此短链接,则可能是布隆过滤器误判,在长链接后加上指定字符串,重新hash
					longURL += DUPLICATE;
					shortURL = saveUrlMap(HashUtils.hashToBase62(longURL), longURL, originalURL);
				} else {
					throw e;
				}
			}
		}
		return shortURL;
	}

总结

借助布隆过滤器和redis实现高性能的短链服务,用户访问www.xxxxx.com/4df12短链服务通过"redirect:" + longURL实现跳转到真正的服务;

0

评论区