黑马---Redis实战篇

本文介绍了Redis作为缓存的使用,包括缓存的基本概念、如何在service层添加Redis缓存、缓存更新策略如主动更新和超时剔除。针对缓存穿透问题,提出了缓存空对象和布隆过滤器两种解决方案。另外,讨论了缓存雪崩和缓存击穿的现象及其应对策略,提供基于互斥锁和逻辑过期时间的解决办法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

(一)、缓存

(1)、什么是缓存

缓存就是数据交换的缓冲区,称作Cache,是存储数据的临时地方,一般读写性能高 

(2)、添加Redis缓存

 service层代码

@Override
	public Result getShopById(Long id) {
		String key = RedisConstants.CACHE_SHOP_KEY + id;
		//  TODO  1. 在缓存中查找数据
		String shopJson = stringRedisTemplate.opsForValue().get(key);
		//  TODO  2. 缓存中存在,直接将数据返回
		if (!StrUtil.isBlank(shopJson)) {
			Shop shop = JSONUtil.toBean(shopJson, Shop.class);
			return Result.ok(shop);
		}
		//  TODO  3. 缓存中不存在,在数据库中进行查找
		Shop shop = shopMapper.selectById(id);
		//  TODO  4. 数据库中不存在,返回【商铺信息不存在】
		if (shop == null) return Result.fail("商铺信息不存在");
		//  TODO  5. 数据库中存在,将数据写入到缓存中并且将数据返回给前端
		String shopJsonFromDataBase = JSONUtil.toJsonStr(shop);
		stringRedisTemplate.opsForValue().set(key,shopJsonFromDataBase);
		return Result.ok(shop);
	}

(3)、缓存更新策略

主动更新策略

案列:  给查询商铺的缓存添加超时剔除和主动更新策略

(4)、缓存穿透

是指客户端请求的数据在缓存中和数据库中都不存在,这样缓存永远不会生效,这些请求都会打到数据库

缓存穿透解决方案

1. 缓存空对象

                优点: 实现简单,维护方便

                缺点:      额外内存消耗(可以设置TTL)

                              可能造成短期的不一致  

2.布隆过滤

                是一个算法

                优点:  内存占用较少,没有多余key

                缺点:实现复杂,存在误判可能

 --------------------------------------------------------

在数据库中查找不存在的时候,将商铺信息写成 "" 写入redis中

并且在redis查找的时候,先判断是否为空,不为空,判断是否为空字符串

public Result getShopById(Long id) {
		String key = RedisConstants.CACHE_SHOP_KEY + id;
		//  TODO  1. 在缓存中查找数据
		String shopJson = stringRedisTemplate.opsForValue().get(key);
		//  TODO  2. 缓存中存在,直接将数据返回
		if (!StrUtil.isBlank(shopJson)) {
			Shop shop = JSONUtil.toBean(shopJson, Shop.class);
			return Result.ok(shop);
		}
		//  现在不在数据库中进行查找,而是继续判断查找出来的信息是否为空字符串
		if (shopJson != null){
			return Result.fail("店铺信息不存在");
		}
		/**
		 * 这个逻辑目前是这样的:
		 *          存入缓存的只有:    1.{"key":value}   2.""
		 * 由于已经在第一个判断语句中判断了是否为空字符串
		 */

		//  TODO  3. 缓存中不存在,在数据库中进行查找
		Shop shop = shopMapper.selectById(id);
		//现在,将数据库中查出来的数据,如果为空的话,那么将【""】存入缓存中,过期时间为2分钟
		stringRedisTemplate.opsForValue().set(key,"",RedisConstants.CACHE_NULL_TTL);
		//  TODO  4. 数据库中不存在,返回【商铺信息不存在】
		if (shop == null) {
			//现在,将数据库中查出来的数据,如果为空的话,那么将【""】存入缓存中,过期时间为2分钟
			stringRedisTemplate.opsForValue().set(key,"",RedisConstants.CACHE_NULL_TTL);
			return Result.fail("商铺信息不存在");
		}
		//  TODO  5. 数据库中存在,将数据写入到缓存中并且将数据返回给前端
		String shopJsonFromDataBase = JSONUtil.toJsonStr(shop);
		stringRedisTemplate.opsForValue().set(key,shopJsonFromDataBase, RedisConstants.CACHE_SHOP_TTL);
		return Result.ok(shop);
	}

(5)、缓存雪崩

是指在同一时间段大量的缓存key同时失效或者redis服务器宕机,导致大量请求到达数据库,带来巨大压力

 解决方案:

 (6)缓存击穿

缓存击穿问题也叫热点key问题,就是一个被高并发访问并且缓存重建业务较复杂的key突然失效,无数的请求访问会在瞬间给数据库带来巨大冲击

解决方案:

 

案例:

 (1)基于互斥锁方式解决缓存击穿问题

 

代码

