📖 本文目录
📖 Redis 缓存实战操作解决实际问题
🔖 缓存更新策略
缓存更新策略
1️⃣、noeviction:添加数据时,如果redis判断该操作会导致占用内存大小超过内存限制,就返回error,然后啥也不干
2️⃣、allkeys-lru:添加数据时,如果redis判断该操作会导致占用内存大小超过内存限制,就会扫描所有的key,淘汰一些最近未使用的key
3️⃣、volatile-lru:添加数据时,如果redis判断该操作会导致占用内存大小超过内存限制,扫描那些设置里过期时间的key,淘汰一些最近未使用的key
4️⃣、allkeys-random:添加数据时,如果redis判断该操作会导致占用内存大小超过内存限制,就会扫描所有的key,随机淘汰一些key
5️⃣、volatile-random:添加数据时,如果redis判断该操作会导致占用内存大小超过内存限制,扫描那些设置里过期时间的key,随机淘汰一些key
6️⃣、volatile-ttl:添加数据时,如果redis判断该操作会导致占用内存大小超过内存限制,扫描那些设置里过期时间的key,淘汰一些即将过期的key
7️⃣、volatile-lfu:添加数据时,如果redis判断该操作会导致占用内存大小超过内存限制,就会淘汰一些设置了过期时间的,并且最近最少使用的key
8️⃣、allkeys-lfu:添加数据时,如果redis判断该操作会导致占用内存大小超过内存限制,就会扫描所有的key,淘汰一些最近最少使用的key


操作缓存和数据库时有三个问题需要考虑:
1️⃣ 删除缓存还是更新缓存?
- 更新操作:每次更新数据库都会更新缓存,无效写操作较多。
- 删除缓存:更新数据库时会让缓存失效,查询时再更新缓存。
2️⃣ 如何保证缓存与数据库的操作的同时成功或失败?
- 单体系统,将缓存与数据库操作放在一个事务
- 分布式系统,利用 TCC 等分布式事务方案
3️⃣ 先操作缓存还是先操作数据库

📚 总结:
缓存更新策略的最佳实践方案:
1、对于低一致性要求:使用 Redis 自带的内存淘汰机制。
2、对于高一致性要求:使用主动更新缓存策略,并且以超时剔除作为兜底方案:
- 读操作:
- 缓存命中则直接将数据返回
- 缓存未命中则查询数据库,并写入缓存,设定超时时间
- 写操作:
- 先写数据库,然后再删除缓存
- 要保证数据库与缓存操作的原子性
🔖 缓存穿透
缓存穿透
缓存穿透代表的是 —— 既要穿透 Redis 又 穿透了 MySQL 即 不存在该值。


具体实现
/**
* 功能描述
* 商户查询缓存 —— 使用 hash 来进行数据缓存
* @date 2022/8/1
* @author Alascanfu
*/
@Override
public Result queryById(Long id) {
// 1. 根据商铺 id 到 Redis 中查询商铺缓存
String shopKey = RedisConstants.CACHE_SHOP_KEY + id ;
Map<Object, Object> shopMap = stringRedisTemplate.opsForHash().entries(shopKey);
// 2. 缓存命中 => 返回商铺信息
if (shopMap.isEmpty()){
// 3. 缓存未命中 => 根据 id 查询数据库
Shop shop = getById(id);
// 3.1 判断商铺缓存是否存在 => 不存在 返回404状态码 提示错误信息
if (shop == null){
stringRedisTemplate.opsForHash().put(shopKey,RedisConstants.CACHE_NULL,RedisConstants.CACHE_NULL_VALUE);
stringRedisTemplate.expire(shopKey,RedisConstants.CACHE_NULL_TTL, TimeUnit.MINUTES);
return Result.fail("404 您访问的商户不存在!");
}
// 3.2 判断商铺缓存是否存在 => 存在 将商铺数据写入到 Redis 中 => 返回 商铺信息
Map<String, Object> redisCache = BeanUtil.beanToMap(shop,new HashMap<>()
,CopyOptions.create()
.setIgnoreNullValue(true)
.setFieldValueEditor((k,v)->{
if (k == null){
v = "0";
}else {
v = v + "";
}
return v;
}));
stringRedisTemplate.opsForHash().putAll(shopKey , redisCache);
// todo 更新数据到缓存中 并且设置超时时间
stringRedisTemplate.expire(shopKey,RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES);
return Result.ok(redisCache);
}
if (stringRedisTemplate.opsForHash().get(shopKey,RedisConstants.CACHE_NULL) != null ){
return Result.fail("404 您访问的商户不存在!");
}
// 4 结束
return Result.ok(shopMap);
}

🔖 缓存雪崩
缓存雪崩概念
缓存雪崩是指在同一时段大量的缓存 key 同时失效或者 Redis 服务宕机,从而导致的大量请求到达数据库,带来巨大压力。
解决方案
缓存雪崩的主要解决方案如下:
- 针对于批量数据加载到缓存时,添加过期时间时额外添加一个随机时间TTL
- Redis 集群提高服务高可用
- 给缓存业务 添加降级限流策略
- 给业务添加多级缓存

🔖 缓存击穿
缓存击穿的概念:
缓存击穿又被称之为热点 Key 问题,就是一个被高并发访问并且缓存重建业务较复杂的 key 突然失效了,无数的请求访问会在瞬间给数据库带来巨大的冲击。
常见的解决方案
- 互斥锁 ——
- 逻辑过期 ——

缓存击穿的 互斥锁 与 逻辑过期优缺点对比
| 解决方案 | 优点 | 缺点 |
|---|---|---|
| 互斥锁 | - 没有额外的内存消耗 - 保证一致性 - 实现简单 |
可能造成死锁风险 线程需要等待,效率受到影响 |
| 逻辑过期 | - 线程无需等待,性能较好 | 不保证一致性 有额外的内存消耗 实现较为复杂 |

本文详细探讨了Redis缓存的更新策略,包括noeviction、LRU、LFU等,并讨论了在操作缓存与数据库时需要考虑的三个关键问题。针对缓存穿透、雪崩和击穿问题,提出了解决方案,如使用互斥锁、延迟缓存以及逻辑过期策略。此外,还分享了具体的Java代码示例,展示了如何在实际项目中应用这些策略来确保缓存系统的稳定性和高可用性。
最低0.47元/天 解锁文章
2086

被折叠的 条评论
为什么被折叠?



