问题1:在修改分组时,有短链接正在访问会出现什么问题?怎么解决

  • 假设:现有线程A正在修改短链a的分组gid1为gid2(还未修改成功)
  • 同时有一个线程B获取了短链a分组gid1,要进行统计pv,uv,uip操作时.发现gid1已经不存在了,就会发生并发问题

解决方案:采用读写锁

什么是读写锁

Redisson读写锁是一种基于Redis实现特殊的机制,用于在分布式系统中协调对共享资源的访问,其继承了Java中的ReentrantReadWriteLock的思想.特别适用于读多写少的场景.其核心是:允许多个线程同时读取共享资源,但写操作必须占用资源.从而保证线程安全的同时提高并发性能

  • 十分适合短链更新时:当某个线程需要更新资源时→需要获取写锁.此时所有的读操作和其他写线程会被阻塞,保证数据的一致性

核心原理

读锁(共享锁):

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

写锁(排他锁):

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

基本代码结构如下:

RReadWriteLock configLock = redisson.getReadWriteLock("configLock");
// 读配置
configLock.readLock().lock();
try {
return loadConfigFromCache();
} finally {
configLock.readLock().unlock();
}
// 写配置
configLock.writeLock().lock();
try {
updateConfigInDB();
refreshCache();
} finally {
configLock.writeLock().unlock();
}

分布式锁Redisson

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

双重检查锁→重建缓存

双重检查流程

  1. 第一次检查-无锁

    • 未在代码中显式体现:通常在实际业务中,外层会先尝试从缓存读取数据。如果缓存命中,直接返回数据,无需加锁。此处代码直接处理未命中场景,可能外层已进行第一次检查。
    • 假设场景:当缓存未命中时,请求进入加锁逻辑。
  2. 加锁后第二次检查-关键
    • 在获取分布式锁后,再次检查缓存-stringRedisTemplate.opsForValue().get
    • 目的:确保在等待锁的过程中,其他线程可能已经更新了缓存,避免重复查询数据库。

缓存穿透和缓存击穿解决方法

  • 流程图如下:

  • 布隆过滤器+缓存空值+redisson锁
  • 缓存空值的操作对布隆过滤器误判操作进行保护→防止穿透
  • redisson锁操作对大量空值同时过期操作进行保护→防止击穿
RLock lock = redissonClient.getLock(String.format(LOCK_SHORT_LINK_GOTO_KEY, fullShortUrl));
// 加锁
lock.lock();
try {
// 再次查询缓存
String originalUrl = stringRedisTemplate.opsForValue().get(String.format(GOTO_SHORT_LINK_KEY, fullShortUrl));
// 如果缓存中不存在,则查询数据库
if (StrUtil.isBlank(originalUrl)) { //查询goto表
LambdaQueryWrapper<ShortLinkGotoDO> linkGotoQueryWrapperqueryWrapper = Wrappers.lambdaQuery(ShortLinkGotoDO.class)
.eq(ShortLinkGotoDO::getFullShortUrl, fullShortUrl); ShortLinkGotoDO shortLinkGotoDO = shortLinkGotoMapper.selectOne(linkGotoQueryWrapperqueryWrapper); if (shortLinkGotoDO == null) {
//进行风控解决缓存穿透问题->设为空值
stringRedisTemplate.opsForValue().set(String.format(SHORT_LINK_IS_NULL_KEY, fullShortUrl), "-", 30, TimeUnit.MINUTES);
resp.sendRedirect("/page/notfound");
return;
} //查询短链接表获取originUrl
LambdaQueryWrapper<ShortLinkDO> queryWrapper = Wrappers.lambdaQuery(ShortLinkDO.class)
.eq(ShortLinkDO::getGid, shortLinkGotoDO.getGid())
.eq(ShortLinkDO::getFullShortUrl, fullShortUrl)
.eq(ShortLinkDO::getEnableStatus, 0); ShortLinkDO shortLinkDO = baseMapper.selectOne(queryWrapper); //数据库中不存在或者短链接已经过期则跳转404页面
if (shortLinkDO == null || (shortLinkDO.getValidDate() != null && shortLinkDO.getValidDate().before(new Date()))) {
//短链接已经过期
stringRedisTemplate.opsForValue().set(String.format(SHORT_LINK_IS_NULL_KEY, fullShortUrl), "-", 30, TimeUnit.MINUTES);
resp.sendRedirect("/page/notfound");
return;
} // 缓存原始链接
stringRedisTemplate.opsForValue().set(String.format(GOTO_SHORT_LINK_KEY, fullShortUrl), shortLinkDO.getOriginUrl()); //预热缓存
stringRedisTemplate.opsForValue().set(
String.format(GOTO_SHORT_LINK_KEY, fullShortUrl),
shortLinkDO.getOriginUrl(),
LinkUtil.getShortLintCacheValidDate(shortLinkDO.getValidDate()),
TimeUnit.MILLISECONDS); // 访问统计
shortLinkAccessStats(fullShortUrl, shortLinkDO.getGid(), req, resp);
// 跳转
resp.sendRedirect(shortLinkDO.getOriginUrl());
}
} finally {
lock. Unlock();

Redisson读写锁和分布式锁的项目实践的更多相关文章

  1. Redisson实现分布式锁(3)—项目落地实现

    Redisson实现分布式锁(3)-项目落地实现 有关Redisson实现分布式锁前面写了两篇博客作为该项目落地的铺垫. 1.Redisson实现分布式锁(1)---原理 2.Redisson实现分布 ...

  2. 二、多线程基础-乐观锁_悲观锁_重入锁_读写锁_CAS无锁机制_自旋锁

    1.10乐观锁_悲观锁_重入锁_读写锁_CAS无锁机制_自旋锁1)乐观锁:就像它的名字一样,对于并发间操作产生的线程安全问题持乐观状态,乐观锁认为竞争不总是会发生,因此它不需要持有锁,将 比较-设置 ...

  3. 通俗易懂 悲观锁、乐观锁、可重入锁、自旋锁、偏向锁、轻量/重量级锁、读写锁、各种锁及其Java实现!

    网上关于Java中锁的话题可以说资料相当丰富,但相关内容总感觉是一大串术语的罗列,让人云里雾里,读完就忘.本文希望能为Java新人做一篇通俗易懂的整合,旨在消除对各种各样锁的术语的恐惧感,对每种锁的底 ...

  4. AQS系列(四)- ReentrantReadWriteLock读写锁的释放锁

    前言 继续JUC包中ReentrantReadWriteLock的学习,今天学习释放锁. 一.写锁释放锁 入口方法 public void unlock() { sync.release(1); } ...

  5. 写文章 通俗易懂 悲观锁、乐观锁、可重入锁、自旋锁、偏向锁、轻量/重量级锁、读写锁、各种锁及其Java实现!

    网上关于Java中锁的话题可以说资料相当丰富,但相关内容总感觉是一大串术语的罗列,让人云里雾里,读完就忘.本文希望能为Java新人做一篇通俗易懂的整合,旨在消除对各种各样锁的术语的恐惧感,对每种锁的底 ...

  6. Redisson源码解读-分布式锁

    前言 Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid).Redisson有一样功能是可重入的分布式锁.本文来讨论一下这个功能的特点以及源 ...

  7. spring boot 利用redisson实现redis的分布式锁

    原文:http://liaoke0123.iteye.com/blog/2375469 利用redis实现分布式锁,网上搜索的大部分是使用java jedis实现的. redis官方推荐的分布式锁实现 ...

  8. JAVA锁机制-可重入锁,可中断锁,公平锁,读写锁,自旋锁,

    如果需要查看具体的synchronized和lock的实现原理,请参考:解决多线程安全问题-无非两个方法synchronized和lock 具体原理(百度) 在并发编程中,经常遇到多个线程访问同一个 ...

  9. 多线程编程之Apue3rd_Chapter11之互斥锁_读写锁_自旋锁

    学习了apue3rd的第11章,主要讲的是多线程编程.因为线程共享进程的资源比如堆和全局变量,多线程编程最重要的是,使用各种锁进行线程同步. 线程编程首先要学习的三个函数如下: #include &l ...

  10. Golang 读写锁RWMutex 互斥锁Mutex 源码详解

    前言 Golang中有两种类型的锁,Mutex (互斥锁)和RWMutex(读写锁)对于这两种锁的使用这里就不多说了,本文主要侧重于从源码的角度分析这两种锁的具体实现. 引子问题 我一般喜欢带着问题去 ...

