Redis高可用

Redis高可用架构模式详解

目录


主从模式

1. 原理概述

主从模式是Redis最基础的高可用方案,通过数据复制实现读写分离和数据备份。

┌─────────────┐    复制    ┌─────────────┐
│   Master    │─────────→  │   Slave 1   │
│  (读写)     │            │   (只读)    │
└─────────────┘           └─────────────┘
       │                        
       │ 复制                   
       ▼                        
┌─────────────┐                
│   Slave 2   │                
│   (只读)    │                
└─────────────┘                

2. 复制过程详解

2.1 全量复制 (Full Resynchronization)

时序图:
Master                           Slave
  │                               │
  │◄─────── PSYNC ? -1 ──────────│  1. Slave发送同步请求
  │                               │
  │────── +FULLRESYNC ──────────►│  2. Master响应全量同步
  │       runid offset            │
  │                               │
  │────────── RDB ──────────────►│  3. Master发送RDB快照
  │                               │
  │─────── 增量数据 ─────────────►│  4. 发送复制期间的新数据
  │                               │

2.2 增量复制 (Partial Resynchronization)

# Redis 2.8+ 支持增量复制机制
# 基于复制偏移量和复制积压缓冲区

# Master维护:
replication_backlog = CircularBuffer(size=1MB)  # 复制积压缓冲区
master_repl_offset = 12345  # 主复制偏移量

# Slave维护:
slave_repl_offset = 12340   # 从复制偏移量
master_runid = "abc123..."  # 主服务器运行ID

3. 配置实现

3.1 Master配置 (redis-master.conf)

# 基本配置
port 6379
bind 0.0.0.0
protected-mode no

# 持久化配置
save 900 1
save 300 10
save 60 10000
rdbcompression yes
dbfilename dump.rdb

# 复制配置
# 设置从服务器密码验证
requirepass "master_password"
# 主服务器密码(如果主服务器也需要认证)
masterauth "master_password"

# 复制积压缓冲区大小
repl-backlog-size 1mb
# 复制积压缓冲区超时时间
repl-backlog-ttl 3600

# 网络配置
tcp-keepalive 300
timeout 0

3.2 Slave配置 (redis-slave.conf)

# 基本配置
port 6380
bind 0.0.0.0
protected-mode no

# 从服务器配置
slaveof 192.168.1.100 6379
# 主服务器密码
masterauth "master_password"
# 从服务器密码
requirepass "slave_password"

# 从服务器只读
slave-read-only yes

# 复制相关配置
# 当主从连接断开时,从服务器是否继续提供服务
slave-serve-stale-data yes
# 复制超时时间
repl-timeout 60

# 复制期间禁用TCP_NODELAY
repl-disable-tcp-nodelay no

# 从服务器优先级(用于故障转移)
slave-priority 100

4. 命令操作

# 启动Master
redis-server redis-master.conf

# 启动Slave
redis-server redis-slave.conf

# 动态设置主从关系
redis-cli -p 6380
127.0.0.1:6380> SLAVEOF 192.168.1.100 6379
OK

# 查看复制信息
127.0.0.1:6379> INFO replication
# Replication
role:master
connected_slaves:2
slave0:ip=192.168.1.101,port=6380,state=online,offset=12345,lag=0
slave1:ip=192.168.1.102,port=6380,state=online,offset=12345,lag=0

# 取消主从关系
127.0.0.1:6380> SLAVEOF NO ONE
OK

5. 主从模式的限制

  • 故障转移需要人工干预
  • 写操作单点瓶颈
  • 主节点故障时服务不可用

哨兵模式

1. 原理概述

哨兵模式在主从模式基础上增加了自动故障转移功能,通过多个哨兵节点监控Redis集群状态。

┌─────────────┐    ┌─────────────┐    ┌─────────────┐
│  Sentinel 1 │    │  Sentinel 2 │    │  Sentinel 3 │
│   (监控)    │    │   (监控)    │    │   (监控)    │
└─────────────┘    └─────────────┘    └─────────────┘
       │                   │                   │
       └───────────────────┼───────────────────┘
                           │ 监控和故障转移
                           ▼
