Redis分布式锁防止缓存击穿

本文探讨了缓存击穿现象,特别是针对热点数据并发访问导致的问题,并提供了一种利用Redis实现分布式锁来解决缓存击穿的方法。

缓存击穿

和缓存穿透不同的是,缓存击穿是指:缓存中没有,但是数据库中存在的热点数据。

例如:首页的热点新闻,并发访问量非常大的热点数据,如果缓存过期失效,服务器会去查询DB,这时候如果大量的并发去查询DB,可能会瞬间压垮DB。

画了个简图,如下所示:
在这里插入图片描述

解决方案:DB查询加分布式锁

未加锁的情况

解决问题之前,先看一下不做处理的代码和运行情况。

根据商品ID查询商品详情代码
在这里插入图片描述

清空Redis缓存,开启5个线程去并发访问测试,测试代码如下:
在这里插入图片描述

我们预期希望DB只查询一次,后面4个查询从Redis缓存中取就行,但是结果:
在这里插入图片描述

没有加分布式锁,结果也在意料之中,但是这样容器给DB造成很大压力。

如果是单台服务器,直接使用Java的同步锁即可
在这里插入图片描述

遗憾的是,通常后端是会部署集群的,Java的同步锁可没办法实现分布式锁。

Redis分布式锁解决缓存击穿

Java的内置锁只能应用在单台机器上,无法实现分布式,可以巧用Redis来实现分布式锁。

加了分布式锁后的代码

//根据ID查询商品
@GetMapping("/{id}")
public R id(@PathVariable String id){
	//先查Redis缓存
	Object o = redisTemplate.opsForValue().get(id);
	if (o != null) {
		//命中缓存
		System.err.println("id:"+id+",命中redis缓存...");
		return R.success(o);
	}

	//缓存未命中 查询数据库
	String lockKey = "lock" + id;
	//加锁,10s后过期
	for (;;) {
		if (redisTemplate.opsForValue().setIfAbsent(lockKey, System.currentTimeMillis(), 10L, TimeUnit.SECONDS)) {
			//加锁成功的线程,再次检查
			o = redisTemplate.opsForValue().get(id);
			if (o != null) {
				//命中缓存
				System.err.println("Thread:" + Thread.currentThread().getName() + ",id:"+id+",命中redis缓存...");
				//释放锁
				redisTemplate.delete(lockKey);
				return R.success(o);
			}

			//仍未命中
			System.err.println("Thread:" + Thread.currentThread().getName() + ",id:" + id + ",查询DB...");
			Goods goods = goodsMapper.selectById(id);
			//结果存入Redis
			redisTemplate.opsForValue().set(id, goods);
			//释放锁
			redisTemplate.delete(lockKey);
			return R.success(goods);
		}
		//竞争不到锁,暂时让出CPU资源
		Thread.yield();
	}
}

启动5个线程,并发访问,结果如下图:
在这里插入图片描述

这里介绍的只是最简单的方案,实际情况要考虑复杂的多,例如:不能误解锁、锁超时等问题。

<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
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值