在上一篇中,我们分析了 Redis 的几个核心架构:
- 单线程执行模型
- I/O 多路复用
- 高效数据结构
但真正让 Redis 在生产环境稳定运行的关键,还包括:
- 持久化策略
- Lua 原子脚本
- 内存管理
- Pipeline 优化
- Cluster 扩展
这些机制决定了 Redis 在 高并发系统中的工程表现。
Redis 持久化机制
Redis 是 内存数据库。
如果完全不持久化:
Redis restart = 数据全部丢失
因此 Redis 提供两种持久化方式:
| 机制 | 类型 |
|---|---|
| RDB | 快照 |
| AOF | 操作日志 |
RDB:快照机制
RDB 会定期生成 数据集快照。
配置示例:
save 900 1
save 300 10
save 60 10000
含义:
| 条件 | 触发 |
|---|---|
| 900 秒内 | 1 次写入 |
| 300 秒内 | 10 次写入 |
| 60 秒内 | 10000 次写入 |
执行流程
当触发保存:
Redis main process
│
│ fork()
▼
Child Process → 写入 dump.rdb
Parent Process → 继续服务请求
关键技术:
Copy-On-Write
写操作发生时才复制内存页
隐藏问题:fork 延迟
如果 Redis 数据量很大:
dataset = 10GB
fork 可能需要:
200ms - 500ms
在此期间:
Redis 停止响应请求
这也是很多系统出现 周期性延迟尖刺 的原因。
AOF:操作日志
AOF 记录所有写操作:
SET user:1 John
INCR counter
DEL key
Redis 重启时:
Replay log → 重建数据
AOF 同步策略
配置:
appendfsync always
appendfsync everysec
appendfsync no
含义:
| 策略 | 特点 |
|---|---|
| always | 最安全 最慢 |
| everysec | 推荐 |
| no | 最快 |
一般生产环境:
everysec
最多丢失:1 秒数据
AOF Rewrite
AOF 会不断增长。
例如:
SET key 1
SET key 2
SET key 3
其实只需要:
SET key 3
因此 Redis 会执行 AOF 重写:
BGREWRITEAOF
生成更小的日志。
Lua 脚本:为什么 Redis 需要 Lua
很多开发者第一次写 Redis 代码会遇到这种逻辑:
GET counter
if counter < 100
INCR counter
EXPIRE counter 60
问题:
GET 和 INCR 之间可能被其他请求插入
导致:限流失效
Lua 解决方案
Lua 脚本在 Redis 中:
原子执行
示例:
local key = KEYS[1]local limit = tonumber(ARGV[1])local ttl = tonumber(ARGV[2])local current = redis.call("GET", key)if not current then
current = 0else
current = tonumber(current)endif current < limit then
redis.call("INCR", key)
redis.call("EXPIRE", key, ttl)return 1elsereturn 0end
执行:
EVALSHA script
特点:
| 特性 | 说明 |
|---|---|
| 原子执行 | 不会被打断 |
| 减少 RTT | 一次请求 |
| 服务器逻辑 | 避免客户端协调 |
Redis 内存管理
Redis 完全运行在内存中。
因此 内存管理极其关键。
内存限制
配置:
maxmemory 2gb
当超过限制:
触发 淘汰策略。
常见策略
| 策略 | 说明 |
|---|---|
| noeviction | 拒绝写入 |
| allkeys-lru | 淘汰最近最少使用 |
| volatile-lru | 淘汰带 TTL 的 key |
| allkeys-random | 随机 |
缓存系统通常使用:
allkeys-lru
Redis LRU 不是严格 LRU
为了性能:
Redis 使用 采样算法:
随机选 N 个 key
淘汰最旧的
这样避免维护完整 LRU 链表。
内存碎片
运行一段时间后可能看到:
used_memory = 2.5GB
used_memory_rss = 4.8GB
碎片率:
1.92
原因:
- 内存分配块
- key 删除留下空洞
解决方案:
activedefrag yes
或者:定期重启 Redis
Pipeline:Redis 性能加速
很多系统 Redis 慢的原因不是 Redis 本身。
而是:
网络 RTT
例如:
1000 次 SET
如果逐条发送:
1000 次 RTT
Pipeline
Pipeline pipeline = jedis.pipelined()
for i=0..1000
pipeline.set(key,value)
pipeline.sync()
效果:
1000 请求 → 1 次 RTT
性能提升:
10x - 100x
Redis 事务的行为
Redis 提供:
MULTI
EXEC
但很多人误以为它类似 SQL 事务。
实际上:
Redis 没有 rollback。
示例:
MULTI
SET key1 value1
BADCOMMAND
SET key2 value2
EXEC
结果:
key1 成功
key2 成功
即使中间有错误。
因此:
复杂原子逻辑应该使用:
Lua Script
而不是事务。
Redis Cluster
当单机 Redis 无法满足需求时,需要:
水平扩展
Redis Cluster 使用:
16384 hash slots
计算:
CRC16(key) % 16384
分布示例:
NodeA 0-5460
NodeB 5461-10922
NodeC 10923-16383
多 Key 限制
例如:
MGET key1 key2
如果 key 在不同节点:
CROSSSLOT error
解决方案:
hash tags
示例:
{user}:1
{user}:2
同一 slot。
Redis 监控
生产环境必须监控:
SLOWLOG
SLOWLOG GET
常见慢操作:
- KEYS *
- 大集合扫描
INFO
关键指标:
used_memory
connected_clients
evicted_keys
keyspace_hits
建议监控:
| 指标 | 阈值 |
|---|---|
| 内存 | >80% |
| 缓存命中率 | <95% |
| evicted_keys | >0 |
总结
Redis 的成功并不是偶然。它依赖一系列 极度克制的架构设计:
| 设计选择 | 目的 |
|---|---|
| 单线程 | 简化并发 |
| I/O 多路复用 | 高连接数 |
| 数据结构优化 | 高性能 |
| Lua | 原子逻辑 |
| Pipeline | 减少 RTT |
| Cluster | 水平扩展 |
理解这些机制之后,你会发现:
Redis 并不是“简单缓存”,而是一套 高性能实时数据平台。