引言:为什么 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 频道共享信息
自动故障转移:

  1. 多数哨兵判定 Master 客观下线(SDown + ODown)
  2. 领导者哨兵(Raft Leader)被选出
  3. 从以下条件选择新 Master:优先级(replica-priority)> 复制数据完整性(offset 最大)> 运行 ID 最小
  4. 其余 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 作者提出的多节点分布式锁方案:

  1. 获取当前时间 T1
  2. 依次向 N 个独立 Redis 节点发送加锁请求(总耗时 < 锁 TTL)
  3. 获取当前时间 T2,若在 T1 + TTL > T2(超过半数节点成功)则认为加锁成功
  4. 锁实际有效期为:TTL - (T2 - T1)
  5. 释放时向所有节点发送释放命令(即使加锁失败也要释放)

争议: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.0I/O 多线程线程化网络读写(默认关闭),命令执行仍单线程
Redis 6.2ACL 完善基于 key pattern + command channel 的细粒度权限控制
Redis 6.2Function 替代 Scripting持久化服务端函数库,支持多语言
Redis 7.0Sharded Pub/Sub集群模式下 channel 按 slot 路由,解决 fan-out 跨节点广播问题
Redis 7.0Command ACL 精细化支持按 subcommand 和 key pattern 指定权限
Redis 7.2Hash 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%
命令延迟 P99LATENCY 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、向量、时序、布隆过滤器),不断拓宽其能力边界。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部