Redis 常见面试题汇总
·
一、Redis 基础
1. Redis 是什么?
- 开源的内存数据存储系统
- 支持多种数据结构:String, Hash, List, Set, Sorted Set, Bitmaps等
- 可用作数据库、缓存、消息中间件
- 单线程模型(处理命令),多线程处理网络IO(6.0+)
- 高性能:10万+ QPS
2. Redis 数据类型及应用场景
|
数据类型 |
底层结构 |
应用场景 |
示例命令 |
|---|---|---|---|
|
String |
SDS |
缓存、计数器、分布式锁 |
SET/GET/INCR |
|
Hash |
哈希表 |
对象存储、购物车 |
HSET/HGET/HGETALL |
|
List |
双向链表/压缩列表 |
消息队列、最新列表 |
LPUSH/RPOP/LRANGE |
|
Set |
哈希表/整数集合 |
标签、共同好友、抽奖 |
SADD/SMEMBERS/SINTER |
|
Sorted Set |
跳跃表+哈希表 |
排行榜、延时队列 |
ZADD/ZRANGE/ZREVRANGE |
|
Bitmap |
String |
签到、用户在线状态 |
SETBIT/GETBIT/BITCOUNT |
|
HyperLogLog |
特殊结构 |
UV统计 |
PFADD/PFCOUNT |
|
GEO |
Sorted Set |
地理位置 |
GEOADD/GEODIST |
|
Stream |
列表 |
消息队列 |
XADD/XREAD |
二、数据结构与内部实现
1. Redis 对象系统
// RedisObject
typedef struct redisObject {
unsigned type:4; // 类型:string, hash, list等
unsigned encoding:4; // 编码:int, embstr, raw, hashtable等
unsigned lru:LRU_BITS; // LRU时间或LFU频率
int refcount; // 引用计数
void *ptr; // 指向实际数据的指针
} robj;
2. String 类型实现
# 三种编码:
1. int: 保存整数值,直接存储在ptr
2. embstr: 长度<=44字节的字符串,连续内存
3. raw: 长度>44字节的字符串,SDS动态字符串
# SDS优点:
1. O(1)获取长度
2. 二进制安全
3. 减少内存分配次数
4. 兼容C字符串
3. Hash 类型实现
# 两种编码:
1. ziplist(压缩列表):元素少且值小
- 元素数量<512
- 所有值<64字节
2. hashtable(哈希表):不满足ziplist条件时转换
4. List 类型实现
# 两种编码:
1. ziplist(压缩列表):元素少且值小
- 元素数量<512
- 所有值<64字节
2. linkedlist(双向链表):3.2版本后废弃
3. quicklist(快速列表):3.2版本后默认
- ziplist + 双向链表组合
5. Set 类型实现
# 两种编码:
1. intset(整数集合):元素少且都是整数
- 元素数量<512
- 元素都是整数
2. hashtable(哈希表):value为NULL的哈希表
6. Sorted Set 类型实现
# 两种编码:
1. ziplist(压缩列表):元素少
- 元素数量<128
- 所有成员<64字节
2. skiplist + hashtable(跳跃表+哈希表)
- 跳跃表:按score排序
- 哈希表:O(1)查找成员
三、持久化机制
1. RDB(Redis Database)
# 原理:
- 在指定时间间隔生成数据快照
- 二进制格式,紧凑
- 主进程fork子进程进行持久化
# 配置:
save 900 1 # 900秒内至少1个key变化
save 300 10 # 300秒内至少10个key变化
save 60 10000 # 60秒内至少10000个key变化
# 优点:
- 适合备份和灾难恢复
- 恢复速度快
- 最大化Redis性能
# 缺点:
- 可能丢失最后几分钟数据
- fork过程可能阻塞
2. AOF(Append Only File)
# 原理:
- 记录每个写操作命令
- 先执行命令再记录日志
- 通过重写压缩AOF文件
# 同步策略:
appendfsync always # 每个命令都同步,最安全,性能差
appendfsync everysec # 每秒同步,推荐
appendfsync no # 操作系统决定,性能最好,可能丢失数据
# 重写机制:
- 自动触发:AOF文件超过阈值
- 手动触发:BGREWRITEAOF
- 原理:分析现有数据,生成新的AOF
# 优点:
- 数据更安全,最多丢失1秒数据
- 可读性高
# 缺点:
- 文件体积大
- 恢复速度慢
3. RDB vs AOF
# 对比:
| 特性 | RDB | AOF |
|------|-----|-----|
| 持久化方式 | 快照 | 日志 |
| 数据安全 | 可能丢失数据 | 相对安全 |
| 恢复速度 | 快 | 慢 |
| 文件大小 | 小 | 大 |
| 对性能影响 | fork时阻塞 | 写AOF时影响 |
| 默认开启 | 是 | 否 |
# 混合持久化(4.0+):
- 开启:aof-use-rdb-preamble yes
- RDB快照 + AOF增量
- 兼具两者优点
四、内存管理与淘汰策略
1. 内存分配
# Jemalloc分配器:
- 减少内存碎片
- 多线程优化
- 自动内存管理
2. 内存优化
# 配置优化:
maxmemory 1gb # 最大内存限制
maxmemory-policy allkeys-lru # 淘汰策略
hash-max-ziplist-entries 512 # Hash使用ziplist的最大元素数
hash-max-ziplist-value 64 # Hash使用ziplist的最大值长度
# 使用建议:
1. 使用合适的数据类型
2. 控制key长度
3. 使用批量操作
4. 开启内存碎片整理
3. 淘汰策略
# 8种策略:
1. noeviction: 不淘汰,写操作报错(默认)
2. allkeys-lru: 从所有key中淘汰最近最少使用
3. volatile-lru: 从设置过期时间的key中淘汰LRU
4. allkeys-random: 随机淘汰所有key
5. volatile-random: 随机淘汰有过期时间的key
6. volatile-ttl: 淘汰即将过期的key
7. allkeys-lfu: 从所有key中淘汰最不经常使用(4.0+)
8. volatile-lfu: 从有过期时间的key中淘汰LFU(4.0+)
# LRU vs LFU:
- LRU: 最近最少使用,适合热点数据变化
- LFU: 最不经常使用,适合长期热点数据
五、主从复制
1. 复制原理
# 同步过程:
1. 从库发送SYNC命令
2. 主库执行BGSAVE生成RDB
3. 主库发送RDB给从库
4. 从库加载RDB
5. 主库发送缓冲区命令
6. 命令传播阶段
# 全量同步 vs 增量同步:
- 全量同步:初次连接或断开太久
- 增量同步:断线重连,通过复制偏移量
2. 配置示例
# 主库配置
bind 0.0.0.0
port 6379
requirepass masterpass
# 从库配置
slaveof 127.0.0.1 6379
masterauth masterpass
slave-read-only yes
3. 复制问题
# 常见问题:
1. 数据不一致:网络延迟、主从延迟
2. 复制风暴:多个从库同时全量同步
3. 脑裂:主库假死,多个主库
# 解决方案:
1. 合理设置repl-backlog-size
2. 使用树状复制结构
3. 合理设置min-slaves-to-write
六、哨兵模式
1. 哨兵功能
# 核心功能:
1. 监控:检查主从是否正常
2. 通知:API通知故障
3. 自动故障转移:主库故障时选举新主
4. 配置提供者:客户端获取主库地址
2. 配置示例
# sentinel.conf
port 26379
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 30000
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 180000
3. 故障转移流程
1. 主观下线:单个哨兵认为主库下线
2. 客观下线:多个哨兵确认主库下线
3. 选举Leader哨兵
4. Leader哨兵选择新主库
5. 切换主从关系
6. 通知其他从库和新主库
七、集群模式
1. 集群架构
# 数据分片:
- 16384个哈希槽
- 每个节点负责部分槽
- 客户端路由:CRC16(key) % 16384
# 节点类型:
- 主节点:处理读写,故障转移
- 从节点:复制主节点,故障时升级
2. 集群搭建
# 创建集群
redis-cli --cluster create \
127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 \
127.0.0.1:7004 127.0.0.1:7005 127.0.0.1:7006 \
--cluster-replicas 1
# 查看集群信息
redis-cli -c -p 7001 cluster nodes
redis-cli -c -p 7001 cluster info
3. 集群特性
# 优点:
- 自动数据分片
- 主从自动切换
- 可线性扩展
- 高可用性
# 限制:
- 不支持多数据库,只有db0
- 批量操作受限(需在同一节点)
- 事务受限
- 不支持多键操作(除非在相同节点)
八、事务与Lua脚本
1. 事务
# 事务命令
MULTI # 开启事务
SET key1 value1
SET key2 value2
EXEC # 执行事务
DISCARD # 取消事务
WATCH key # 监视key,乐观锁
2. 事务特性
# 特点:
1. 原子性:要么全部执行,要么全部不执行
2. 隔离性:事务中的命令顺序执行
3. 不支持回滚:执行失败后继续执行
4. 不满足一致性:中途失败不保证数据一致
3. Lua脚本
-- 示例:限流脚本
local key = KEYS[1]
local limit = tonumber(ARGV[1])
local current = tonumber(redis.call('get', key) or "0")
if current + 1 > limit then
return 0
else
redis.call("INCRBY", key, 1)
redis.call("EXPIRE", key, 60)
return 1
end
# 执行Lua脚本
redis-cli --eval script.lua key1 key2 , arg1 arg2
# 或
EVAL "script" numkeys key1 key2 arg1 arg2
九、缓存设计与问题
1. 缓存穿透
# 问题:查询不存在的数据,请求直达数据库
# 解决方案:
1. 缓存空值:SET key null 5
2. 布隆过滤器:判断key是否存在
3. 参数校验:过滤非法请求
2. 缓存雪崩
# 问题:大量key同时过期,请求直达数据库
# 解决方案:
1. 设置随机过期时间
2. 热点数据永不过期
3. 集群部署,主从复制
4. 限流降级
3. 缓存击穿
# 问题:热点key过期,大量请求直达数据库
# 解决方案:
1. 互斥锁:SETNX锁
2. 永不过期+异步更新
3. 逻辑过期:设置业务过期时间
4. 缓存更新策略
# 1. Cache Aside(旁路缓存)
- 读:先读缓存,无则读DB,再写缓存
- 写:先写DB,再删缓存
# 2. Read/Write Through
- 应用只操作缓存
- 缓存同步写DB
# 3. Write Behind
- 应用只操作缓存
- 缓存异步批量写DB
十、性能优化
1. 性能调优
# 配置优化:
1. 设置最大内存和淘汰策略
2. 合理设置持久化策略
3. 优化网络配置
4. 调整内核参数
# 使用优化:
1. 使用Pipeline批量操作
2. 避免大key(>10KB)
3. 使用合适的数据结构
4. 合理设置过期时间
2. Pipeline vs 事务
# Pipeline:
- 批量发送命令,减少RTT
- 不保证原子性
- 性能提升明显
# 事务:
- 命令打包执行
- 保证原子性
- 性能低于Pipeline
3. 大key优化
# 查找大key
redis-cli --bigkeys
redis-cli --memkeys
# 分析内存
redis-cli -p 6379 --memkeys --largest 10
INFO memory
十一、监控与运维
1. 监控命令
# 实时监控
redis-cli --stat # 统计信息
redis-cli --bigkeys # 大key分析
redis-cli monitor # 监控命令
redis-cli --latency # 延迟测试
redis-cli --intrinsic-latency 100 # 内部延迟
# 状态信息
INFO # 全部信息
INFO memory # 内存信息
INFO clients # 客户端信息
INFO stats # 统计信息
INFO replication # 复制信息
INFO cpu # CPU信息
2. 性能指标
# 关键指标:
1. 内存使用率:used_memory/maxmemory
2. 命中率:keyspace_hits/(keyspace_hits+keyspace_misses)
3. 连接数:connected_clients
4. 命令处理:instantaneous_ops_per_sec
5. 网络流量:total_net_input_bytes/total_net_output_bytes
6. 延迟:响应时间
3. 运维命令
# 慢查询
CONFIG SET slowlog-log-slower-than 10000
CONFIG SET slowlog-max-len 128
SLOWLOG GET 10
SLOWLOG LEN
SLOWLOG RESET
# 客户端管理
CLIENT LIST
CLIENT SETNAME client1
CLIENT GETNAME
CLIENT KILL ip:port
CLIENT PAUSE 1000 # 暂停客户端1000ms
# 键管理
KEYS pattern # 慎用,阻塞
SCAN cursor [MATCH pattern] [COUNT count] # 非阻塞遍历
OBJECT encoding key
OBJECT refcount key
OBJECT idletime key
十二、应用场景
1. 分布式锁
-- 加锁
SET lock_key unique_value NX PX 30000
-- 解锁Lua脚本
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
2. 限流
-- 令牌桶算法
local key = KEYS[1]
local rate = tonumber(ARGV[1]) -- 令牌生成速率
local capacity = tonumber(ARGV[2]) -- 桶容量
local now = tonumber(ARGV[3]) -- 当前时间
local requested = tonumber(ARGV[4]) -- 请求令牌数
local fill_time = capacity / rate
local ttl = math.floor(fill_time * 2)
local last_tokens = tonumber(redis.call("get", key .. ":tokens"))
if last_tokens == nil then
last_tokens = capacity
end
local last_refreshed = tonumber(redis.call("get", key .. ":timestamp"))
if last_refreshed == nil then
last_refreshed = 0
end
local delta = math.max(0, now - last_refreshed)
local filled_tokens = math.min(capacity, last_tokens + (delta * rate))
local allowed = filled_tokens >= requested
local new_tokens = filled_tokens
if allowed then
new_tokens = filled_tokens - requested
end
redis.call("setex", key .. ":tokens", ttl, new_tokens)
redis.call("setex", key .. ":timestamp", ttl, now)
return allowed and 1 or 0
3. 排行榜
# 添加分数
ZADD leaderboard 1000 "user1"
ZADD leaderboard 800 "user2"
# 获取排名
ZREVRANK leaderboard "user1" # 从高到低排名
ZRANK leaderboard "user1" # 从低到高排名
# 获取前10名
ZREVRANGE leaderboard 0 9 WITHSCORES
十三、常见面试题
1. 基础问题
Q1: Redis为什么快?
A:
1. 基于内存操作
2. 单线程避免上下文切换
3. IO多路复用
4. 高效的数据结构
5. 合理的编码方式
Q2: Redis单线程为什么能处理高并发?
A:
1. IO多路复用:epoll/kqueue
2. 内存操作速度快
3. 避免锁竞争
4. 6.0+多线程处理网络IO
Q3: Redis过期键删除策略?
A:
1. 定期删除:定期随机检查删除
2. 惰性删除:获取时检查是否过期
2. 进阶问题
Q4: Redis持久化时数据修改怎么办?
A:
1. RDB:fork子进程,写时复制
2. AOF:AOF缓冲区,子进程重写不影响主进程
Q5: Redis集群数据如何分布?
A:
- 16384个哈希槽
- CRC16(key) % 16384计算槽位
- 每个节点负责部分槽
- 客户端重定向
Q6: Redis事务为什么不支持回滚?
A:
1. 设计哲学:简单快速
2. 错误通常由编程错误导致
3. 回滚会增加复杂性
4. 开发者应保证命令正确性
3. 实战问题
Q7: 如何实现分布式锁?
A:
1. SET key value NX PX timeout
2. value使用唯一标识
3. 解锁时用Lua脚本保证原子性
4. 设置锁续期
Q8: 如何实现延迟队列?
A:
1. 使用Sorted Set,score为执行时间
2. 定时任务获取到期的任务
3. 使用Stream数据结构
Q9: 大key有什么问题?如何解决?
A:
问题:
1. 内存不均
2. 阻塞操作
3. 网络拥塞
4. 迁移困难
解决:
1. 拆分大key
2. 压缩value
3. 使用合适的数据结构
4. 定期清理
十四、Redis 6.0+ 新特性
1. 多线程IO
# 配置:
io-threads 4
io-threads-do-reads yes
# 特点:
- 多线程处理网络IO
- 单线程处理命令
- 默认关闭,需手动开启
2. 客户端缓存
# 服务端辅助的客户端缓存
CLIENT TRACKING ON|OFF
RESP3协议支持
3. ACL访问控制
# ACL配置
ACL SETUSER alice on >password ~cached:* +get
ACL LIST
ACL GETUSER alice
4. SSL/TLS支持
# 加密连接
tls-port 6379
tls-cert-file redis.crt
tls-key-file redis.key
面试准备建议:
-
深入理解数据结构和内部实现
-
掌握持久化、复制、集群原理
-
熟悉缓存问题和解决方案
-
了解性能优化和监控
-
准备实际应用场景案例
-
关注新版本特性
-
动手实践常见功能实现
高频考点:
-
Redis数据结构与适用场景
-
持久化机制对比
-
缓存穿透/雪崩/击穿解决方案
-
主从复制和哨兵原理
-
集群数据分片原理
-
事务和Lua脚本
-
分布式锁实现
-
性能优化策略
更多推荐




所有评论(0)