引言:为什么 Redis 是现代系统的"标配"缓存
Redis(Remote Dictionary Server)由 Salvatore Sanfilippo 于 2009 年开源,凭借亚毫秒级响应、丰富的数据结构、原子操作和持久化能力,已成为高性能数据库缓存、消息队列、分布式锁、实时排行榜等领域的事实标准。从初创公司到全球互联网巨头(Twitter、GitHub、美团、微博),Redis 几乎无处不在。本文从 Redis 底层数据结构入手,深入剖析内存模型、持久化策略、主从复制、哨兵与集群模式、分布式锁、Lua 脚本、流处理与 Redis 7.x 新功能,最后梳理生产环境调优与高可用架构实践。
一、Redis 核心数据结构底层实现
1.1 简单动态字符串(SDS)
Redis 没有沿用 C 字符串,而是实现了 SDS(Simple Dynamic String):
- O(1) 获取长度:结构体包含 len 属性,无需遍历
- 自动扩容:空间预分配(增长后 <1MB 双倍,≥1MB 每次 1MB),惰性空间释放
- 二进制安全:可容纳 '\0',使用 len 而非 \0 判断结尾
- 减少内存重分配次数:避免频繁 malloc/free 的开销
1.2 压缩列表(ZipList)
Vec 结构的连续内存块,用于小数据量的 Hash/Set/Sorted Set:
zlbytes | zltail | zllen | entry1 | entry2 | ... | entryN | zlend
每个 entry 包含:
prevlen(前一个 entry 长度,用于反向遍历)
encoding(编码类型:整数/字符串)
entrylen(当前 entry 长度)
data(实际数据)
当元素大小或数量超过阈值(hash-max-ziplist-entries 512 / hash-max-ziplist-value 64)时编码转为标准哈希表(dict)。
1.3 QuickList(3.2+ 引入)
将双向链表与 ziplist 结合:每个链表节点是指向一个 ziplist 的指针。兼顾插入性能(在 ziplist 边界无需全量分裂)和内存局部性(单个 ziplist 内连续存储)。
1.4 跳表(SkipList)
Sorted Set(zset)在元素较多时使用跳表实现,每层都是有序链表,高层"快速通道"实现 O(log N) 复杂度:
当数据量 > 128 且元素长度 > 64 时使用跳表,否则用 ziplist。
1.5 整数集合(IntSet)
Set 在仅包含整数且数量小于 set-max-intset-entries(默认 512) 时使用 intset:内部是有序的 int16/int32/int64 数组,支持升级(upgrade)但不支持降级,二分查找 O(log N)。
二、内存淘汰策略
2.1 八种淘汰策略
| 策略 | 说明 | 适用场景 |
|---|---|---|
| noeviction | 写入返回错误(默认) | 数据不可丢场景 |
| allkeys-lru | 在所有 key 中按 LRU(最近最少使用)淘汰 | 纯缓存场景 |
| volatile-lru | 在设置了 TTL 的 key 中按 LRU 淘汰 | 热点缓存 + 持久化共存 |
| allkeys-lfu | 在所有 key 中按 LFU(最不经常使用)淘汰 | 长尾访问模式 |
| volatile-lfu | 在 TTL 集合中按 LFU 淘汰 | 频率敏感场景 |
| allkeys-random | 随机淘汰任意 key | 均匀访问模式 |
| volatile-random | 随机淘汰 TTL 内 key | 简单过期策略 |
| volatile-ttl | 优先淘汰最接近过期的 key | 时效敏感场景 |
2.2 LRU/LFU 实现
- 近似 LRU:Redis 不使用严格 LRU 链表(内存开销大),而是随机采样 N 个 key(默认 5),从中淘汰最久未访问的。3.0+ 使用 pool 提升淘汰精度
- LFU 模式:24-bit LRU 字段拆分为 16-bit 对数计数器(访问频率)和 8-bit 时间衰减字段,防止"历史热 key"长期占据内存。配置
lfu-log-factor(默认 10)和lfu-decay-time(默认 1)控制
三、持久化机制
3.1 RDB(Redis Database)
内存快照文件,fork 子进程 后通过 Copy-On-Write 写入磁盘:
- 优势:文件紧凑(经 LZF 压缩),加载快,适合全量复制和灾备
- 劣势:两次 RDB 快照间数据可能丢失;fork 大内存实例时可能阻塞主线程(几百毫秒至秒级)
- 触发方式:save 900 1 / save 300 10 / save 60 10000;bgsave 命令;主从复制自动触发
3.2 AOF(Append Only File)
记录每次写操作的命令日志:
- 同步策略:
appendfsync always(每条都 fsync)、everysec(每秒 fsync,默认)、no(交由 OS) - AOF 重写(Rewriting):fork 子进程,基于当前内存状态生成重写后的 AOF 文件(等价于重建数据集),显著缩小文件体积
- 劣势:体积通常大于 RDB;同等数据集恢复速度慢于 RDB
3.3 混合持久化(4.0+)
Redis 4.0 引入 aof-use-rdb-preamble yes:AOF 重写文件的前半段为 RDB 格式(快速加载),后半段为 AOF 格式(增量命令),兼顾 RDB 的快速恢复和 AOF 数据安全。
3.4 fork 优化——COW(Copy-On-Write)
RDB 和 AOF 重写都需要 fork 子进程。fork 本身通常 O(1)(Linux 的 Copy-On-Write MMU 机制,父子共享物理内存页)。风险:当主进程大量写入时触发大量 page copy,可能导致内存翻倍和延迟抖动。建议 overcommit_memory=1 避免 fork 失败。
四、主从复制与高可用
4.1 复制原理
1. Slave 发送 PSYNC runId offset
2. Master 判断:(runId 匹配 + offset 在 backlog 内) → 增量同步
否则 → 全量同步(bgsave + RDB + backlog)
3. 全量同步期间写入暂存到复制缓冲区
4. Slave 加载 RDB → 进入命令传播阶段
4.2 复制 ID(Replication ID)
Redis 4.0 引入 replid(而非 runId)作为集群标识:
- Master Replid:由服务器启动时随机生成
- Slave 成为 Master 后:保持原有 replid 作为 secondary id(实现无数据丢失的部分 resynchronization)
- repl backlog(复制积压缓冲区):环形缓冲区,默认 1MB,建议大流量场景调整到 64MB+
4.3 哨兵(Sentinel)模式
监控:3+ 哨兵节点每秒 PING 主从节点
通知:通过 __sentinel__:hello 频道共享信息
自动故障转移:
- 多数哨兵判定 Master 客观下线(SDown + ODown)
- 领导者哨兵(Raft Leader)被选出
- 从以下条件选择新 Master:优先级(replica-priority)> 复制数据完整性(offset 最大)> 运行 ID 最小
- 其余 Slave 切换到新 Master,配置文件被自动更新
4.4 集群(Cluster)模式
Redis Cluster 在 3.0 引入,提供去中心化的分布式方案:
- Slot 分片:集群固定 16384 个 slot,每个 key 通过 CRC16(key) & 16383 确定 slot
- 节点通信:Gossip 协议(PING/PONG)交换集群状态,非中心化协调
- 重定向:MOVED(slot 最终归属)和 ASK(迁移过程中临时重定向)
- 故障检测:超过半数 Master 判定某 Master 失效时,其 Slave 发起选举成为新 Master
五、Redis 分布式锁与原子操作
5.1 基本锁模式
// 加锁:NX(仅空时设置)+ PX(毫秒过期)
SET resource_name random_value NX PX 30000
// 释放锁:仅当 value 匹配(防误释放其他客户端的锁)
EVAL "if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
else
return 0
end" 1 resource_name random_value
5.2 Redlock 算法
Redis 作者提出的多节点分布式锁方案:
- 获取当前时间 T1
- 依次向 N 个独立 Redis 节点发送加锁请求(总耗时 < 锁 TTL)
- 获取当前时间 T2,若在 T1 + TTL > T2(超过半数节点成功)则认为加锁成功
- 锁实际有效期为:TTL - (T2 - T1)
- 释放时向所有节点发送释放命令(即使加锁失败也要释放)
争议:Martin Kleppmann 指出 Redlock 在系统时钟跳变、进程 GC 暂停、网络延迟时存在风险。Zookeeper 方案(基于顺序临时节点+watch)更适合强一致性场景。
5.3 Redisson 实现
Java Redisson 库实现了看门狗机制:
- 加锁后启动后台守护线程,每 TTL/3 续期
- 只有显式取消看门狗时才会停止续期,避免因 GC/处理时间过长导致锁提前释放
- 支持 MultiLock(Redlock)、ReadWriteLock、Semaphore、CountDownLatch 等
六、Pub/Sub 与 Stream
6.1 发布订阅(Pub/Sub)
经典的 fire-and-forget 模式:消息发出即丢弃(无持久化),订阅者必须在线。适合实时通知和事件广播场景。
6.2 Stream(Redis 5.0+)
借鉴 Kafka 设计思想的强日志型数据结构:
- 消息持久化:每条消息有写入时间戳的递增 ID(如 1234567890-0),不会自动删除
- 消费者组:支持组内竞争消费、未 ACK 消息重投(Pending Entries List)
- XRANGE/XREVRANGE:范围查询消息历史
- XCLAIM:将超时未 ACK 的消息转给其他消费者
- MAXLEN / XTRIM:限制 stream 最大长度(近似修剪)
典型用途:事件溯源、消息队列替代方案(轻量级)、CDC(Change Data Capture)
七、Lua 脚本
7.1 脚本执行模型
Redis 通过 EVAL/EVALSHA 执行 Lua 脚本:
- 原子性:脚本执行期间不执行其他命令(类似事务)
- 禁止:在脚本中调用
TIME、RANDOM、SPOP等命令时,结果会被随机写入缓冲并在复制/集群中传播 - 脚本缓存:通过 SHA1 指纹复用已加载脚本(
SCRIPT LOAD+EVALSHA)
7.2 典型实践:令牌桶限流
-- KEYS[1]: 限流 key ARGV[1]: 桶容量 ARGV[2]: 填充速率/秒
local key = KEYS[1]
local capacity = tonumber(ARGV[1])
local rate = tonumber(ARGV[2])
local now = tonumber(redis.call('TIME')[1])
local fill_time = capacity / rate
local ttl = math.floor(fill_time * 2)
local last_tokens = tonumber(redis.call('get', key) or capacity)
local last_refreshed = tonumber(redis.call('get', key .. ':ts') or now)
local delta = math.max(0, now - last_refreshed)
local filled_tokens = math.min(capacity, last_tokens + delta * rate)
local allowed = filled_tokens >= 1
local new_tokens = filled_tokens
if allowed then
new_tokens = filled_tokens - 1
end
redis.call('setex', key, ttl, new_tokens)
redis.call('setex', key .. ':ts', ttl, now)
return allowed and 1 or 0
八、Redis 7.x 新特性
| 版本 | 特性 | 说明 |
|---|---|---|
| Redis 6.0 | I/O 多线程 | 线程化网络读写(默认关闭),命令执行仍单线程 |
| Redis 6.2 | ACL 完善 | 基于 key pattern + command channel 的细粒度权限控制 |
| Redis 6.2 | Function 替代 Scripting | 持久化服务端函数库,支持多语言 |
| Redis 7.0 | Sharded Pub/Sub | 集群模式下 channel 按 slot 路由,解决 fan-out 跨节点广播问题 |
| Redis 7.0 | Command ACL 精细化 | 支持按 subcommand 和 key pattern 指定权限 |
| Redis 7.2 | Hash Field 过期 | HFIELD EXPIRES(8.2 正式 GA) |
| Redis 8.0 | 数据结构扩展 | Tensor 类型、Bloom Filter 增强 |
九、生产环境调优实践
9.1 大 Key 和热 Key 问题
大 Key 危害:读写阻塞主线程;全量复制时耗尽输出缓冲区;迁移时 slot 超时。
检测:redis-cli --bigkeys(基于 SCAN 采样)、MEMORY USAGE key。
热 Key 危害:单节点 CPU/带宽瓶颈。
方案:客户端本地缓存 + TTL(主从架构下);Redis 代理层(如 Envoy、Codis)实现 key 路由分散;使用 read replica 分担读压力。
9.2 连接管理与 Pipeline
- Pipeline:将多个命令一次发给服务端,减少网络 RTT(注意:pipeline 不是事务,不保证原子性)
- 连接池:避免频繁建立 TCP 连接,合理设置 maxTotal / maxIdle / minIdle
- 避免阻塞命令:KEYS(用 SCAN 替代)、SMEMBERS(用 SSCAN)、FLUSHALL(异步变体 FLUSHALL ASYNC)
9.3 内存优化
- 内存碎片:启用
activedefrag yes(4.0+)自动整理碎片,设置碎片率阈值(active-defrag-ignore-bytes、active-defrag-threshold-lower) - 编码降级:使用
hash-max-ziplist-entries、list-max-listpack-entries控制内部编码 - 共享对象:小整数(0-9999)使用共享对象池(
maxmemory-policy),可节省内存
9.4 核心配置参考
# /etc/redis/redis.conf 生产推荐配置
maxmemory 8gb
maxmemory-policy allkeys-lru
maxmemory-samples 10
# AOF 持久化
appendonly yes
appendfsync everysec
aof-use-rdb-preamble yes
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
# 网络
tcp-backlog 511
timeout 300
tcp-keepalive 60
# 复制
repl-backlog-size 64mb
repl-disable-tcp-nodelay no
repl-diskless-sync yes # 6.2+ 支持无磁盘全量同步
# 安全
requirepass your-strong-password
rename-command FLUSHALL ""
rename-command CONFIG ""
# 慢日志
slowlog-log-slower-than 10000 # 微秒
slowlog-max-len 128
十、高可用架构选型
| 模式 | 特点 | 适用场景 |
|---|---|---|
| 单实例 | 简单、无冗余 | 开发/测试 |
| 主从 + 哨兵 | 自动故障转移,读写分离 | 中小规模(≤ 50GB),读多写少 |
| Redis Cluster | 去中心化,自动分片 | 大规模数据(≥ 100GB),高写入吞吐 |
| Codis/Redis Cluster Proxy | 中心化代理,客户端透明 | 跨语言兼容、复杂集群管理 |
| 云托管服务 | 自动扩容、一键备份 | AWS ElastiCache / 阿里云 ApsaraDB |
10.1 跨地域复制(CRDB,Redis Enterprise)
Redis Enterprise 的 CRDB(Active-Active)基于 CRDT(Conflict-Free Replicated Data Types)实现最终一致性的跨地域多活,适用于全球部署的低延迟写入场景。社区版可通过自定义方案(Debezium + Kafka Connect + Redis Sink)实现准实时同步。
十一、监控与排错
11.1 核心监控指标
| 指标 | 命令 | 告警建议 |
|---|---|---|
| 内存使用率 | INFO memory | > 80% |
| 命令延迟 P99 | LATENCY DOCTOR / --latency | > 5ms |
| 每秒命令 | INFO stats instantaneous_ops_per_sec | 环比波动 30%+ |
| 连接数 | INFO clients connected_clients | > maxclients 80% |
| 键命中率 | INFO stats keyspace_hits/(hits+misses) | < 95% |
| 主从复制延迟 | INFO replication master_repl_offset - slave_repl_offset | > 1s |
| 阻塞命令 | SLOWLOG GET | 定期审查 |
| 内存碎片率 | INFO memory mem_fragmentation_ratio | < 0.8 或 > 1.5 |
11.2 DBA 工具链
- redis-cli --bigkeys / --hotkeys:检测大 key 和热 key
- redis-cli --memkeys:按内存占用排序显示 key
- MEMORY STATS:详细内存统计(含 arena、RSS、fragmentation)
- LatencyMonitor:LATENCY DOCTOR 报告各耗时子系统状态
- Redis Exporter + Prometheus + Grafana:生产监控标配组合
结语
Redis 凭借极简的设计哲学("做一件事情并做到极致")和持续的性能优化,已成为现代软件架构不可或缺的基础设施组件。理解其底层数据结构(SDS、ZipList、SkipList)、内存淘汰与持久化取舍、复制协议、分布式锁的局限,以及在实践中规避大 Key、热 Key 和内存碎片等陷阱,是构建高性能、高可用 Redis 系统的关键。随着 Redis 8.x 和 Redis StackDB 的发展,Redis 已从纯 Key-Value 缓存进化为多模数据库(字符串、JSON、向量、时序、布隆过滤器),不断拓宽其能力边界。

发表评论 取消回复