短链接跳转(缓存穿透)

短链接跳转(缓存穿透)

功能概述

短链接跳转功能通过优化缓存策略,结合布隆过滤器、空值缓存和分布式锁,有效解决 缓存穿透缓存击穿 问题。在高并发场景下确保系统的稳定性和性能。


功能流程

  1. 生成完整短链接 URL

    • 根据请求的 shortUriserverName 拼接生成完整短链接 URL(fullShortUrl)。
  2. 查询缓存

    • 在 Redis 缓存中检查短链接是否存在。
    • 若存在,直接返回原始链接进行重定向。
  3. 布隆过滤器校验

    • 通过布隆过滤器快速判断短链接是否有效。
    • 若不存在,直接返回无效状态,避免访问数据库。
  4. 查询空值缓存

    • 检查 Redis 是否已缓存空值(标记无效短链接)。
    • 若已缓存空值,直接返回无效状态,避免重复查询。
  5. 处理缓存击穿

    • 若缓存和布隆过滤器均未命中,使用分布式锁(如 Redisson)防止并发查询。
    • 获取锁后,再次检查缓存是否已更新。
  6. 查询数据库

    • 在数据库中查询完整短链接的原始链接,并验证其状态(如未删除、已启用)。
    • 若查询成功,将结果写入缓存并返回。
    • 若查询失败,缓存空值,减少无效查询。
  7. 释放锁

    • 无论查询成功或失败,均释放分布式锁,避免资源浪费。

缓存穿透与缓存击穿的解决方案

缓存穿透

  • 问题:查询不存在的短链接,每次都穿透缓存,直接访问数据库。
  • 解决方案
    • 使用布隆过滤器快速判断短链接是否存在。
    • 对无效短链接缓存空值并设置过期时间,避免重复查询。

缓存击穿

  • 问题:热点数据缓存失效导致大量并发请求直接访问数据库。
  • 解决方案
    • 使用分布式锁(如 Redisson)控制并发查询,防止重复访问数据库。
    • 重新加载热点数据到缓存,保证后续请求直接命中缓存。

关键代码逻辑

生成完整短链接

1
2
String serverName = request.getServerName();
String fullShortUrl = serverName + "/" + shortUri;

查询缓存

1
2
3
4
5
String originalLink = stringRedisTemplate.opsForValue().get(String.format(GOTO_SHORT_LINK_KEY, fullShortUrl));
if (StrUtil.isNotBlank(originalLink)) {
((HttpServletResponse) response).sendRedirect(originalLink);
return;
}

布隆过滤器校验

1
2
3
4
boolean contains = shortUriCreateCachePenetrationBloomFilter.contains(fullShortUrl);
if (!contains) {
return; // 布隆过滤器判定短链接不存在
}

查询空值缓存

1
2
3
4
String gotoIsNullShortLink = stringRedisTemplate.opsForValue().get(String.format(GOTO_SHORT_IS_NULL_LINK_KEY, fullShortUrl));
if (StrUtil.isNotBlank(gotoIsNullShortLink)) {
return; // 缓存中已标记为空值
}

分布式锁防止缓存击穿

1
2
3
4
5
6
7
8
9
10
11
12
RLock lock = redissonClient.getLock(String.format("LOCK_GOTO_SHORT_LINK_KEY", fullShortUrl));
lock.lock();
try {
originalLink = stringRedisTemplate.opsForValue().get(String.format(GOTO_SHORT_LINK_KEY, fullShortUrl));
if (StrUtil.isNotBlank(originalLink)) {
((HttpServletResponse) response).sendRedirect(originalLink);
return;
}
// 数据库查询逻辑
} finally {
lock.unlock();
}

查询数据库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
LambdaQueryWrapper<ShortLinkDO> queryWrapper = Wrappers.lambdaQuery(ShortLinkDO.class)
.eq(ShortLinkDO::getGid, shortLinkGotoDO.getGid())
.eq(ShortLinkDO::getFullShortUrl, fullShortUrl)
.eq(ShortLinkDO::getDelFlag, 0)
.eq(ShortLinkDO::getEnableStatus, 0);

ShortLinkDO shortLinkDO = baseMapper.selectOne(queryWrapper);

if (shortLinkDO != null) {
stringRedisTemplate.opsForValue().set(GOTO_SHORT_LINK_KEY, shortLinkDO.getOriginUrl());
((HttpServletResponse) response).sendRedirect(shortLinkDO.getOriginUrl());
} else {
stringRedisTemplate.opsForValue().set(String.format(GOTO_SHORT_IS_NULL_LINK_KEY, fullShortUrl), "-", 30, TimeUnit.MINUTES);
}

优点

  1. 高效缓存策略

    • 使用 Redis 加速短链接跳转。
    • 空值缓存减少无效数据库查询。
  2. 防止缓存穿透

    • 使用布隆过滤器避免无效查询。
    • 缓存空值,减少重复查询。
  3. 防止缓存击穿

    • 分布式锁控制并发查询,避免缓存失效对数据库的冲击。
  4. 高并发支持

    • 结合 Redis 和 Redisson 提供高并发性能支持。

示例

有效短链接跳转

  • 输入短链接:http://shortlink.com/abc123
  • 重定向到原始链接:http://example.com/page

无效短链接处理

  • 输入短链接:http://shortlink.com/invalid
  • 布隆过滤器判定不存在,或缓存空值直接返回无效状态。

总结

通过 布隆过滤器空值缓存分布式锁 的组合,系统有效解决了缓存穿透与缓存击穿问题,确保短链接跳转功能在高并发场景下的稳定性和性能,特别适用于大规模短链接服务。