┌─────────────┐    复制    ┌─────────────┐
│   Master    │─────────→│   Slave 1   │
│  (读写)     │          │   (只读)    │
└─────────────┘          └─────────────┘
       │                        
       │ 复制                   
       ▼                        
┌─────────────┐                
│   Slave 2   │                
│   (只读)    │                
└─────────────┘                

2. 哨兵工作机制

2.1 监控 (Monitoring)

# 哨兵定期向Master和Slave发送PING命令
def monitor_redis_instances():
    for instance in redis_instances:
        try:
            response = instance.ping()
            if response != "PONG":
                mark_as_down(instance)
        except Exception:
            mark_as_down(instance)

2.2 故障检测和判断

# 主观下线 (Subjectively Down, SDOWN)
def check_sdown(instance):
    if instance.last_ping_time + down_after_milliseconds < current_time():
        instance.flags |= SRI_S_DOWN
        return True
    return False

# 客观下线 (Objectively Down, ODOWN) 
def check_odown(master):
    sdown_count = 0
    for sentinel in sentinels:
        if sentinel.is_master_down(master):
            sdown_count += 1
    
    if sdown_count >= quorum:
        master.flags |= SRI_O_DOWN
        return True
    return False

2.3 故障转移流程

故障转移步骤:
1. 检测Master客观下线
2. 选举Leader Sentinel
3. 选择新的Master
4. 执行故障转移
5. 更新配置并通知

时序图:
Sentinel1   Sentinel2   Sentinel3   Master   Slave1   Slave2
    │           │           │         │        │        │
    │─────── PING ──────────────────►│        │        │
    │           │           │         X        │        │
    │◄─── timeout ──────────────────│         │        │
    │           │           │                  │        │
    │───── is-master-down-by-addr ──►│         │        │
    │◄───── +odown ─────────────────│         │        │
    │           │           │                  │        │
    │──── Leader选举 ──────────────►│         │        │
    │◄─── Leader确认 ──────────────│         │        │
    │           │           │                  │        │
    │─────── SLAVEOF NO ONE ─────────────────►│        │
    │◄────── OK ────────────────────────────│         │
    │           │           │                  │        │
    │─────── SLAVEOF new_master ─────────────────────►│
    │◄────── OK ──────────────────────────────────────│

3. 配置实现

3.1 哨兵配置 (sentinel.conf)

# 哨兵端口
port 26379
# 绑定地址
bind 0.0.0.0
# 工作目录
dir /var/lib/redis

# 监控Master配置
# sentinel monitor <master-name> <ip> <port> <quorum>
sentinel monitor mymaster 192.168.1.100 6379 2

# 认证配置
sentinel auth-pass mymaster master_password

# 故障转移配置
# 判断Master下线的时间(毫秒)
sentinel down-after-milliseconds mymaster 5000

# 故障转移超时时间
sentinel failover-timeout mymaster 15000

# 同时从新Master同步数据的Slave数量
sentinel parallel-syncs mymaster 1

# 日志配置
logfile "/var/log/redis/sentinel.log"
loglevel notice

# 通知脚本(可选)
sentinel notification-script mymaster /scripts/notify.sh
sentinel client-reconfig-script mymaster /scripts/reconfig.sh

3.2 启动哨兵集群

# 启动3个哨兵节点
redis-sentinel /etc/redis/sentinel-26379.conf
redis-sentinel /etc/redis/sentinel-26380.conf  
redis-sentinel /etc/redis/sentinel-26381.conf

# 或使用systemd
systemctl start redis-sentinel@26379
systemctl start redis-sentinel@26380
systemctl start redis-sentinel@26381

4. 客户端连接

4.1 Java客户端 (Jedis)

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisSentinelPool;

public class RedisSentinelClient {
    private JedisSentinelPool sentinelPool;
    
    public void initSentinelPool() {
        Set<String> sentinels = new HashSet<>();
        sentinels.add("192.168.1.100:26379");
        sentinels.add("192.168.1.101:26379");
        sentinels.add("192.168.1.102:26379");
        
        JedisPoolConfig poolConfig = new JedisPoolConfig();
        poolConfig.setMaxTotal(20);
        poolConfig.setMaxIdle(10);
        poolConfig.setMinIdle(5);
        
        sentinelPool = new JedisSentinelPool(
            "mymaster",           // Master名称
            sentinels,            // 哨兵地址集合
            poolConfig,           // 连接池配置
            "master_password"     // Redis密码
        );
    }
    
