Redisson读写锁和分布式锁的项目实践

[复制链接]
发表于 2025-8-7 06:38:39 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
办理方案:采用读写锁

什么是读写锁

   Redisson读写锁是一种基于Redis实现特殊的机制,用于在分布式系统中协调对共享资源的访问,其继续了Java中的ReentrantReadWriteLock的思想.特别适用于读多写少的场景.其焦点是:允很多个线程同时读取共享资源,但写操纵必须占用资源.从而包管线程安全的同时进步并发性能
  

  • 十分得当短链更新时:当某个线程必要更新资源时→必要获取写锁.此时所有的读操纵和其他写线程会被壅闭,包管数据的一致性
焦点原理

读锁(共享锁):



  • 共享性:允很多个线程同时持有读锁
  • 互斥性:只要存在读锁,该线程就不能获取写锁
写锁(排他锁):



  • 独占性:同一时刻只能占有一个线程持有写锁
  • 互斥性:当一个线程获取了写锁,其他线程就无法同时获取写锁和读锁.写锁占用线程修改共享资源,确保了在修改时没有其他线程访问
根本代码结构如下:

  1. RReadWriteLock configLock = redisson.getReadWriteLock("configLock");
  2. // 读配置
  3. configLock.readLock().lock();
  4. try {
  5.     return loadConfigFromCache();
  6. } finally {
  7.     configLock.readLock().unlock();
  8. }
  9. // 写配置
  10. configLock.writeLock().lock();
  11. try {
  12.     updateConfigInDB();
  13.     refreshCache();
  14. } finally {
  15.     configLock.writeLock().unlock();
  16. }
复制代码
分布式锁Redisson



  • 为防止缓存击穿→大量并发哀求同时查询一个失效的缓存,导致数据库压力骤增
  • 通过 Redisson 的 RLock(分布式锁)确保同一时刻只有一个线程实行数据库查询操纵
双重检查锁→重修缓存

双重检查流程


  • 第一次检查-无锁:

    • 未在代码中显式体现:通常在实际业务中,外层会先尝试从缓存读取数据。假如缓存掷中,直接返回数据,无需加锁。此处代码直接处理未掷中场景,可能外层已进行第一次检查。
    • 假设场景:当缓存未掷中时,哀求进入加锁逻辑。

  • 加锁后第二次检查-关键:

    • 在获取分布式锁后,再次检查缓存-stringRedisTemplate.opsForValue().get。
    • 目的:确保在期待锁的过程中,其他线程可能已经更新了缓存,制止重复查询数据库。

缓存穿透和缓存击穿办理方法



  • 流程图如下:




  • 布隆过滤器+缓存空值+redisson锁
  • 缓存空值的操纵对布隆过滤器误判操纵进行保护→防止穿透
  • redisson锁操纵对大量空值同时过期操纵进行保护→防止击穿
  1. RLock lock = redissonClient.getLock(String.format(LOCK_SHORT_LINK_GOTO_KEY, fullShortUrl));
  2.         // 加锁
  3.         lock.lock();
  4.         try {
  5.             // 再次查询缓存
  6.             String originalUrl = stringRedisTemplate.opsForValue().get(String.format(GOTO_SHORT_LINK_KEY, fullShortUrl));
  7.             // 如果缓存中不存在,则查询数据库
  8.             if (StrUtil.isBlank(originalUrl)) {
  9.                 //查询goto表
  10.                 LambdaQueryWrapper<ShortLinkGotoDO> linkGotoQueryWrapperqueryWrapper = Wrappers.lambdaQuery(ShortLinkGotoDO.class)
  11.                         .eq(ShortLinkGotoDO::getFullShortUrl, fullShortUrl);
  12.                 ShortLinkGotoDO shortLinkGotoDO = shortLinkGotoMapper.selectOne(linkGotoQueryWrapperqueryWrapper);
  13.                 if (shortLinkGotoDO == null) {
  14.                     //进行风控解决缓存穿透问题->设为空值
  15.                     stringRedisTemplate.opsForValue().set(String.format(SHORT_LINK_IS_NULL_KEY, fullShortUrl), "-", 30, TimeUnit.MINUTES);
  16.                     resp.sendRedirect("/page/notfound");
  17.                     return;
  18.                 }
  19.                 //查询短链接表获取originUrl
  20.                 LambdaQueryWrapper<ShortLinkDO> queryWrapper = Wrappers.lambdaQuery(ShortLinkDO.class)
  21.                         .eq(ShortLinkDO::getGid, shortLinkGotoDO.getGid())
  22.                         .eq(ShortLinkDO::getFullShortUrl, fullShortUrl)
  23.                         .eq(ShortLinkDO::getEnableStatus, 0);
  24.                 ShortLinkDO shortLinkDO = baseMapper.selectOne(queryWrapper);
  25.                 //数据库中不存在或者短链接已经过期则跳转404页面
  26.                 if (shortLinkDO == null || (shortLinkDO.getValidDate() != null && shortLinkDO.getValidDate().before(new Date()))) {
  27.                     //短链接已经过期
  28.                     stringRedisTemplate.opsForValue().set(String.format(SHORT_LINK_IS_NULL_KEY, fullShortUrl), "-", 30, TimeUnit.MINUTES);
  29.                     resp.sendRedirect("/page/notfound");
  30.                     return;
  31.                 }
  32.                 // 缓存原始链接
  33.                 stringRedisTemplate.opsForValue().set(String.format(GOTO_SHORT_LINK_KEY, fullShortUrl), shortLinkDO.getOriginUrl());
  34.                 //预热缓存
  35.                 stringRedisTemplate.opsForValue().set(
  36.                         String.format(GOTO_SHORT_LINK_KEY, fullShortUrl),
  37.                         shortLinkDO.getOriginUrl(),
  38.                         LinkUtil.getShortLintCacheValidDate(shortLinkDO.getValidDate()),
  39.                         TimeUnit.MILLISECONDS);
  40.                 // 访问统计
  41.                 shortLinkAccessStats(fullShortUrl, shortLinkDO.getGid(), req, resp);
  42.                 // 跳转
  43.                 resp.sendRedirect(shortLinkDO.getOriginUrl());
  44.             }
  45.         } finally {
  46.             lock. Unlock();
复制代码
 
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表