一、Redis 概述
1.1 什么是 Redis
Redis(Remote Dictionary Server)是一款开源的、基于内存的高性能键值对(Key-Value)数据库。与传统关系型数据库不同,Redis 将所有数据存储在内存中,读写速度极快,每秒可处理数万至数十万次请求。
1.2 核心特性
- 内存存储:数据主要在内存中操作,读写速度可达 10 万+ QPS
- 丰富的数据结构:支持字符串、哈希、列表、集合、有序集合等
- 持久化支持:提供 RDB 快照与 AOF 日志两种持久化方案
- 高可用性:支持主从复制、哨兵模式、集群模式
- 原子性:单线程模型保证命令原子性执行,支持事务和 Lua 脚本
1.3 适用场景
- 缓存层:减轻数据库压力,加速热点数据访问
- 会话存储:保存用户登录状态或临时数据
- 消息队列:通过 List 或 Stream 实现轻量级消息传递
- 分布式锁:利用 SETNX 命令实现资源互斥访问
- 实时排行榜:有序集合(ZSET)支持范围查询和排名计算
1.4 安装与基本配置
# 下载安装
wget https://download.redis.io/redis-stable.tar.gz
tar xzf redis-stable.tar.gz
cd redis-stable
make && make install
核心配置文件 redis.conf 关键参数:
bind 0.0.0.0 # 允许远程连接
port 6379 # 默认端口
daemonize yes # 守护进程模式
requirepass yourpass # 设置密码
maxmemory 2gb # 内存限制
maxmemory-policy allkeys-lru # 内存淘汰策略
二、核心数据结构与命令
2.1 数据类型总览
Redis 原生支持的数据结构包括:Strings、Hashes、Lists、Sets、Sorted Sets、Bitmaps、Bitfields、HyperLogLog、地理空间索引(Geospatial)和 Streams。
2.2 字符串(String)
应用场景:计数器、缓存、分布式锁
常用命令:
SET key value [EX seconds] # 设置键值,可选过期时间
GET key # 获取值
INCR key # 原子递增
DECRBY key 10 # 原子递减指定数值
MSET key1 value1 key2 value2 # 批量设置
MGET key1 key2 # 批量获取
示例:实现访问量统计
INCR website:views
GET website:views
注意:SET 会覆盖已存在的 key,SETNX 则只在 key 不存在时设置;SETEX 可设置带过期时间的键值。
2.3 哈希(Hash)
应用场景:存储对象属性(如用户信息)
常用命令:
HSET user:1001 name "Alice" age 25
HGET user:1001 name
HGETALL user:1001 # 获取所有字段
HINCRBY user:1001 age 1 # 字段递增
HDEL user:1001 age # 删除字段
2.4 列表(List)
应用场景:消息队列、最新消息推送
常用命令:
LPUSH messages "msg1" # 左侧插入
RPUSH messages "msg2" # 右侧插入
LPOP messages # 左侧弹出
RPOP messages # 右侧弹出
LRANGE messages 0 -1 # 获取所有元素
LLEN messages # 获取列表长度
2.5 集合(Set)
应用场景:去重、标签系统、共同好友
常用命令:
SADD tags "redis" "database" # 添加元素
SMEMBERS tags # 获取所有元素
SISMEMBER tags "redis" # 判断是否存在
SINTER set1 set2 # 交集
SUNION set1 set2 # 并集
SDIFF set1 set2 # 差集
2.6 有序集合(Sorted Set / ZSET)
应用场景:排行榜、优先级队列、延时队列
常用命令:
ZADD leaderboard 100 "Alice" 200 "Bob"
ZREVRANGE leaderboard 0 2 WITHSCORES # 获取前3名及分数
ZINCRBY leaderboard 50 "Alice" # 增加分数
ZRANK leaderboard "Alice" # 获取排名
ZREM leaderboard "Bob" # 删除元素
2.7 通用命令
KEYS pattern # 查找匹配的 key(生产环境慎用!)
EXISTS key # 判断 key 是否存在
EXPIRE key seconds # 设置过期时间
TTL key # 查看剩余过期时间
DEL key # 删除 key
UNLINK key # 异步删除(非阻塞)
TYPE key # 查看 key 的数据类型
三、持久化机制
持久化是确保数据不丢失的关键机制。Redis 提供了两种持久化方案。
3.1 RDB 持久化(快照)
原理:定时将内存数据保存到磁盘文件(dump.rdb)
触发方式:
- SAVE 命令:同步执行,会阻塞主进程
- BGSAVE 命令:异步执行,fork 子进程完成持久化
- Redis 停机时:自动执行一次 RDB
- 自动触发条件:配置文件中的 save 规则
配置示例:
save 900 1 # 900秒内至少1次修改触发
save 300 10 # 300秒内至少10次修改触发
save 60 10000 # 60秒内至少10000次修改触发
dbfilename dump.rdb
dir /var/lib/redis
工作原理:bgsave 时 fork 主进程得到子进程,子进程共享主进程的内存数据。采用 copy-on-write 技术:主进程读操作访问共享内存,写操作则拷贝一份数据再执行。
优缺点:
- ✅ 文件体积小,恢复速度快
- ✅ 适合备份和灾难恢复
- ❌ 可能丢失最后一次快照后的数据
- ❌ fork 子进程耗时,内存占用翻倍
3.2 AOF 持久化(追加日志)
原理:记录所有写操作命令,重启时重放
配置:
appendonly yes
appendfilename "appendonly.aof"
appendfsync everysec # 同步策略
同步策略对比:
| 策略 | 说明 | 安全性 | 性能 |
|---|---|---|---|
| always | 每个写命令都同步 | 最高 | 最差 |
| everysec | 每秒同步一次 | 较高(最多丢1秒) | 平衡(推荐) |
| no | 由操作系统决定 | 较低 | 最好 |
AOF 重写:AOF 文件会记录对同一个 key 的多次写操作,但只有最后一次有效。通过 bgrewriteaof 命令可执行重写,用最少命令达到相同效果,解决文件膨胀问题。
3.3 混合持久化(Redis 4.0+)
最佳实践:同时开启 RDB + AOF,或启用混合持久化。
混合持久化模式下,重写后的新 AOF 文件开头是 RDB 格式的全量数据,后续是增量 AOF 日志。重启时先加载 RDB 内容,再重放 AOF 日志,兼顾恢复速度与数据安全性。
配置:
aof-use-rdb-preamble yes
四、高可用架构
单机 Redis 存在单点故障风险,需要通过高可用架构保障服务连续性。
4.1 主从复制(Replication)
架构:一主多从,从节点同步主节点数据,实现读写分离
建立主从连接三种方式:
# 方式一:客户端命令
SLAVEOF <masterip> <masterport>
# 方式二:启动参数
redis-server --slaveof <masterip> <masterport>
# 方式三:配置文件
replicaof 192.168.1.100 6379
复制流程:
- 全量同步:从服务器发送 PSYNC,主服务器执行 BGSAVE 生成 RDB 并发送
- 增量同步:主服务器将复制积压缓冲区中的命令持续发送给从服务器
触发全量同步的情况:首次连接、复制 ID 不匹配、偏移量不在积压缓冲区范围内。
特点:
- ✅ 简单易实现,低成本扩展读性能
- ✅ 数据冗余备份
- ❌ 故障切换需人工干预
- ❌ 写性能无法扩展
4.2 哨兵模式(Sentinel)
为什么需要哨兵:主从复制中主节点宕机后需要人工介入,哨兵实现了自动故障转移。
核心功能:
- 监控:监控主从 Redis 实例是否在线
- 自动故障转移:主节点宕机后自动将从节点提升为主节点
- 通知:实例下线后通知其他组件
工作流程:
- 主观下线(SDOWN) :单个哨兵认为 Redis 实例不可用
- 客观下线(ODOWN) :多个哨兵达成共识(达到 quorum 配置值),认为主节点确实下线
- 领导者选举:哨兵集群通过 Raft 算法选举出一个领导者哨兵
- 故障转移:领导者哨兵从从节点中选举新主节点,通知其他从节点复制新主节点
部署建议:哨兵必须集群部署,奇数个节点(最低 3 节点)。
脑裂问题:网络分区可能导致同一个 Redis 系统出现多个主节点,造成数据不一致。解决方案:配置合理的 quorum 数量,部署奇数个哨兵,客户端使用哨兵模式的连接池。
4.3 Redis Cluster(集群模式)
适用场景:数据量过大、需要水平扩展的大规模高并发场景。
核心原理:基于哈希槽(Hash Slot)进行数据分片。
哈希槽算法:
hash_slot = CRC16(key) % 16384
一共有 16384 个槽位,每个分片负责一部分槽位。
分片分配示例(3 个分片):
- 0号分片:[0, 5461],共 5462 个槽位
- 1号分片:[5462, 10923],共 5462 个槽位
- 2号分片:[10924, 16383],共 5460 个槽位
三种分片算法对比:
| 算法 | 说明 | 扩容成本 | 数据均衡 |
|---|---|---|---|
| 哈希求余 | key 对分片数取模 | 极高(大量数据搬运) | 均衡 |
| 一致性哈希 | 环形空间 + 顺时针查找 | 较低 | 可能倾斜 |
| 哈希槽(Redis 采用) | 16384 个槽位灵活分配 | 可控 | 可灵活调整 |
集群部署示例:
redis-cli --cluster create \
192.168.0.1:7000 192.168.0.2:7001 \
192.168.0.3:7002 --cluster-replicas 1
五、内存管理与淘汰策略
5.1 过期键删除策略
Redis 采用 惰性删除 + 定期删除 的组合策略:
- 惰性删除:访问 key 时检查是否过期,过期则删除
- 定期删除:后台周期性执行
activeExpireCycle函数,从设置了过期时间的键中随机抽样检测并删除
性能控制:每次执行最多占用 CPU 时间不超过 25ms,每次默认检查 20 个键,采用随机抽样而非全表扫描。
大量键同时过期的优化:
- 分散过期时间:添加随机扰动值
- 监控预警:通过
INFO命令监控expired_keys指标 - 使用 UNLINK 而非 DEL 进行异步删除
5.2 内存淘汰策略
当内存达到 maxmemory 限制时触发淘汰。
八种淘汰策略:
| 策略 | 说明 |
|---|---|
| noeviction | 默认策略,拒绝所有可能导致内存增加的命令 |
| allkeys-lru | 从所有键中移除最近最少使用的键 |
| volatile-lru | 仅从设置了过期时间的键中移除最近最少使用的 |
| allkeys-random | 从所有键中随机移除 |
| volatile-random | 仅从设置了过期时间的键中随机移除 |
| volatile-ttl | 移除设置了过期时间且剩余 TTL 最短的键 |
| allkeys-lfu | 从所有键中移除最不经常使用的键(Redis 4.0+) |
| volatile-lfu | 仅从设置了过期时间的键中移除最不经常使用的键(Redis 4.0+) |
LRU vs LFU:
- LRU(最近最少使用):基于"最近使用过的数据很可能再次被使用"的假设。Redis 采用近似算法,通过随机采样实现
- LFU(最不经常使用):关注访问频率,适合有明显热点数据的场景。通过衰减机制和频率计数器实现
配置建议:
maxmemory 2gb
maxmemory-policy allkeys-lru
六、事务、管道与 Lua 脚本
6.1 管道(Pipeline)
目的:节约网络传输时间,提升批量操作效率
原理:客户端一次性将多个命令打包发送,服务器按顺序处理并一次性返回所有结果。
注意:Pipeline 不具备事务性,不保证"要么全部成功,要么全部失败"。
适用场景:需要一次执行大量读写操作,且命令间彼此独立。
6.2 事务(Transaction)
核心命令:
MULTI:开启事务,命令入队但不执行EXEC:提交事务,按顺序执行所有命令DISCARD:取消事务(清空队列)WATCH:乐观锁,监视 key,若被修改则事务取消
示例:
WATCH gulu
MULTI
SET gulu 2000
EXEC # 如果 gulu 被其他客户端修改,返回 nil
事务局限性:
- ❌ 不支持回滚:命令执行出错不会回滚之前的命令
- ✅ 保证命令按顺序原子执行
6.3 Lua 脚本
优势:整个脚本在服务端原子执行,网络开销最小,是保证复杂操作原子性的"终极武器"。
执行方式:
EVAL:直接执行 Lua 脚本SCRIPT LOAD+EVALSHA:预加载脚本,后续通过 SHA1 调用(推荐)
注意:Lua 脚本会占用较多计算和内存资源,过于复杂的脚本可能导致资源耗尽。
七、缓存三大问题与解决方案
7.1 缓存穿透
现象:查询数据库中根本不存在的数据,请求绕过缓存直接冲击数据库
解决方案:
- 布隆过滤器:在缓存前加一层布隆过滤器,拦截不存在的数据请求
- 空值缓存:对查询结果为 null 的数据也进行缓存(过期时间 2-5 分钟)
7.2 缓存击穿
现象:单个热点 key 在缓存过期瞬间,突发大量请求直接击穿到数据库
典型案例:秒杀商品详情、热点新闻内容
解决方案:使用 互斥锁(分布式锁)
- 获取锁的线程负责查询数据库并回填缓存
- 未获取锁的线程短暂等待后重试
- 锁超时时间需大于数据库查询时间
7.3 缓存雪崩
现象:大量缓存 key 在同一时间同时失效,导致大量请求直接打到数据库
解决方案:
- 设置缓存过期时间时添加随机值,避免同时失效
- 使用互斥锁或分布式锁
- 搭建高可用 Redis 集群
八、性能优化最佳实践
8.1 内存优化
- 使用
ziplist编码压缩小数据 - 设置合理的
maxmemory-policy - 开启内存碎片整理
8.2 批量操作
- 使用 Pipeline 提升吞吐量
- 使用
MSET/MGET替代多次SET/GET
8.3 慢查询监控
slowlog-log-slower-than 10000 # 10毫秒
slowlog-max-len 128
8.4 安全防护
requirepass your_strong_password
bind 127.0.0.1 # 仅本地访问
九、面试高频考点速查
| 考点 | 核心要点 |
|---|---|
| Redis 为什么快 | 基于内存、单线程(避免上下文切换和锁竞争)、IO 多路复用 |
| 单线程 vs 多线程 | Redis 核心操作单线程,但 Redis 6.0+ 在网络 IO 方面引入多线程 |
| RDB vs AOF | RDB:快照,恢复快但可能丢数据;AOF:日志,安全但恢复慢 |
| 缓存穿透/击穿/雪崩 | 布隆过滤器/互斥锁/过期时间加随机值 |
| 哨兵 vs 集群 | 哨兵:高可用(故障自动转移);集群:高可用 + 水平扩展(数据分片) |
| 分布式锁 | SETNX + 过期时间 + Redisson 看门狗机制 |
十、总结
Redis 作为当今最流行的内存数据库,其知识体系可以概括为三个层次:
- 基础层:数据结构与命令、持久化机制——这是日常开发的核心
- 进阶层:主从复制、哨兵、集群、事务与 Lua 脚本——解决生产环境的高可用和复杂业务需求
- 专家层:内存淘汰策略、性能调优、缓存问题治理、源码原理——应对大规模高并发场景
掌握 Redis,不仅要"会用",更要理解其背后的设计哲学——在性能与数据安全之间做精妙的权衡与取舍。
Comments NOTHING