    public void useRedis() {
        try (Jedis jedis = sentinelPool.getResource()) {
            jedis.set("key", "value");
            String value = jedis.get("key");
            System.out.println("Value: " + value);
        }
    }
}

4.2 Python客户端 (redis-py)

import redis.sentinel

# 配置哨兵
sentinels = [
    ('192.168.1.100', 26379),
    ('192.168.1.101', 26379),
    ('192.168.1.102', 26379)
]

# 创建哨兵实例
sentinel = redis.sentinel.Sentinel(
    sentinels,
    socket_timeout=0.1,
    password='master_password'
)

# 获取Master连接
master = sentinel.master_for('mymaster', socket_timeout=0.1)

# 获取Slave连接(读操作)
slave = sentinel.slave_for('mymaster', socket_timeout=0.1)

# 使用示例
master.set('key', 'value')
value = slave.get('key')
print(f'Value: {value}')

5. 哨兵命令

# 连接哨兵
redis-cli -p 26379

# 查看Master信息
127.0.0.1:26379> SENTINEL masters

# 查看Slave信息
127.0.0.1:26379> SENTINEL slaves mymaster

# 查看哨兵信息
127.0.0.1:26379> SENTINEL sentinels mymaster

# 手动故障转移
127.0.0.1:26379> SENTINEL failover mymaster

# 重置Master
127.0.0.1:26379> SENTINEL reset mymaster

集群模式

1. 原理概述

Redis Cluster是Redis官方的分布式解决方案,支持数据分片和自动故障转移。

集群拓扑结构:
┌─────────────┐  ┌─────────────┐  ┌─────────────┐
│   Node 1    │  │   Node 2    │  │   Node 3    │
│ (Master)    │  │ (Master)    │  │ (Master)    │
│ Slots:      │  │ Slots:      │  │ Slots:      │
│ 0-5460      │  │ 5461-10922  │  │ 10923-16383 │
└─────────────┘  └─────────────┘  └─────────────┘
       │                │                │
       │                │                │
┌─────────────┐  ┌─────────────┐  ┌─────────────┐
│   Node 4    │  │   Node 5    │  │   Node 6    │
│ (Slave of 1)│  │ (Slave of 2)│  │ (Slave of 3)│
└─────────────┘  └─────────────┘  └─────────────┘

2. 数据分片机制

2.1 哈希槽 (Hash Slots)

# Redis Cluster使用16384个哈希槽
CLUSTER_SLOTS = 16384

def calculate_slot(key):
    """计算key对应的槽位"""
    # 检查是否有哈希标签 {tag}
    start = key.find('{')
    if start != -1:
        end = key.find('}', start + 1)
        if end != -1 and end != start + 1:
            key = key[start + 1:end]
    
    # 使用CRC16算法计算槽位
    crc = crc16(key.encode('utf-8'))
    return crc % CLUSTER_SLOTS

# 示例
print(calculate_slot("user:1000"))     # 槽位: 8842
print(calculate_slot("user:{tag}:1000"))  # 使用tag计算
print(calculate_slot("user:{tag}:2000"))  # 相同tag,相同槽位

2.2 槽位分配

# 集群槽位分配示例
Node 192.168.1.100:7000 - Slots: 0-5460 (5461 slots)
Node 192.168.1.101:7000 - Slots: 5461-10922 (5462 slots)  
Node 192.168.1.102:7000 - Slots: 10923-16383 (5461 slots)

3. 配置实现

3.1 集群节点配置 (redis-cluster.conf)

# 基本配置
port 7000
bind 0.0.0.0
protected-mode no

# 集群模式启用
cluster-enabled yes
# 集群配置文件(自动生成)
cluster-config-file nodes-7000.conf
# 节点超时时间
cluster-node-timeout 5000
# 集群require-full-coverage
cluster-require-full-coverage no