public Result queryThroughMultex(Long id) {
	String key = RedisConstants.CACHE_SHOP_KEY + id;
	//  TODO  1. 在缓存中查找数据
	String shopJson = stringRedisTemplate.opsForValue().get(key);
	//  TODO  2. 缓存中存在,直接将数据返回
	if (!StrUtil.isBlank(shopJson)) {
		Shop shop = JSONUtil.toBean(shopJson, Shop.class);
		return null;
	}
	//  现在不在数据库中进行查找,而是继续判断查找出来的信息是否为空字符串
	if (shopJson != null){
		return null;
	}
	/**
	 * 以下进行缓存重建
	 */
	//  1) 获取锁
	//	2) 判断是否获取成功
	//  3) 成功   去数据库中进行查找
	//     失败   【休眠并且进行重试】 <----important
	Shop shop = null;
	String lockKey  = "lock:shop:" + id;
	try {
		if (!getLock(lockKey)) {   //  获取锁失败
			Thread.sleep(50);
			return queryThroughMultex(id);
		}
		//  ======================================================================================
		//  TODO  3. 缓存中不存在,在数据库中进行查找
		shop = shopMapper.selectById(id);
		//现在,将数据库中查出来的数据,如果为空的话,那么将【""】存入缓存中,过期时间为2分钟
		stringRedisTemplate.opsForValue().set(key,"",RedisConstants.CACHE_NULL_TTL);
		//  TODO  4. 数据库中不存在,返回【商铺信息不存在】
		if (shop == null) {
			//现在,将数据库中查出来的数据,如果为空的话,那么将【""】存入缓存中,过期时间为2分钟
			stringRedisTemplate.opsForValue().set(key,"",RedisConstants.CACHE_NULL_TTL);
			return Result.fail("商铺信息不存在");
		}
		//  TODO  5. 数据库中存在,将数据写入到缓存中并且将数据返回给前端
		String shopJsonFromDataBase = JSONUtil.toJsonStr(shop);
		stringRedisTemplate.opsForValue().set(key,shopJsonFromDataBase, RedisConstants.CACHE_SHOP_TTL);
	} catch (InterruptedException e) {
		throw new RuntimeException(e);
	}finally {
		//  释放锁
		deleteLock(lockKey);
	}
	return Result.ok(shop);
}

(2) 、基于逻辑过期方式解决缓存击穿问题

需求: 修改根据id查询商铺的业务,基于逻辑过期方式来解决缓存击穿问题

 

这里的线程使用线程池

(7)、缓存工具封装

### 关于黑马程序员 Redis 实战篇 示例代码下载 根据提供的引用内容,可以确认黑马程序员确实有针对 Redis 的实战课程,并且该课程涵盖了 Redis 的实际应用场景和技术细节[^2]。然而,具体示例代码的获取途径并未在现有引用中提及。 通常情况下,此类课程的示例代码可以通过以下几种方式进行获取: 1. **官方渠道**:如果参加了黑马程序员的正式培训课程,示例代码一般会通过课程配套资源或者讲师分享的方式提供给学员。 2. **在线平台**:部分培训机构会在其官方网站或合作平台上发布课程资料和示例代码供学员下载。建议访问黑马程序员官网或其他授权的学习平台查找相关内容。 3. **社区支持**:一些开源社区可能会共享类似的 Redis 实战案例代码。例如 GitHub 上可能存在由其他开发者上传的 Redis 相关项目源码,这些代码可能与课程中的示例相似。 以下是基于 Redis 和 Lua 脚本的一个简单示例代码片段,展示如何利用 Lua 脚本来确保多条 Redis 命令执行时的原子性: ```lua -- 使用 Lua 脚本实现 Redis 多命令原子操作 local key = KEYS[1] local value = ARGV[1] if redis.call("exists", key) == 0 then redis.call("set", key, value) return 1 else return 0 end ``` 此脚本的功能是检查指定键是否存在,如果不存在则设置新值;如果存在,则返回 `0` 表示未执行写入操作。 另外,关于分布式锁的具体实现,Redisson 提供了一种非常简便的方式来管理分布式环境下的锁机制[^1]。下面是一段使用 Redisson 进行分布式锁控制的 Java 示例代码: ```java import org.redisson.api.RLock; import org.redisson.api.RedissonClient; public class DistributedLockExample { private final RedissonClient redissonClient; public void acquireAndReleaseLock(String lockName) { RLock lock = redissonClient.getLock(lockName); try { boolean isLocked = lock.tryLock(10, 30, TimeUnit.SECONDS); // 尝试加锁,等待时间10秒,持有时间30秒 if (isLocked) { System.out.println("成功获得锁"); // 执行受保护的操作 } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { if (lock.isHeldByCurrentThread()) { lock.unlock(); // 解锁 System.out.println("释放锁"); } } } } ``` 以上代码展示了如何使用 Redisson 库来创建并管理分布式锁,有效避免了并发场景下可能出现的竞争问题。 ####
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值