Redisson分布式锁介绍及实战应用(防止缓存击穿)

本地锁

    浏览器把100w请求由网关随机往下传,在集群情况下,每台服务都放行10w请求过来,这时候每台服务都用的是本地锁是跨JVM的, 列如这些服务都没有49企业,此时有几个服务进行回原了打击在DB上面,那后期把这个服务部署了N台,N台用的都是自己的锁,是锁不住的

分布式锁第一阶段

让微服务都去公共位置去,列如Redis去抢占坑位利用setnx命令,如果是1了,操作成功,是1变成0操作失败,

问题:如下图如果抢到锁了 业务在执行期间机器宕机或停电了,后面解锁业务没有执行到,锁一直在,其他服务如果还想获取到这个锁,就会导致永远获取不到这个锁了,所以得引入过期时间

 分布式锁第二阶段

引入过期时间后即使业务爆炸等10s以后再自行删除

问题:如果执行过期时间的时候停电了,导致这行代码没有设置上,所以加锁和过期时间必须是一起的保证原子性操作。

 分布式第三阶段

为了保证原子性可以使用set key value EX “过期时间” NX,加锁+过期时间 用原子操作没问题了

极端情况:
假设锁10s过期,

  1. A运行9.5s业务结束开始删锁,给redis发请求。速度慢
  2. redis第10s锁过期自动删除,然后B抢到了锁,这个锁是B,B已经开始执行业务了
  3. B执行业务的时候,A删锁命令顺着网线爬到redis。直接调用del lock,删除了这个key。导致删除了B的锁的错误。B锁没有,其他C可能又进来。两个人都在执行业务,没锁住 

分布式锁第四阶段

为了避免解别人的锁,利用UUID作为每个线程自己锁的唯一值

问题解除锁的时候可能会解到别人的锁

分布式锁终极阶段

利用Lua脚本删除锁

使用redisson

特点:
    1,锁名称相同就认为是同一把锁
    2,自动续期 默认是30S  每10S 会续期30S  
        只要锁没有指定释放时间,每隔lockWatchdogTimeout/3 就会给锁续期,续满看门狗时间30S  也就是每10S会进行喂狗操作
private long lockWatchdogTimeout = 30 * 1000;
    3,加锁操作 + 过期时间操作 能保证原子性,获取锁 + 判断锁 + 删除锁 也能保证原子性
4,redisson底层的所有操作都依赖于lua脚本

看门狗如何自动续期 

Redisson看门狗机制, 只要客户端加锁成功,就会启动一个 Watch Dog。

redisson底层有个 看门狗时间 = lockWatchdogTimeout ; 30s;每10s进行一次 喂狗

1,scheduleExpirationRenewal(threadId); 定时重新设置过期时间

  • 底层使用 Timeout;定时任务。每隔internalLockLeaseTime / 3执行重新设置超时时间的任务;

    • 超时时间设置好以后,递归调用自己,又启动一个定时任务

    • 定时任务是在 internalLockLeaseTime[30000] / 3 毫秒以后执行

    • 每10s执行一次任务。

2,如果服务宕机了,Watch Dog 机制线程也就没有了,此时就不会延长 key 的过期时间,到了 30s 之后就会自动过期了,其他线程就可以获取到锁。