# 数据持久化
appendonly yes
appendfilename "appendonly-7000.aof"

# 内存配置
maxmemory 1gb
maxmemory-policy allkeys-lru

# 日志配置
logfile "/var/log/redis/redis-7000.log"
loglevel notice

# 密码配置(可选)
requirepass "cluster_password"
masterauth "cluster_password"

3.2 创建集群脚本

#!/bin/bash
# create-cluster.sh

# 创建6个节点的目录
for port in 7000 7001 7002 7003 7004 7005; do
    mkdir -p /etc/redis/cluster/${port}
    cp redis-cluster.conf /etc/redis/cluster/${port}/redis.conf
    sed -i "s/port 7000/port ${port}/g" /etc/redis/cluster/${port}/redis.conf
    sed -i "s/nodes-7000.conf/nodes-${port}.conf/g" /etc/redis/cluster/${port}/redis.conf
    sed -i "s/appendonly-7000.aof/appendonly-${port}.aof/g" /etc/redis/cluster/${port}/redis.conf
    sed -i "s/redis-7000.log/redis-${port}.log/g" /etc/redis/cluster/${port}/redis.conf
done

# 启动所有节点
for port in 7000 7001 7002 7003 7004 7005; do
    redis-server /etc/redis/cluster/${port}/redis.conf &
done

sleep 5

# 创建集群(Redis 5.0+)
redis-cli --cluster create \
192.168.1.100:7000 \
192.168.1.100:7001 \
192.168.1.100:7002 \
192.168.1.100:7003 \
192.168.1.100:7004 \
192.168.1.100:7005 \
--cluster-replicas 1 \
--cluster-yes

echo "Cluster created successfully!"

3.3 多机部署配置

# 机器1 (192.168.1.100) - 运行节点 7000, 7003
# 机器2 (192.168.1.101) - 运行节点 7001, 7004  
# 机器3 (192.168.1.102) - 运行节点 7002, 7005

# 创建集群
redis-cli --cluster create \
192.168.1.100:7000 \
192.168.1.101:7001 \
192.168.1.102:7002 \
192.168.1.100:7003 \
192.168.1.101:7004 \
192.168.1.102:7005 \
--cluster-replicas 1

4. 集群管理

4.1 集群信息查看

# 连接集群(任意节点)
redis-cli -c -h 192.168.1.100 -p 7000

# 查看集群信息
127.0.0.1:7000> CLUSTER INFO
cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6
cluster_size:3

# 查看集群节点
127.0.0.1:7000> CLUSTER NODES
a1b2c3d4... 192.168.1.100:7000 myself,master - 0 0 1 connected 0-5460
e5f6g7h8... 192.168.1.101:7001 master - 0 1623456789 2 connected 5461-10922
i9j0k1l2... 192.168.1.102:7002 master - 0 1623456789 3 connected 10923-16383
m3n4o5p6... 192.168.1.100:7003 slave e5f6g7h8... 0 1623456789 4 connected
q7r8s9t0... 192.168.1.101:7004 slave i9j0k1l2... 0 1623456789 5 connected
u1v2w3x4... 192.168.1.102:7005 slave a1b2c3d4... 0 1623456789 6 connected

# 查看槽位分配
127.0.0.1:7000> CLUSTER SLOTS
1) 1) (integer) 0
   2) (integer) 5460
   3) 1) "192.168.1.100"
      2) (integer) 7000
      3) "a1b2c3d4..."
   4) 1) "192.168.1.102"
      2) (integer) 7005
      3) "u1v2w3x4..."

4.2 集群扩容

# 1. 添加新的Master节点
redis-cli --cluster add-node 192.168.1.103:7006 192.168.1.100:7000

# 2. 重新分配槽位
redis-cli --cluster reshard 192.168.1.100:7000
# 按提示输入要迁移的槽位数量和目标节点ID

# 3. 添加Slave节点
redis-cli --cluster add-node 192.168.1.103:7007 192.168.1.100:7000 --cluster-slave --cluster-master-id <master-node-id>

4.3 集群缩容

# 1. 迁移槽位到其他节点
redis-cli --cluster reshard 192.168.1.100:7000 \
--cluster-from <node-id> \
--cluster-to <target-node-id> \
--cluster-slots 1000

