Redis高可用与实战

1. 主从复制

Redis 主从复制是实现数据冗余、读写分离和高可用的基础机制。其核心目标是让一个或多个 从节点(Replica) 实时同步 主节点(Master) 的数据变更。

整个过程分为两个阶段:全量同步(Full Sync)增量同步(Partial Sync)


1.1 全量同步(Full Synchronization)

当从节点首次连接主节点,或因网络中断太久导致无法进行增量同步时,触发全量同步。

流程详解:

  1. 从节点发送 PSYNC 命令

    • 格式:PSYNC <runid> <offset>
    • 初次连接时,runid 为空,offset = -1
  2. 主节点判断需全量同步

    • 因从节点无有效 runid 或 offset 不在积压缓冲区范围内,主节点决定执行全量同步。
  3. 主节点执行 BGSAVE 创建 RDB 文件

    • 在后台 fork 子进程生成 RDB 快照。
    • 主节点继续处理客户端请求。
  4. 主节点缓存写命令到复制缓冲区

    • 所有新写操作被记录到 repl-backlog-buffer(默认 1MB)。
  5. 主节点将 RDB 文件发送给从节点

    • RDB 生成后通过 socket 传输。
  6. 从节点加载 RDB 文件

    • 清空本地数据,加载 RDB,重建状态。
  7. 主节点发送复制缓冲区中的增量命令

    • 传输 RDB 期间的写命令。
  8. 进入持续增量复制阶段

缺点:大数据量下 RDB 生成和传输耗时较长。


1.2 增量同步(Partial Synchronization)

Redis 2.8+ 引入的同步机制,用于网络短暂中断后快速恢复。

关键概念:

概念说明
Replication Offset主从各自维护的偏移量,表示已复制的数据字节数。
Replication Backlog Buffer主节点维护的环形队列,保存最近的写命令。
Run ID每个 Redis 实例启动时生成的唯一标识。

增量同步流程:

  1. 从节点断线重连后,发送 PSYNC <master_runid> <last_offset>
  2. 主节点检查:
    • runid 匹配
    • last_offsetbacklog 范围内
    • 返回增量命令。
  3. 若任一条件不满足 → 触发全量同步。

相关配置:

repl-backlog-size 16mb      # 提高积压缓冲区大小
repl-backlog-ttl 1440       # backlog 保留时间(分钟)

2. 高可用方案

2.1 Redis Sentinel(哨兵)

2.1.1 原理概述

Redis Sentinel 是 Redis 官方提供的高可用性(HA)解决方案,用于监控多个 Redis 主从实例,并在主节点故障时自动进行故障转移(failover),确保服务持续可用。

核心功能:

  1. 监控(Monitoring)
    Sentinel 持续检查主节点和从节点是否正常运行。

  2. 通知(Notification)
    当被监控的 Redis 实例出现故障时,Sentinel 可通过 API 或脚本通知管理员。

  3. 自动故障转移(Automatic Failover)
    当主节点不可用时,Sentinel 会从多个从节点中选举出一个新的主节点,并重新配置其他从节点,使其复制新主节点。

  4. 配置提供者(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)高可用,适用于大规模生产环境。

核心特性:

  1. 数据分片(Sharding)

    • 整个数据集被划分为 16384 个 slot(槽)
    • 每个 key 通过 CRC16(key) % 16384 映射到一个 slot
    • 每个 master 节点负责一部分 slot
  2. 高可用(High Availability)

    • 每个 master 节点可配置一个或多个 replica(从节点)
    • 主节点宕机时,从节点自动发起故障转移,升级为主
  3. 去中心化(Decentralized)

    • 所有节点通过 Gossip 协议 通信,维护集群状态
    • 客户端可连接任意节点,通过 MOVED / ASK 重定向访问正确节点
  4. 水平扩展(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 份,分别修改 portcluster-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)当缓存失效时,使用 SETNXRedisson 分布式锁,只允许一个线程去数据库加载数据,其他线程等待并重试缓存。
后台定时刷新对已知热点 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 ClusterSentinel,避免单点故障导致全量缓存失效。
多级缓存(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 替代 DELUNLINK 会立即返回,后台线程异步释放内存。
监控与告警使用 redis-cli --bigkeys 或监控工具(如 RedisInsight)定期扫描大 key,及时发现并处理。
限制单 key 大小在业务设计阶段就规定单 key 不超过 10KB,List/Hash 元素不超过 1000 个。
># 扫描大 key
redis-cli --bigkeys
# 查看内存分布
redis-cli MEMORY USAGE key_name

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值