随机推荐

  1. Flink学习(一) 行情介绍

    想进大厂,必须掌握 Flink 技术!!! 随着大数据时代的发展.海量数据的实时处理和多样业务的数据计算需求激增,传统的批处理方式和早期的流式处理框架也有自身的局限性,难以在延迟性.吞吐量.容错能力, ...

  2. 万字解析Golang的map实现原理

    0.引言 相信大家对Map这个数据结构都不陌生,像C++的map.Java的HashMap.各个语言的底层实现各有不同,在本篇博客中,我将分享个人对Go的map实现的理解,以及深入源码进行分析,相信耐 ...

  3. 【Matlab函数】提取inp文件中的节点、单元数据并保留集合信息

    功能 提取hypermesh2020(其他版本也可以)中的节点.单元信息,并保留elem set信息. 返回的是一个结构体 输入输出参数 输入: inp文件路径,如:'example.inp'.&qu ...

  4. python excel 读取:如何读取符合多个条件的记录【出差、外出、调休、年假】

    if 语句结合or 实现:读取所有出差.外出.调休.年假的记录 if '出差' in str(c_cell) or '外出' in str(c_cell) or'调休' in str(c_cell) ...

  5. css 技巧:利用 after 伪对象和 background 属性实现 img 图片标签占位图

    同步发布:https://blog.jijian.link/2020-04-15/css-img-after-placeholder/ 如图: 图片加载失败了,在浏览器会默认显示一张破图.受各种网速. ...

  6. linux shell用expect实现在scp时自动输入密码

    文章目录 linux shell用expect自动输入密码 按行读取文件 expect 其他 linux shell用expect自动输入密码 最近有东西需要部署到很多服务器上去,一个服务器一个服务器 ...

  7. 写于vue3.0发布前夕的helloworld之二

    接着,继续走,来到了vm.$mount. 开始生成render函数,生成VNode,由于是第一次加载,所以patch机制为只删除前一个dom节点机制,下面都会讲到. 先到$mount: Vue.pro ...

  8. RMQ 和 LCA 问题

    # Part 1 RMQ RMQ,即区间信息维护问题 如最大值,最小值,GCD 等 RMQ 算法实现很多 具体有线段树,树状数组和 ST 表 但综合时间复杂度最好的是 ST 表 查询 O(1),预处理 ...

  9. 【SpringMVC】使用 @RequestMapping 映射请求

    使用 @RequestMapping 映射请求 Spring MVC 使用 @RequestMapping 注解为控制器指定可以处理哪些 URL 请求 在控制器的类定义及方法定义处都可标注 @Requ ...

  10. ubuntu16.04安装SQLite

    主流的sqlite3,占用内存小,处理时速度快,跨平台. 几乎所有版本的 Linux 操作系统都附带 SQLite.所以,只要使用下面的命令来检查您的机器上是否已经安装了 SQLite. 一.检查是否 ...