# 2. 删除空的Master节点
redis-cli --cluster del-node 192.168.1.103:7006 <node-id>

# 3. 删除Slave节点
redis-cli --cluster del-node 192.168.1.103:7007 <slave-node-id>

5. 客户端连接

5.1 Java客户端 (Jedis)

import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.HostAndPort;

public class RedisClusterClient {
    private JedisCluster jedisCluster;
    
    public void initCluster() {
        Set<HostAndPort> nodes = new HashSet<>();
        nodes.add(new HostAndPort("192.168.1.100", 7000));
        nodes.add(new HostAndPort("192.168.1.101", 7001));
        nodes.add(new HostAndPort("192.168.1.102", 7002));
        
        JedisPoolConfig poolConfig = new JedisPoolConfig();
        poolConfig.setMaxTotal(20);
        poolConfig.setMaxIdle(10);
        poolConfig.setMinIdle(5);
        
        jedisCluster = new JedisCluster(
            nodes,              // 集群节点
            2000,               // 连接超时
            2000,               // 读取超时  
            5,                  // 最大重定向次数
            "cluster_password", // 密码
            poolConfig          // 连接池配置
        );
    }
    
    public void useCluster() {
        // 自动处理槽位路由和重定向
        jedisCluster.set("user:1000", "John");
        jedisCluster.set("user:1001", "Jane");
        
        String user1 = jedisCluster.get("user:1000");
        String user2 = jedisCluster.get("user:1001");
        
        // 批量操作需要在同一槽位
        Map<String, String> users = new HashMap<>();
        users.put("user:{group1}:1000", "John");
        users.put("user:{group1}:1001", "Jane");
        jedisCluster.mset(users);
    }
}

5.2 Python客户端 (redis-py-cluster)

from rediscluster import RedisCluster

# 配置集群节点
startup_nodes = [
    {"host": "192.168.1.100", "port": "7000"},
    {"host": "192.168.1.101", "port": "7001"},
    {"host": "192.168.1.102", "port": "7002"}
]

# 创建集群连接
cluster = RedisCluster(
    startup_nodes=startup_nodes,
    decode_responses=True,
    password="cluster_password",
    skip_full_coverage_check=True,
    max_connections=20
)

# 使用集群
cluster.set("user:1000", "John")
cluster.set("product:2000", "Laptop")

user = cluster.get("user:1000")
product = cluster.get("product:2000")

# 使用哈希标签确保相关数据在同一槽位
cluster.mset({
    "order:{user1000}:detail": "order details",
    "order:{user1000}:items": "order items",
    "order:{user1000}:payment": "payment info"
})

6. 故障转移机制

6.1 自动故障检测

# 集群节点故障检测机制
def detect_node_failure():
    for node in cluster_nodes:
        if node.last_ping_time + NODE_TIMEOUT < current_time():
            node.flags |= REDIS_NODE_PFAIL  # 标记为可能失败
            
            # 询问其他节点的意见
            fail_reports = 0
            for other_node in cluster_nodes:
                if other_node.get_node_flag(node.id) & REDIS_NODE_PFAIL:
                    fail_reports += 1
            
            # 如果大多数节点认为该节点失败
            if fail_reports > len(cluster_nodes) // 2:
                node.flags |= REDIS_NODE_FAIL
                trigger_failover(node)

6.2 Slave提升为Master

# 故障转移过程
1. 检测Master下线
2. Slave节点开始选举
3. 选举出新Master
4. 更新集群配置
5. 重新分配连接

# 故障转移日志示例
[WARNING] Node 192.168.1.100:7000 is now possibly failing (PFAIL)
[WARNING] Node 192.168.1.100:7000 is now failing (FAIL)
[NOTICE] Start of election delayed for 500 milliseconds
[NOTICE] Failover election started by 192.168.1.102:7005
[NOTICE] Failover election won: new configuration epoch is 7
[NOTICE] configEpoch set to 7 after successful failover
[NOTICE] Setting secondary replication ID

三种模式对比

1. 功能对比表

