文章目录
1. 主从复制
Redis 主从复制是实现数据冗余、读写分离和高可用的基础机制。其核心目标是让一个或多个 从节点(Replica) 实时同步 主节点(Master) 的数据变更。
整个过程分为两个阶段:全量同步(Full Sync) 和 增量同步(Partial Sync)。
1.1 全量同步(Full Synchronization)
当从节点首次连接主节点,或因网络中断太久导致无法进行增量同步时,触发全量同步。
流程详解:
-
从节点发送
PSYNC命令- 格式:
PSYNC <runid> <offset> - 初次连接时,
runid为空,offset = -1。
- 格式:
-
主节点判断需全量同步
- 因从节点无有效 runid 或 offset 不在积压缓冲区范围内,主节点决定执行全量同步。
-
主节点执行
BGSAVE创建 RDB 文件- 在后台 fork 子进程生成 RDB 快照。
- 主节点继续处理客户端请求。
-
主节点缓存写命令到复制缓冲区
- 所有新写操作被记录到
repl-backlog-buffer(默认 1MB)。
- 所有新写操作被记录到
-
主节点将 RDB 文件发送给从节点
- RDB 生成后通过 socket 传输。
-
从节点加载 RDB 文件
- 清空本地数据,加载 RDB,重建状态。
-
主节点发送复制缓冲区中的增量命令
- 传输 RDB 期间的写命令。
-
进入持续增量复制阶段
缺点:大数据量下 RDB 生成和传输耗时较长。
1.2 增量同步(Partial Synchronization)
Redis 2.8+ 引入的同步机制,用于网络短暂中断后快速恢复。
关键概念:
| 概念 | 说明 |
|---|---|
| Replication Offset | 主从各自维护的偏移量,表示已复制的数据字节数。 |
| Replication Backlog Buffer | 主节点维护的环形队列,保存最近的写命令。 |
| Run ID | 每个 Redis 实例启动时生成的唯一标识。 |
增量同步流程:
- 从节点断线重连后,发送
PSYNC <master_runid> <last_offset>。 - 主节点检查:
runid匹配last_offset在backlog范围内- 返回增量命令。
- 若任一条件不满足 → 触发全量同步。
相关配置:
repl-backlog-size 16mb # 提高积压缓冲区大小
repl-backlog-ttl 1440 # backlog 保留时间(分钟)
2. 高可用方案
2.1 Redis Sentinel(哨兵)
2.1.1 原理概述
Redis Sentinel 是 Redis 官方提供的高可用性(HA)解决方案,用于监控多个 Redis 主从实例,并在主节点故障时自动进行故障转移(failover),确保服务持续可用。
核心功能:
-
监控(Monitoring)
Sentinel 持续检查主节点和从节点是否正常运行。 -
通知(Notification)
当被监控的 Redis 实例出现故障时,Sentinel 可通过 API 或脚本通知管理员。 -
自动故障转移(Automatic Failover)
当主节点不可用时,Sentinel 会从多个从节点中选举出一个新的主节点,并重新配置其他从节点,使其复制新主节点。 -
配置提供者(Configuration Provider)
客户端初始化时可连接 Sentinel,获取当前主节点的地址,实现动态发现。
架构图:
+----------------+
| Client App |
+-------+--------+
| 询问当前主节点
↓
+-------------------------------+
| Sentinel Cluster |
| (至少3个节点,避免脑裂) |
+-------------------------------+
|
监控与故障转移 ↓
+------------------------+
| Redis Master (6379) |
+------------------------+
↑ 复制
|
+------------------------+
| Redis Replica (6380) |
+------------------------+
2.1.2 搭建 Sentinel 集群案例(3节点)
1. 准备 Redis 主从环境
- 主节点:
127.0.0.1:6379 - 从节点:
127.0.0.1:6380 - Sentinel 节点:
26379,26380,26381
2. 配置主从复制
redis-master.conf
port 6379
daemonize no
pidfile /var/run/redis-6379.pid
logfile "redis-master.log"
dir ./
requirepass masterpass
masterauth masterpass
redis-replica.conf
port 6380
daemonize no
pidfile /var/run/redis-6380.pid
logfile "redis-replica.log"
dir ./
replicaof 127.0.0.1 6379
masterauth masterpass
requirepass masterpass
启动:
redis-server redis-master.conf
redis-server redis-replica.conf
3. 配置 Sentinel 集群
sentinel-26379.conf(其他节点类似,端口不同)
port 26379
daemonize no
pidfile /var/run/sentinel-26379.pid
logfile "sentinel-26379.log"
dir ./
# 监控主节点 mymaster,quorum=2 表示至少 2 个 Sentinel 认为 master down 才触发 failover
sentinel monitor mymaster 127.0.0.1 6379 2
# 主节点密码
sentinel auth-pass mymaster masterpass
# 判断主观下线时间(毫秒)
sentinel down-after-milliseconds mymaster 5000
# 故障转移超时时间
sentinel failover-timeout mymaster 15000
# 故障转移时最多一个从节点同步
sentinel parallel-syncs mymaster 1
启动三个 Sentinel:
redis-sentinel sentinel-26379.conf --sentinel
redis-sentinel sentinel-26380.conf --sentinel
redis-sentinel sentinel-26381.conf --sentinel
4. 验证 Sentinel 集群
# 查询主节点信息
redis-cli -p 26379 sentinel master mymaster
# 查询从节点信息
redis-cli -p 26379 sentinel replicas mymaster
# 查询正在监控mymaster的其他Sentinel节点信息
redis-cli -p 26379 sentinel sentinels mymaster
2.1.3 客户端连接 Sentinel
客户端不直接连接 Redis 主节点,而是通过 Sentinel 获取主节点地址。
客户端通过 Sentinel 自动发现主节点,故障转移后自动更新连接。
Java(Jedis 示例):
Set<String> sentinels = new HashSet<>();
sentinels.add("127.0.0.1:26379");
sentinels.add("127.0.0.1:26380");
sentinels.add("127.0.0.1:26381");
JedisSentinelPool pool = new JedisSentinelPool(
"mymaster", // 监控的 master 名称
sentinels,
"masterpass" // 主节点密码
);
try (Jedis jedis = pool.getResource()) {
jedis.set("hello", "world");
System.out.println(jedis.get("hello"));
}
Python(redis-py 示例):
from redis.sentinel import Sentinel
sentinel = Sentinel(
[('127.0.0.1', 26379), ('127.0.0.1', 26380), ('127.0.0.1', 26381)],
socket_timeout=0.1
)
# 获取主节点连接
master = sentinel.master_for('mymaster', password='masterpass', db=0)
master.set('foo', 'bar')
# 获取从节点连接(用于读)
slave = sentinel.slave_for('mymaster', password='masterpass', db=0)
print(slave.get('foo'))
2.2 Redis Cluster(集群)
2.2.1 原理概述
Redis Cluster 是 Redis 原生的分布式集群方案,支持数据分片(sharding) 和 高可用,适用于大规模生产环境。
核心特性:
-
数据分片(Sharding)
- 整个数据集被划分为 16384 个 slot(槽)
- 每个 key 通过
CRC16(key) % 16384映射到一个 slot - 每个 master 节点负责一部分 slot
-
高可用(High Availability)
- 每个 master 节点可配置一个或多个 replica(从节点)
- 主节点宕机时,从节点自动发起故障转移,升级为主
-
去中心化(Decentralized)
- 所有节点通过 Gossip 协议 通信,维护集群状态
- 客户端可连接任意节点,通过
MOVED/ASK重定向访问正确节点
-
水平扩展(Horizontal Scaling)
- 支持在线添加/删除节点,并通过
reshard迁移 slot
- 支持在线添加/删除节点,并通过
架构图:
Client
↓
Redirect (MOVED)
↓
+-----------------------+
| Redis Cluster |
+-----------------------+
| Slot 0-5500 → Node A (Master)
| ↖ Node D (Replica)
|
| Slot 5501-11000 → Node B (Master)
| ↖ Node E (Replica)
|
| Slot 11001-16383 → Node C (Master)
| ↖ Node F (Replica)
+-----------------------+
推荐部署:3主3从 或 6主6从,确保任意一个节点宕机不影响服务。
2.2.2 搭建 Redis Cluster 案例(3主3从)
1. 准备 6 个 Redis 实例
端口:7000~7005
redis-cluster.conf 模板:
port 7000
cluster-enabled yes
cluster-config-file nodes-7000.conf
cluster-node-timeout 5000
daemonize no
pidfile /var/run/redis_7000.pid
logfile "redis_7000.log"
dir ./
appendonly yes
requirepass clusterpass
masterauth clusterpass
复制 6 份,分别修改 port 和 cluster-config-file 对应端口。
2. 启动 6 个 Redis 节点
redis-server redis-7000.conf
redis-server redis-7001.conf
...
redis-server redis-7005.conf
3. 创建集群(使用 redis-cli)
redis-cli --cluster create \
127.0.0.1:7000 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 \
--cluster-replicas 1 \
-a clusterpass
--cluster-replicas 1:每个 master 配 1 个 replica- 系统会自动分配主从关系和 slot
4. 验证集群状态
# 查看 Redis 集群中所有节点的详细信息列表,包括主节点(master)、从节点(replica)、节点 ID、IP 端口、角色、状态、复制关系、负责的 slot 范围等。
redis-cli -c -p 7000 -a clusterpass CLUSTER NODES
# 查看 Redis 集群的整体汇总信息,提供集群的健康状态、节点数量、槽分配情况等概要指标。
redis-cli -c -p 7000 -a clusterpass CLUSTER INFO
输出应显示 3 个 master、3 个 slave,状态 ok。
5. 测试读写
# 使用 `-c` 启用集群模式
redis-cli -c -p 7000 -a clusterpass
> set hello world
> get hello
客户端会自动重定向到负责该 key 的节点。
2.2.3 客户端连接 Redis Cluster
客户端只需提供部分节点地址,即可自动发现整个集群拓扑。
Java(Jedis):
Set<HostAndPort> nodes = new HashSet<>();
nodes.add(new HostAndPort("127.0.0.1", 7000));
nodes.add(new HostAndPort("127.0.0.1", 7001));
// ... 添加所有节点
JedisCluster cluster = new JedisCluster(nodes, "clusterpass");
cluster.set("foo", "bar");
String value = cluster.get("foo");
System.out.println(value);
cluster.close();
Python(redis-py):
from redis.cluster import RedisCluster
client = RedisCluster(
host="127.0.0.1",
port=7000,
password="clusterpass",
decode_responses=True
)
client.set("hello", "world")
print(client.get("hello"))
3. 常见问题与解决方案
3.1 缓存穿透
缓存穿透 是指查询一个数据库和缓存中都不存在的数据,导致每次请求都绕过缓存,直接访问数据库。
- 例如:恶意攻击者查询
id = -1或随机不存在的 ID。 - 结果:数据库压力剧增,可能被拖垮。
本质:无效请求频繁访问底层存储。
| 方案 | 说明 |
|---|---|
| 缓存空值(Null Value Caching) | 对于查询结果为空的 key,也缓存一个特殊值(如 nil 或 "-"),并设置较短过期时间(如 30~60 秒)。避免重复查询数据库。 |
| 布隆过滤器(Bloom Filter) | 在缓存层前加一层布隆过滤器,用于快速判断某个 key 是否“一定不存在”。如果布隆过滤器返回不存在,则直接拒绝请求,不查缓存和数据库。适用于写少读多的场景。 |
| 接口层校验 | 对请求参数进行合法性校验(如 ID 格式、范围限制),提前拦截非法请求。 |
| 限流与熔断 | 对高频访问的异常 key 进行限流(如 Redis + Lua 实现计数),防止恶意刷量。 |
3.2 缓存击穿
缓存击穿 是指某个热点 key 在过期瞬间,大量并发请求同时涌入,导致这些请求全部打到数据库,造成瞬时压力激增。
- 场景:热搜新闻、秒杀商品详情页等。
- 特点:单个 key 热点 + 过期,雪崩式数据库访问。
| 方案 | 说明 |
|---|---|
| 热点 key 永不过期(逻辑过期) | 不设置 Redis 的 TTL,而是将过期时间存储在 value 中(如 JSON 里加 expire_time 字段)。读取时判断是否过期,过期则异步更新缓存,但返回旧值。 |
| 互斥锁(Mutex Lock) | 当缓存失效时,使用 SETNX 或 Redisson 分布式锁,只允许一个线程去数据库加载数据,其他线程等待并重试缓存。 |
| 后台定时刷新 | 对已知热点 key,由后台任务定期主动刷新缓存,避免其过期。 |
-- 示例:使用 SETNX 实现互斥锁
if redis.call("set", "lock:hotkey", "1", "NX", "EX", 10) then
-- 加载数据
local data = load_from_db()
redis.call("set", "hotkey", data, "EX", 3600)
redis.call("del", "lock:hotkey")
return data
else
-- 等待并重试
return nil
end
3.3 缓存雪崩
缓存雪崩 是指在同一时间大量 key 集中过期,导致大量请求同时穿透到数据库,造成数据库压力骤增甚至宕机。
- 常见原因:
- 批量导入数据时统一设置相同 TTL。
- Redis 故障重启,缓存全量失效。
- 定时任务清空缓存。
本质:缓存层大面积失效,流量全部压向数据库。
| 方案 | 说明 |
|---|---|
| 随机过期时间(Random TTL) | 设置 key 时,TTL 加上一个随机值(如 3600 + rand(1, 300)),避免大量 key 同时过期。 |
| 高可用架构 | 使用 Redis Cluster 或 Sentinel,避免单点故障导致全量缓存失效。 |
| 多级缓存(Local + Redis) | 使用本地缓存(如 Caffeine、Guava Cache)作为一级缓存,即使 Redis 失效,本地缓存仍可抗住部分流量。 |
| 限流降级 | 使用 Hystrix、Sentinel 等组件,在数据库压力过大时进行限流或返回默认值(降级)。 |
| 持久化与快速恢复 | 开启 AOF/RDB,确保 Redis 重启后能快速恢复数据。 |
3.4 热Key与大Key
| 类型 | 描述 | 风险 |
|---|---|---|
| 热 Key(Hot Key) | 某个 key 被极高频率访问(如微博热搜)。 | 单个 Redis 节点 CPU 或带宽打满,导致响应变慢或超时。 |
| 大 Key(Big Key) | 某个 key 存储了大量数据(如一个包含 10 万元素的 Hash、List)。 | 网络阻塞、阻塞其他请求、删除时引发 DEL 阻塞、主从同步延迟。 |
Redis 是单线程处理命令,大 key 的读写/删除操作会阻塞其他请求。
热 Key 处理
| 方案 | 说明 |
|---|---|
| 本地缓存 + 失效通知 | 在应用层使用本地缓存(如 Caffeine)缓存热 key,设置较短 TTL。Redis 中 key 更新时,通过消息队列或发布订阅通知各节点失效本地缓存。 |
| 拆分 key | 将一个热 key 拆分为多个 key(如 hotkey_1, hotkey_2),通过轮询或哈希访问,分散压力。 |
| 读写分离 | 使用 Redis Cluster,让多个 replica 分担读请求。 |
大 Key 处理
| 方案 | 说明 |
|---|---|
| 拆分结构 | - Hash → 拆分为多个小 Hash - List → 拆分为多个 List 或使用分页 - Set/ZSet → 分片存储 |
| 使用合适数据结构 | 避免用 List 存大量数据,可考虑用 ZSet + score 实现排序分页。 |
| 异步删除 | 使用 UNLINK 替代 DEL,UNLINK 会立即返回,后台线程异步释放内存。 |
| 监控与告警 | 使用 redis-cli --bigkeys 或监控工具(如 RedisInsight)定期扫描大 key,及时发现并处理。 |
| 限制单 key 大小 | 在业务设计阶段就规定单 key 不超过 10KB,List/Hash 元素不超过 1000 个。 |
># 扫描大 key
redis-cli --bigkeys
# 查看内存分布
redis-cli MEMORY USAGE key_name
1669

被折叠的 条评论
为什么被折叠?



