Redis
2025/5/21大约 8 分钟
Redis 缓存穿透、缓存雪崩和缓存击穿分别是什么,如何解决?
| 问题 | 现象 | 解决方案 |
|---|---|---|
| 缓存穿透 | 查询不存在的数据,请求直达数据库 | 布隆过滤器、空值缓存、接口限流与熔断 |
| 缓存雪崩 | 大量缓存同时过期,请求直达数据库 | 随机过期时间、互斥锁 |
| 缓存击穿 | 热点key过期瞬间,大量并发请求同时落DB | 双重检查锁(DCL)、分布式锁、逻辑过期 |
双重检查锁(DCL)
核心步骤:
- 第一次检查缓存
- 获取分布式锁
- 锁内再次检查缓存
- 查询数据库并回填缓存
- 其他线程读取更新后的缓存
核心思想:通过"双重检查 + 分布式锁"确保同一时刻只有一个线程查询数据库。
public String getData(String key) {
// 第一次检查缓存
String value = redis.get(key);
if (value != null) return value;
// 获取分布式锁
if (redis.setnx(lockKey, "1", 10)) {
try {
// 锁内再次检查缓存
value = redis.get(key);
if (value == null) {
// 查询数据库并回填缓存
value = database.query(key);
redis.setex(key, value, 3600);
}
} finally {
redis.del(lockKey);
}
} else {
// 未获取锁,等待后重试
Thread.sleep(50);
return getData(key);
}
return value;
}Redis 有哪些数据类型?
| 类型 | 特点 | 典型场景 |
|---|---|---|
| String | 二进制安全,最大512MB | 缓存、计数器 |
| Hash | 键值对集合,适合存储对象 | 用户信息、商品属性 |
| List | 字符串链表,支持两端操作 | 消息队列、最新列表 |
| Set | 无序不重复集合 | 标签、共同好友 |
| ZSet | 有序集合,按分数排序 | 排行榜、延迟队列 |
| HyperLogLog | 基数统计,内存占用小 | UV统计 |
| Bitmap | 位图,支持位运算 | 用户签到、在线状态 |
用 Redis 怎么实现队列?
1. 使用 List 实现简单队列
Redis 的 List 数据结构天然支持队列操作,一端添加元素,另一端取出元素。
# 生产者推送消息到队列尾部
RPUSH queue:message "任务内容"
# 消费者从队列头部取出消息
LPOP queue:message
# 阻塞式取出(无消息时等待,避免频繁轮询)
BLPOP queue:message 30 # 最多等待30秒特点:简单易用,适合基础队列需求,但消息消费后即删除,不支持重复消费。
2. 使用 List 实现优先级队列
通过多个 List 模拟不同优先级的队列,消费者按优先级顺序处理。
# 高优先级任务入队
RPUSH queue:high "紧急任务"
# 普通优先级任务入队
RPUSH queue:normal "普通任务"
# 消费者先检查高优先级队列
LPOP queue:high # 优先处理高优先级
LPOP queue:normal # 高优先级为空时处理普通任务特点:实现简单,但需要消费者主动轮询多个队列,优先级数量固定时适用。
3. 使用 ZSet 实现延迟队列
利用 ZSet 的有序特性,将执行时间作为分数,实现定时任务队列。
# 添加延迟任务(时间戳作为分数)
ZADD delay:queue 1625097600000 "任务ID1" # 指定时间戳执行
ZADD delay:queue 1625097700000 "任务ID2"
# 获取当前时间到期的任务
ZRANGEBYSCORE delay:queue 0 $(date +%s)000
# 处理完成后删除任务
ZREM delay:queue "任务ID1"特点:支持精确的延迟执行,适合定时任务场景,但需要额外的时间管理逻辑。
4. 使用 Stream 实现消息队列(Redis 5.0+)
Stream 是 Redis 5.0 引入的专门用于消息队列的数据结构,功能更完善。
# 生产者发送消息
XADD stream:messages * name 张三 age 25
# 创建消费者组
XGROUP CREATE stream:messages mygroup 0 MKSTREAM
# 消费者读取消息(未确认消息会保留)
XREADGROUP GROUP mygroup consumer1 COUNT 1 STREAMS stream:messages >
# 消息处理完成后确认
XACK stream:messages mygroup 1625097600000-0特点:
- 支持消费者组,实现消息广播
- 消息确认机制,确保不丢失
- 支持消息回溯,可读取历史消息
- 适合复杂的消息队列场景
各方案对比
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| List | 简单高效 | 消息即删,不支持重复消费 | 简单队列、任务分发 |
| 多List | 实现简单 | 需轮询,优先级固定 | 优先级任务处理 |
| ZSet | 支持延迟执行 | 需要额外时间管理 | 定时任务、延迟队列 |
| Stream | 功能完善 | Redis 5.0+,相对复杂 | 可靠消息队列、事件流 |
缓存与数据库一致性保持
1. 如何保证缓存与数据库的一致性?
脏数据产生原因:
- 更新数据库后更新缓存:如果更新缓存失败,会导致数据库与缓存不一致
- 并发操作:线程A更新数据库但未更新缓存时,线程B读取到旧缓存数据
常用策略:
- 更新数据库后删除缓存:避免脏数据,下次读取时从数据库重新加载
- 延迟双删:更新数据库→删除缓存→延迟→再删除缓存,确保脏数据清除
- 局限:第二次删除时数据可能又被更新,仍存在不一致风险
- 改进:结合版本号或时间戳判断,避免删除新数据
- 基于消息队列:通过MQ异步更新缓存,保证最终一致性
异步更新避免脏数据原理:
- 顺序保证:MQ保证消息按序处理,避免并发问题
- 重试机制:消息消费失败可重试,确保最终一致性
2. 先更新数据库再删缓存,缓存短暂miss导致大量请求打过来怎么办?
采用 双重检查锁(DCL) 解决:
- 第一次检查缓存
- 获取分布式锁
- 锁内再次检查缓存
- 查询数据库并回填缓存
- 其他线程读取更新后的缓存
核心思想:通过"双重检查+分布式锁"确保同一时刻只有一个线程查询数据库,避免缓存失效瞬间的并发请求击穿数据库。
Redis怎么保证数据不丢失?
回答:
Redis作为内存数据库,通过持久化和高可用两种方式保证数据不丢失:
1. 数据持久化机制
RDB快照
- 原理:周期性将内存数据生成快照保存到磁盘
- 优点:文件小、恢复快
- 缺点:可能丢失最后一次快照后的数据
AOF日志(Append Only File)
- 原理:记录所有写操作命令到日志文件
- 同步策略:
- always:每次写操作同步(最安全,性能最低)
- everysec:每秒同步一次(默认,平衡安全与性能)
- no:由OS决定同步(性能最高,安全性最低)
- 优点:最多丢失1秒数据
- 缺点:文件大、恢复慢
混合持久化(推荐)
- 原理:RDB+AOF结合,AOF只记录RDB快照后的命令
- 优点:兼顾恢复速度和数据安全性
2. 高可用机制
主从复制
- 原理:主节点数据自动同步到从节点
- 作用:数据备份、读写分离
哨兵模式(Sentinel)
- 原理:监控主从集群,自动完成故障转移
- 功能:监控、通知、自动故障转移、配置中心
集群模式(Cluster)
- 原理:数据分片+自动故障转移
- 优点:高可用、高扩展性
核心要点:混合持久化+主从复制+哨兵监控,可最大程度保证Redis数据不丢失。
Redis的过期删除策略了解吗?
键过期是Redis提供的一种自动删除机制,允许为键设置一个生存时间(TTL, Time To Live)。当超过设定的时间后,Redis会自动删除该键。
如何设置过期策略?
# 设置键值并指定30秒过期
SET key value EX 30
# 为已存在的键设置30秒过期
EXPIRE key 30
# 查看剩余生存时间(秒)
TTL key
# 移除过期时间,使键永久存在
PERSIST key过期删除策略:
Redis使用两种策略相结合的方式管理过期键:
惰性删除
- 原理:当访问一个键时,才会检查该键是否过期,如果过期则删除
- 优点:节省CPU资源,只在必要时执行删除操作
- 缺点:可能导致内存中存在大量过期键,占用内存资源
定期删除
- 原理:每隔一段时间,随机抽取部分过期键进行检查,如果过期则删除
- 优点:定期清理过期键,防止内存溢出
- 缺点:可能漏删部分过期键,需要配合惰性删除使用
内存淘汰策略
当Redis内存达到最大限制时,会触发内存淘汰策略:
| 策略 | 描述 |
|---|---|
noeviction | 不删除键,返回错误(默认) |
volatile-lru | 从有过期时间的键中,删除最近最少使用的 |
volatile-ttl | 从有过期时间的键中,删除剩余时间最短的 |
volatile-random | 从有过期时间的键中,随机删除 |
allkeys-lru | 从所有键中,删除最近最少使用的 |
allkeys-random | 从所有键中,随机删除 |
过期策略的工作流程
- 客户端访问键时,触发惰性删除检查
- Redis定期执行定期删除,清理部分过期键
- 内存达到上限时,触发内存淘汰策略
- 过期键被删除后,会记录到AOF日志中保证持久化一致性