特性 主从模式 哨兵模式 集群模式
数据分片
自动故障转移
读写分离 ❌ (需手动实现)
横向扩展
配置复杂度
客户端支持 简单 中等 复杂
最小节点数 2 3+3 6

2. 性能对比

2.1 写性能

主从模式: 单Master写入,性能受限于单机
哨兵模式: 单Master写入,性能受限于单机  
集群模式: 多Master并行写入,性能线性扩展

2.2 读性能

主从模式: Master+Slave读取,可配置读写分离
哨兵模式: Master+Slave读取,可配置读写分离
集群模式: 所有节点都可读取,但需要正确路由

3. 使用场景建议

3.1 主从模式适用场景

  • 小型应用,数据量不大
  • 读多写少的场景
  • 对一致性要求高,可接受手动故障恢复
  • 预算有限,硬件资源少

3.2 哨兵模式适用场景

  • 中型应用,需要高可用
  • 读写比例均衡
  • 需要自动故障转移
  • 数据量在单机可承受范围内

3.3 集群模式适用场景

  • 大型应用,大数据量
  • 需要水平扩展
  • 写操作频繁
  • 可以接受分布式系统的复杂性

4. 迁移路径

4.1 主从 → 哨兵模式

# 1. 停止应用写入
# 2. 部署哨兵节点
# 3. 修改应用连接配置
# 4. 重启应用
# 5. 测试故障转移

# 配置变更示例
# 原配置
redis_host = "192.168.1.100"
redis_port = 6379

# 新配置  
sentinels = [
    ("192.168.1.100", 26379),
    ("192.168.1.101", 26379), 
    ("192.168.1.102", 26379)
]
master_name = "mymaster"

4.2 哨兵 → 集群模式

# 1. 备份现有数据
redis-cli --rdb /backup/dump.rdb

# 2. 创建新集群
# 3. 导入数据到集群
redis-cli --cluster import 192.168.1.100:7000 --cluster-from 192.168.1.100:6379

# 4. 验证数据完整性
# 5. 切换应用连接
# 6. 下线旧环境

5. 监控和运维

5.1 关键监控指标

# 主从模式监控
- 主从延迟 (replication lag)
- 复制缓冲区使用率
- 主从连接状态

# 哨兵模式监控  
- 哨兵节点状态
- 故障转移次数
- Master切换历史

# 集群模式监控
- 集群节点状态
- 槽位迁移状态  
- 集群性能指标
- 数据分布均匀性

5.2 运维脚本示例

#!/bin/bash
# redis-health-check.sh

check_redis_cluster() {
    echo "=== Redis Cluster Health Check ==="
    
    # 检查集群状态
    cluster_state=$(redis-cli -c -h 192.168.1.100 -p 7000 cluster info | grep "cluster_state" | cut -d: -f2)
    echo "Cluster State: $cluster_state"
    
    # 检查节点状态
    echo "=== Node Status ==="
    redis-cli -c -h 192.168.1.100 -p 7000 cluster nodes | while read line; do
        node_id=$(echo $line | awk '{print $1}')
        node_addr=$(echo $line | awk '{print $2}')
        node_flags=$(echo $line | awk '{print $3}')
        echo "Node: $node_addr, Flags: $node_flags"
    done
    
    # 检查槽位分配
    echo "=== Slot Distribution ==="
    redis-cli -c -h 192.168.1.100 -p 7000 cluster slots | grep -E "^\d+" | wc -l
    echo "Active slot ranges: $(redis-cli -c -h 192.168.1.100 -p 7000 cluster slots | grep -E "^\d+" | wc -l)"
}

check_redis_cluster

总结

Redis的三种高可用架构各有特点:

  1. 主从模式:简单易用,适合小型应用
  2. 哨兵模式:自动故障转移,适合中型应用
  3. 集群模式:分布式架构,适合大型应用

选择合适的架构需要考虑:

  • 数据量大小
  • 并发要求
  • 可用性要求
  • 运维复杂度
  • 硬件成本

建议根据业务发展阶段逐步演进:单机 → 主从 → 哨兵 → 集群

posted @ 2025-08-18 14:43  MadLongTom  阅读(26)  评论(0)    收藏  举报