Redis核心
基础
Redis是什么?有什么特点?
答案: Redis (Remote Dictionary Server) 是一个开源的内存数据库,支持多种数据结构。
特点:
- 高性能:基于内存,读写速度快(10万+QPS)
- 丰富的数据类型:String、Hash、List、Set、ZSet等
- 持久化:RDB、AOF
- 高可用:主从复制、哨兵、集群
- 原子性:所有操作都是原子性的
- 支持事务:MULTI、EXEC
- 发布订阅:消息队列
Redis的数据类型及应用场景?
答案:
| 数据类型 | 底层实现 | 应用场景 |
|---|---|---|
| String | SDS | 缓存、计数器、分布式锁 |
| Hash | 哈希表/压缩列表 | 对象存储、购物车 |
| List | 双向链表/压缩列表 | 消息队列、文章列表 |
| Set | 哈希表/整数集合 | 标签、共同好友 |
| ZSet | 跳表+哈希表 | 排行榜、延时队列 |
代码示例:
bash
# String
SET key value
GET key
INCR counter # 计数器
# Hash
HSET user:1 name Tom age 20
HGET user:1 name
HGETALL user:1
# List
LPUSH list 1 2 3 # 左插入
RPUSH list 4 5 6 # 右插入
LRANGE list 0 -1 # 获取所有
# Set
SADD tags java redis mysql
SMEMBERS tags
SINTER tags1 tags2 # 交集
# ZSet
ZADD rank 100 user1 200 user2
ZRANGE rank 0 -1 WITHSCORES # 排行榜Redis为什么这么快?
答案:
- 基于内存:内存读写速度远超磁盘
- 单线程模型:避免线程切换和锁竞争(Redis 6.0后网络I/O多线程)
- I/O多路复用:epoll模型,高效处理并发连接
- 高效的数据结构:SDS、跳表、压缩列表等
- 优化的内存管理:jemalloc内存分配器
客户端请求
↓
I/O多路复用(epoll)
↓
事件分发器
↓
单线程处理
↓
返回结果持久化
Redis的持久化方式?
答案:
1. RDB (Redis Database)
- 原理:快照,保存某个时间点的完整数据
- 触发:SAVE、BGSAVE、自动触发
- 优点:文件小,恢复快
- 缺点:可能丢失最后一次快照后的数据
bash
# 配置
save 900 1 # 900秒内至少1个key变化
save 300 10 # 300秒内至少10个key变化
save 60 10000 # 60秒内至少10000个key变化
# 手动触发
SAVE # 阻塞
BGSAVE # 后台执行(fork子进程)2. AOF (Append Only File)
- 原理:记录每个写操作命令
- 优点:数据更完整,最多丢失1秒数据
- 缺点:文件大,恢复慢
bash
# 配置
appendonly yes
appendfsync always # 每次写入都同步(慢但安全)
appendfsync everysec # 每秒同步(推荐)
appendfsync no # 由OS决定(快但不安全)AOF重写:
bash
# 自动触发
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
# 手动触发
BGREWRITEAOF混合持久化(Redis 4.0+):
- RDB快照 + AOF增量
- 兼顾恢复速度和数据完整性
RDB和AOF如何选择?
答案:
| 场景 | 推荐方案 |
|---|---|
| 可以容忍分钟级数据丢失 | RDB |
| 数据完整性要求高 | AOF |
| 生产环境 | RDB + AOF混合 |
缓存
缓存穿透、缓存击穿、缓存雪崩?
答案:
1. 缓存穿透
- 问题:查询不存在的数据,缓存和数据库都没有,每次都打到数据库
- 解决:
- 布隆过滤器:判断数据是否存在
- 缓存空值:设置短过期时间
java
// 布隆过滤器
if (!bloomFilter.contains(key)) {
return null; // 数据不存在
}
// 缓存空值
String value = redis.get(key);
if (value == null) {
value = db.query(key);
if (value == null) {
redis.set(key, "", 60); // 缓存空值60秒
} else {
redis.set(key, value, 3600);
}
}2. 缓存击穿
- 问题:热点key过期,大量请求同时打到数据库
- 解决:
- 互斥锁:只让一个线程查数据库
- 热点数据永不过期
java
// 互斥锁
String value = redis.get(key);
if (value == null) {
if (redis.setnx(lock_key, "1", 10)) { // 获取锁
value = db.query(key);
redis.set(key, value, 3600);
redis.del(lock_key); // 释放锁
} else {
Thread.sleep(100);
return get(key); // 重试
}
}3. 缓存雪崩
- 问题:大量key同时过期,请求全部打到数据库
- 解决:
- 过期时间加随机值
- 多级缓存
- 限流降级
java
// 过期时间加随机值
int expire = 3600 + new Random().nextInt(300); // 3600~3900秒
redis.set(key, value, expire);如何保证缓存和数据库一致性?
答案:
常见方案:
1. Cache Aside(旁路缓存)
- 读:先读缓存,miss则读数据库并更新缓存
- 写:先更新数据库,再删除缓存
java
// 读
public User get(Long id) {
User user = redis.get(id);
if (user == null) {
user = db.query(id);
redis.set(id, user, 3600);
}
return user;
}
// 写
public void update(User user) {
db.update(user);
redis.del(user.getId()); // 删除缓存
}为什么删除而不是更新缓存?
- 更新缓存可能浪费(数据可能不会被读取)
- 删除缓存更简单,下次读取时再加载
2. 延迟双删
java
public void update(User user) {
redis.del(user.getId()); // 第一次删除
db.update(user);
Thread.sleep(500); // 延迟
redis.del(user.getId()); // 第二次删除
}3. 订阅MySQL Binlog
- 使用Canal监听binlog
- 异步更新缓存
分布式锁
如何实现Redis分布式锁?
答案:
基础版本:
java
// 加锁
SET lock_key unique_value NX EX 30
// 解锁(Lua脚本保证原子性)
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
endJava实现:
java
public boolean tryLock(String key, String value, int expireTime) {
return redis.set(key, value, "NX", "EX", expireTime);
}
public void unlock(String key, String value) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
"return redis.call('del', KEYS[1]) else return 0 end";
redis.eval(script, Collections.singletonList(key), Collections.singletonList(value));
}问题和优化:
1. 锁过期问题
- 问题:业务未执行完,锁就过期了
- 解决:Redisson的看门狗机制,自动续期
2. 主从切换问题
- 问题:主节点宕机,从节点未同步锁数据
- 解决:RedLock算法(多个独立Redis实例)
Redisson使用:
java
RLock lock = redisson.getLock("myLock");
try {
lock.lock(30, TimeUnit.SECONDS); // 自动续期
// 业务逻辑
} finally {
lock.unlock();
}高可用
Redis的主从复制原理?
答案:
复制流程:
1. Slave发送PSYNC命令
2. Master执行BGSAVE生成RDB
3. Master发送RDB给Slave
4. Slave加载RDB
5. Master发送缓冲区的写命令
6. 后续增量同步全量复制 vs 增量复制:
- 全量复制:第一次同步,传输RDB
- 增量复制:后续同步,传输写命令
Redis哨兵(Sentinel)的作用?
答案:
功能:
- 监控:检测Master和Slave是否正常
- 通知:故障时通知管理员
- 自动故障转移:Master宕机时选举新Master
- 配置提供:客户端连接哨兵获取Master地址
故障转移流程:
1. 哨兵检测到Master下线
2. 多数哨兵确认(主观下线→客观下线)
3. 选举Leader哨兵
4. Leader选择一个Slave升级为Master
5. 其他Slave指向新Master
6. 通知客户端Redis集群(Cluster)的原理?
答案:
特点:
- 无中心架构
- 数据分片存储(16384个槽)
- 支持主从复制
- 自动故障转移
槽分配:
CRkey) % 16384 = slot数据迁移:
- 节点间通过Gossip协议通信
- 槽可以在线迁移
练习题
- Redis的过期策略和内存淘汰策略?
- Redis的单线程模型为什么还能支持高 如何用Redis实现延时队列?
- Redis的跳表是什么?为什么用跳表而不是红黑树?