<think>嗯,用户想知道怎么用Redisson分布式锁来解决缓存击穿的问题。首先我得回忆一下缓存击穿是什么情况。缓存击穿通常是指某个热点数据过期后,大量并发请求直接打到数据库,导致数据库压力激增。这时候需要用分布式锁来保证只有一个请求去数据库加载数据,其他请求等待或重试。 接下来,用户需要具体的方案和示例,所以我得按照步骤来分解。首先应该介绍缓存击穿的背景,然后分步骤说明如何用Redisson实现。Redisson的文档里提到过RLock和tryLock方法,可能需要用这些来加锁。然后,代码示例是关键,用户可能需要看到具体的Java代码,比如如何获取锁、查询数据库、设置缓存等步骤。 用户提供的引用里有提到Redisson的配置,比如缓存时间和相关配置类,可能需要引用这些内容。同时,用户之前的问题中有提到有人使用Redisson的tryLock方法但未达到预期效果,这可能需要注意锁的获取方式和等待时间。例如,是否设置了合理的等待时间和超时时间,避免线程长时间阻塞。 另外,引用5中提到了分布式锁防止缓存穿透和雪崩的步骤,可能需要参考这个流程:获取锁、查缓存、查数据库、写缓存、释放锁。这应该是一个标准的流程。还需要考虑缓存空值的情况,防止缓存穿透,也就是即使数据库中没有数据,也缓存一个空值,避免频繁查询数据库。 代码示例部分,需要确保语法正确,使用try-finally来释放锁,避免死锁。同时,要处理获取锁失败的情况,比如等待一段时间后重试或者直接返回错误。Redisson的tryLock方法可以指定等待时间和锁的持有时间,这样可以避免死锁问题。 可能还需要解释为什么使用Redisson而不是其他锁机制,比如它基于Redis,支持高可用,自动续期等特性。引用4中提到Redisson分布式系统中的JUC,所以它的锁机制类似于Java的并发工具,但适用于分布式环境。 最后,相关问题的生成需要考虑用户可能进一步的问题,比如性能优化、锁的可重入性、如何避免死锁,或者与其他缓存问题的区别,比如穿透和雪崩的不同解决方案。这些可以作为相关问题列出,帮助用户深入理解。</think>### 如何用 Redisson 分布式锁解决缓存击穿问题? #### 1. **问题背景** 缓存击穿指某个**热点数据过期瞬间**,大量并发请求穿透缓存直接访问数据库,导致数据库瞬时压力骤增。通过分布式锁可控制**仅一个线程查询数据库并重建缓存**,其他线程等待后重试缓存[^5]。 --- #### 2. **解决方案步骤** 1. **查询缓存**:先尝试从缓存获取数据 2. **缓存未命中时加锁**:使用 Redisson 的 `RLock` 获取分布式锁 3. **二次校验缓存**:加锁后再次查询缓存防止其他线程已更新) 4. **查询数据库并写入缓存**:若仍无数据,从数据库加载后更新缓存 5. **释放锁**:确保锁在 `finally` 块中释放 --- #### 3. **代码示例** ```java public String getData(String key) { String data = redisTemplate.opsForValue().get(key); if (data == null) { RLock lock = redissonClient.getLock(key + "_LOCK"); try { // 尝试获取锁,最多等待5秒,锁持有10秒后自动释放 if (lock.tryLock(5, 10, TimeUnit.SECONDS)) { // 二次校验防止重复查询 data = redisTemplate.opsForValue().get(key); if (data == null) { data = database.query(key); // 模拟数据库查询 redisTemplate.opsForValue().set(key, data, 1, TimeUnit.HOURS); } } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { if (lock.isHeldByCurrentThread()) { lock.unlock(); // 确保只有持有锁的线程释放 } } } return data; } ``` --- #### 4. **关键配置说明** - **Redisson 客户端初始化**:需注入 `RedissonClient` 实例 - **Redis 缓存配置**:需设置合理的过期时间(如1小时)并允许缓存空值[^2] - **锁参数优化**:`tryLock` 的等待时间(5秒)和锁持有时间(10秒)需根据业务调整 --- #### 5. **注意事项** - **死锁预防**:锁必须设置超时时间,避免线程崩溃导致锁无法释放[^1] - **缓存空值**:若数据库无数据,应缓存空值或占位符(如 `NULL`),防止反复穿透 - **锁的可重入性**:Redisson 锁支持同一线程多次加锁,避免嵌套调用死锁[^4] ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

有心不在迟

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

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

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

打赏作者

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

抵扣说明:

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

余额充值