当前位置: 首页 > news >正文

Redis面试问题缓存相关详解

Redis面试问题缓存相关详解

一、缓存三兄弟(穿透、击穿、雪崩)

1. 穿透

问题描述
缓存穿透是指查询一个数据库中不存在的数据,由于缓存不会保存这样的数据,每次都会穿透到数据库,导致数据库压力增大。例如,用户请求一个不存在的用户ID,每次请求都会直接查询数据库。

解决方法

1.1、存储特殊标记值
当数据库中没有查询到数据时,可以将一个特殊标记值(如"not_found")存储到Redis中,并设置一个较短的过期时间(如5分钟)。这样,后续的请求可以直接返回"not_found",而不会再次查询数据库。
优点:实现简单,减少数据库压力。
缺点:如果数据库中后来插入了数据,而Redis中仍然缓存了"not_found",会导致查询结果不一致。
示例

String value = redis.get(key);
if (value == null) {value = database.get(key);if (value == null) {redis.set(key, "not_found", 300); // 缓存5分钟return "not_found";} else {redis.set(key, value, 3600); // 缓存1小时}
}
return value;

1.2、布隆过滤器
在缓存和数据库之间加入一个布隆过滤器,它可以预存储一些可能存在的键。如果查询的键不在布隆过滤器中,直接返回不存在,避免查询数据库。布隆过滤器通过哈希函数实现,误判率可以通过调整其大小和哈希函数的数量来控制。
优点:减少对数据库的无效查询。
缺点:实现复杂,有一定的误判率。
示例

if (!bloomFilter.contains(key)) {return "not_found";
}
String value = redis.get(key);
if (value == null) {value = database.get(key);redis.set(key, value, 3600); // 缓存1小时
}
return value;

2. 击穿

问题描述
缓存击穿是指一个热点数据的缓存过期后,大量请求同时查询数据库,导致数据库压力过大。例如,一个热门商品的详情页缓存过期,大量用户同时请求该页面。

解决方法

2.1、互斥锁
使用互斥锁确保只有一个线程可以查询数据库并更新缓存。其他线程等待锁释放后直接从缓存中获取数据。可以使用Redis的SETNX命令实现分布式锁。
示例

String lockKey = "lock:" + key;
if (redis.setnx(lockKey, "1", 30) == 1) { // 尝试获取锁,超时30秒String value = database.get(key);redis.set(key, value, 3600); // 更新缓存redis.del(lockKey); // 释放锁return value;
} else {// 等待锁释放Thread.sleep(100);return redis.get(key);
}

2.2、逻辑过期
在缓存中存储一个额外的过期时间字段,每次读取时检查逻辑过期时间。即使Redis的物理过期时间到了,逻辑过期时间仍然有效,可以避免频繁更新缓存。
示例

String value = redis.get(key);
if (value == null) {String lockKey = "lock:" + key;if (redis.setnx(lockKey, "1", 30) == 1) {value = database.get(key);redis.set(key, value, 3600); // 更新缓存redis.del(lockKey); // 释放锁} else {Thread.sleep(100);return redis.get(key);}
}
return value;

3. 雪崩

问题描述
缓存雪崩是指大量缓存数据在同一时间过期,导致大量请求同时查询数据库,造成数据库压力过大。例如,多个热点数据的缓存同时过期。

解决方法

3.1、随机过期时间
为每个缓存设置不同的过期时间,避免大量缓存同时过期。例如,可以使用SETEX命令为每个key设置随机的过期时间(如1-5分钟)。
示例

int randomTTL = new Random().nextInt(300) + 60; // 随机过期时间1-5分钟
redis.setex(key, randomTTL, value);

3.2、本地缓存
在应用层使用本地缓存(如Guava Cache)作为二级缓存,减轻Redis的压力。本地缓存可以快速响应,减少对Redis的依赖。
示例

LoadingCache<String, String> localCache = CacheBuilder.newBuilder().expireAfterWrite(1, TimeUnit.MINUTES).build(new CacheLoader<String, String>() {@Overridepublic String load(String key) throws Exception {return redis.get(key);}});String value = localCache.get(key);
if (value == null) {value = database.get(key);redis.setex(key, 3600, value); // 更新Redis缓存localCache.put(key, value); // 更新本地缓存
}
return value;

3.3、降级限流
降级和限流是应对高并发场景的通用策略。可以使用Guava RateLimiter或Redis的INCREXPIRE命令实现限流。例如,每秒最多允许100次请求:
示例

RateLimiter rateLimiter = RateLimiter.create(100); // 每秒100次
if (rateLimiter.tryAcquire()) {return handleRequest();
} else {return "Too many requests";
}

二、双写一致

问题描述
双写一致是指保持Redis和数据库的更新操作一致。如果操作顺序不当,可能会导致数据不一致。例如:

  • 先更新Redis,再更新数据库:可能导致Redis中的数据丢失。
  • 先更新数据库,再更新Redis:可能导致Redis中的旧数据覆盖新数据。

解决方法

1、延迟双删
先删除Redis中的缓存,然后更新数据库,最后再删除一次Redis中的缓存。这样可以确保Redis中的数据是最新的。
示例

redis.del(key); // 删除缓存
database.update(key, value); // 更新数据库
Thread.sleep(300); // 延迟300毫秒
redis.del(key); // 再次删除缓存

2、分布式锁
使用分布式锁确保同一时间只有一个线程可以更新数据。可以使用Redisson等库实现分布式锁。
示例

RLock lock = redisson.getLock("lock:" + key);
try {lock.lock();database.update(key, value); // 更新数据库redis.del(key); // 删除缓存
} finally {lock.unlock();
}

3、共享锁和排它锁

共享锁:当一个线程在读取数据时,其他线程可以同时读取,但不能写入。

排它锁:当一个线程在写入数据时,其他线程不能读取或写入。
示例

// 使用Redis的SET命令实现共享锁和排它锁
String lockKey = "lock:" + key;
if (redis.set(lockKey, "1", 30, NX, EX)) { // 获取排它锁database.update(key, value); // 更新数据库redis.del(key); // 删除缓存redis.del(lockKey); // 释放锁
} else {// 等待锁释放Thread.sleep(100);
}

三、持久化

Redis提供了两种持久化方式:RDB和AOF。

1、RDB(快照模式):

优点:恢复速度快,数据完整性好,方便使用。

缺点:可能会丢失最后一次快照之后的数据,占用磁盘空间较大。

配置示例

save 900 1  # 900秒内至少有1个键被修改时保存快照
save 300 10 # 300秒内至少有10个键被修改时保存快照

2、AOF(追加模式):

优点:数据不易丢失,恢复时可以逐条执行命令。

缺点:文件体积大,恢复速度慢。

配置示例

appendonly yes
appendfsync everysec  # 每秒同步一次

3、混合持久化:
Redis 4.0引入了混合持久化模式,结合了RDB和AOF的优点。可以同时使用两种持久化方式,提高数据安全性和恢复速度。

四、数据过期策略

Redis提供了两种主要的数据过期策略:惰性删除和定期删除。

1、惰性删除:

优点:占用CPU资源少。

缺点:可能会导致内存中积累大量过期数据。

原理:只有当访问某个键时,才会检查该键是否过期,如果过期则删除。

2、定期删除:

原理:Redis会定期扫描内存中的键,删除过期的键。

配置示例

hz 10  # 设置Redis的事件循环频率,单位为每秒

五、数据淘汰策略

Redis支持多种数据淘汰策略,可以通过maxmemory-policy配置。

策略描述适用场景
noeviction不淘汰任何数据,当内存不足时返回错误内存足够大,不需要淘汰数据
allkeys-lru对所有键使用LRU(最近最少使用)算法淘汰通用缓存场景
volatile-lru对设置了过期时间的键使用LRU算法淘汰热点数据缓存
allkeys-random随机淘汰所有键对数据一致性要求不高的场景
volatile-random随机淘汰设置了过期时间的键热点数据缓存
allkeys-lfu对所有键使用LFU(最不经常使用)算法淘汰访问频率差异较大的缓存
volatile-lfu对设置了过期时间的键使用LFU算法淘汰热点数据缓存
volatile-ttl优先淘汰TTL较短的键临时数据缓存

相关文章:

  • 插件化设计,打造个性化音乐体验!
  • 算法——果蝇算法
  • C++23 Lambda 表达式上的属性:P2173R1 深度解析
  • 【ROS】map_server 地图的保存和加载
  • 50、Spring Boot 详细讲义(七) Spring Boot 与 NoSQL
  • 在生信分析中,从生物学数据库中下载的序列存放在哪里?要不要建立一个小型数据库,或者存放在Gitee上?
  • 常见数据结构
  • 【系统分析师之1、绪论+2、数学与工程基础】
  • 【正点原子STM32MP257连载】第四章 ATK-DLMP257B功能测试——LED、按键测试
  • 删除win11电脑上的阿尔巴尼亚输入法SQI
  • OSPF综合实验
  • MySQL——流程控制
  • 【Unity笔记】Unity开发笔记:ScriptableObject实现高效游戏配置管理(含源码解析)
  • 全国青少年信息素养大赛 C++算法创意实践挑战赛初赛 集训模拟试卷《二》及详细答案解析
  • ACI EP Learning Whitepaper 3. Disabling IP Data-plane Learning 功能
  • Vue3服务端渲染(SSR)深度调优:架构裂变与性能突围
  • bC一体化是传统批发企业 换道超车的唯一路径
  • Spring Cache(笔记)
  • 第二天 通过脚本控制物体移动和旋转
  • 03 UV
  • 魔都眼丨人形机器人“华山论剑”:拳击赛缺席,足球赛抢镜
  • 公安部知识产权犯罪侦查局:侦破盗录传播春节档院线电影刑案25起
  • 青海西宁市公安局原党委委员、副局长王小华被“双开”
  • 海关总署牵头部署开展跨境贸易便利化专项行动
  • 民生访谈|马拉松中签率低何解?预付费监管落实得如何?市体育局回应
  • 今年一季度上海离境退税商品销售额7.6亿元,同比增85%