Redis 核心知识笔记 - 第三部分:高可用架构
Redis 核心知识笔记 - 第三部分:高可用架构
一、高可用架构概述
| 模式 | 特点 | 适用场景 |
|---|---|---|
| 主从复制 | 数据备份、读写分离 | 读多写少,数据量适中 |
| 哨兵模式 | 自动故障转移、高可用 | 中小规模高可用需求 |
| Cluster 集群 | 分片存储、水平扩展 | 大数据量、高并发场景 |
二、主从复制
2.1 主从复制原理
2.2 全量复制 vs 部分复制
2.3 主从复制配置
主节点配置(Master):
# redis.conf
bind 0.0.0.0
port 6379
daemonize yes
pidfile /var/run/redis_6379.pid
logfile "/var/log/redis/redis_6379.log"
dbfilename dump_6379.rdb
dir /var/lib/redis
# 设置密码(可选)
requirepass master_password
masterauth master_password # 如果主从都设密码
# 复制积压缓冲区大小(支持部分复制)
repl-backlog-size 256mb
repl-backlog-ttl 3600
从节点配置(Slave):
# redis.conf
bind 0.0.0.0
port 6380
daemonize yes
pidfile /var/run/redis_6380.pid
logfile "/var/log/redis/redis_6380.log"
dbfilename dump_6380.rdb
dir /var/lib/redis
# 配置主节点
replicaof 192.168.1.100 6379
# 主节点密码
masterauth master_password
# 从节点只读(默认)
replica-read-only yes
# 从节点是否提供过期数据
replica-serve-stale-data yes
动态设置主从关系:
# 设置为某个主节点的从节点
REPLICAOF 192.168.1.100 6379
# 取消主从关系,变为独立主节点
REPLICAOF NO ONE
# 查看复制状态
INFO replication
2.4 读写分离架构
2.5 Java 读写分离代码
@Configuration
public class RedisReadWriteConfig {
@Bean
public LettuceConnectionFactory masterConnectionFactory() {
RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
config.setHostName("192.168.1.100");
config.setPort(6379);
config.setPassword("master_password");
return new LettuceConnectionFactory(config);
}
@Bean
public LettuceConnectionFactory slaveConnectionFactory() {
// 配置从节点(可配置多个进行负载均衡)
RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
config.setHostName("192.168.1.101");
config.setPort(6380);
config.setPassword("master_password");
LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder()
.readFrom(ReadFrom.REPLICA_PREFERRED) // 优先从从节点读取
.build();
return new LettuceConnectionFactory(config, clientConfig);
}
@Bean("masterRedisTemplate")
public StringRedisTemplate masterRedisTemplate(
@Qualifier("masterConnectionFactory") LettuceConnectionFactory factory) {
return new StringRedisTemplate(factory);
}
@Bean("slaveRedisTemplate")
public StringRedisTemplate slaveRedisTemplate(
@Qualifier("slaveConnectionFactory") LettuceConnectionFactory factory) {
return new StringRedisTemplate(factory);
}
}
@Service
public class ReadWriteSplitService {
@Autowired
@Qualifier("masterRedisTemplate")
private StringRedisTemplate masterTemplate;
@Autowired
@Qualifier("slaveRedisTemplate")
private StringRedisTemplate slaveTemplate;
// 写操作走主节点
public void write(String key, String value) {
masterTemplate.opsForValue().set(key, value);
}
// 读操作走从节点
public String read(String key) {
return slaveTemplate.opsForValue().get(key);
}
}
2.6 主从复制优缺点
优点:
- ✅ 数据热备份,提高数据安全性
- ✅ 读写分离,提升读性能
- ✅ 配置简单,易于实现
缺点:
- ❌ 主节点故障需手动切换
- ❌ 写操作无法扩展(单主节点)
- ❌ 全量复制时性能开销大
三、哨兵模式(Sentinel)
3.1 哨兵架构
3.2 哨兵功能
| 功能 | 描述 |
|---|---|
| 监控 | 持续检查主从节点是否正常运行 |
| 通知 | 节点出现问题时通知管理员或其他程序 |
| 自动故障转移 | 主节点故障时自动选举新的主节点 |
| 配置提供者 | 客户端通过哨兵获取当前主节点地址 |
3.3 故障转移流程
3.4 从节点选举规则
3.5 哨兵配置
sentinel.conf 配置:
# 哨兵端口
port 26379
# 守护进程运行
daemonize yes
# 日志文件
logfile "/var/log/redis/sentinel.log"
# 监控的主节点
# sentinel monitor <master-name> <ip> <port> <quorum>
sentinel monitor mymaster 192.168.1.100 6379 2
# 主节点密码
sentinel auth-pass mymaster master_password
# 主观下线判断时间(毫秒)
sentinel down-after-milliseconds mymaster 30000
# 故障转移超时时间(毫秒)
sentinel failover-timeout mymaster 180000
# 同时进行复制的从节点数量
sentinel parallel-syncs mymaster 1
# 通知脚本(可选)
sentinel notification-script mymaster /opt/redis/notify.sh
# 故障转移后执行的脚本(可选)
sentinel client-reconfig-script mymaster /opt/redis/reconfig.sh
启动哨兵:
# 方式1
redis-sentinel /etc/redis/sentinel.conf
# 方式2
redis-server /etc/redis/sentinel.conf --sentinel
3.6 Java 连接哨兵
@Configuration
public class RedisSentinelConfig {
@Bean
public LettuceConnectionFactory redisConnectionFactory() {
RedisSentinelConfiguration sentinelConfig = new RedisSentinelConfiguration()
.master("mymaster")
.sentinel("192.168.1.100", 26379)
.sentinel("192.168.1.101", 26379)
.sentinel("192.168.1.102", 26379);
sentinelConfig.setPassword("master_password");
LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder()
.readFrom(ReadFrom.REPLICA_PREFERRED)
.build();
return new LettuceConnectionFactory(sentinelConfig, clientConfig);
}
@Bean
public StringRedisTemplate stringRedisTemplate(LettuceConnectionFactory factory) {
return new StringRedisTemplate(factory);
}
}
Spring Boot 配置方式:
spring:
redis:
sentinel:
master: mymaster
nodes:
- 192.168.1.100:26379
- 192.168.1.101:26379
- 192.168.1.102:26379
password: master_password
lettuce:
pool:
max-active: 100
max-idle: 50
min-idle: 10
3.7 哨兵优缺点
优点:
- ✅ 自动故障转移,高可用
- ✅ 配置相对简单
- ✅ 客户端自动发现主节点
缺点:
- ❌ 写操作仍然只能单点
- ❌ 存储容量受单机限制
- ❌ 故障转移期间短暂不可用
四、Cluster 集群模式
4.1 Cluster 架构
4.2 数据分片原理
槽位(Slot)分配:
- Redis Cluster 将数据划分为 16384 个槽位
- 每个主节点负责一部分槽位
- 数据根据 Key 的 CRC16 值决定存储在哪个槽位
槽位计算公式:
SLOT = CRC16(key) % 16384
Hash Tag 机制:
# 使用 {} 标记 Hash Tag,相同 Tag 的 Key 会分配到同一槽位
SET {user:1001}:name "张三"
SET {user:1001}:age "25"
SET {user:1001}:email "[email protected]"
# 这三个 Key 都会根据 "user:1001" 计算槽位
# 从而保证存储在同一节点,支持 MGET 等多 Key 操作
4.3 Cluster 配置
节点配置 (node-6379.conf):
port 6379
daemonize yes
pidfile /var/run/redis_6379.pid
logfile "/var/log/redis/redis_6379.log"
dbfilename dump_6379.rdb
dir /var/lib/redis
# 开启集群模式
cluster-enabled yes
# 集群配置文件(自动生成)
cluster-config-file nodes-6379.conf
# 节点超时时间
cluster-node-timeout 15000
# 主节点无从节点时是否继续服务
cluster-require-full-coverage no
# 密码
requirepass cluster_password
masterauth cluster_password
创建集群:
# 启动所有节点
redis-server /etc/redis/node-6379.conf
redis-server /etc/redis/node-6380.conf
redis-server /etc/redis/node-6381.conf
redis-server /etc/redis/node-6382.conf
redis-server /etc/redis/node-6383.conf
redis-server /etc/redis/node-6384.conf
# 创建集群(3主3从)
redis-cli --cluster create \
192.168.1.100:6379 \
192.168.1.101:6379 \
192.168.1.102:6379 \
192.168.1.100:6380 \
192.168.1.101:6380 \
192.168.1.102:6380 \
--cluster-replicas 1 \
-a cluster_password
4.4 集群管理命令
# 查看集群信息
redis-cli -c -a password cluster info
# 查看节点列表
redis-cli -c -a password cluster nodes
# 查看槽位分配
redis-cli -c -a password cluster slots
# 手动故障转移(在从节点执行)
redis-cli -c -a password cluster failover
# 添加节点
redis-cli --cluster add-node new_host:port existing_host:port
# 删除节点
redis-cli --cluster del-node host:port node_id
# 重新分片
redis-cli --cluster reshard host:port
4.5 故障转移机制
4.6 动态扩容
扩容步骤:
# 1. 启动新节点
redis-server /etc/redis/node-new.conf
# 2. 加入集群
redis-cli --cluster add-node 192.168.1.103:6379 192.168.1.100:6379
# 3. 重新分片(分配槽位)
redis-cli --cluster reshard 192.168.1.100:6379
# 4. 添加从节点
redis-cli --cluster add-node 192.168.1.103:6380 192.168.1.103:6379 \
--cluster-slave --cluster-master-id <master-node-id>
4.7 Java 连接 Cluster
@Configuration
public class RedisClusterConfig {
@Bean
public LettuceConnectionFactory redisConnectionFactory() {
RedisClusterConfiguration clusterConfig = new RedisClusterConfiguration();
clusterConfig.addClusterNode(new RedisNode("192.168.1.100", 6379));
clusterConfig.addClusterNode(new RedisNode("192.168.1.101", 6379));
clusterConfig.addClusterNode(new RedisNode("192.168.1.102", 6379));
clusterConfig.setPassword("cluster_password");
clusterConfig.setMaxRedirects(3);
LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder()
.readFrom(ReadFrom.REPLICA_PREFERRED)
.build();
return new LettuceConnectionFactory(clusterConfig, clientConfig);
}
}
Spring Boot 配置:
spring:
redis:
cluster:
nodes:
- 192.168.1.100:6379
- 192.168.1.101:6379
- 192.168.1.102:6379
max-redirects: 3
password: cluster_password
lettuce:
pool:
max-active: 100
4.8 Cluster 优缺点
优点:
- ✅ 数据自动分片,线性扩展
- ✅ 高可用,自动故障转移
- ✅ 去中心化,无 SPOF
- ✅ 支持在线扩容缩容
缺点:
- ❌ 不支持跨槽位的多 Key 操作(需用 Hash Tag)
- ❌ 架构复杂,运维成本高
- ❌ 集群最少 3 主节点
- ❌ 不支持 SELECT 切换数据库
五、脑裂问题与解决方案
5.1 什么是脑裂
5.2 脑裂解决方案
# redis.conf 配置
# 最少从节点数量(写操作需要至少N个从节点在线)
min-replicas-to-write 1
# 从节点最大延迟时间(秒)
min-replicas-max-lag 10
# 作用:
# 当从节点数量少于 min-replicas-to-write
# 或者从节点延迟超过 min-replicas-max-lag 秒时
# 主节点拒绝写入操作,避免脑裂时数据丢失
六、数据一致性问题
6.1 主从延迟
问题:从节点数据可能落后于主节点
解决方案:
@Service
public class ConsistencyService {
@Autowired
private StringRedisTemplate redisTemplate;
// 方案1:强制读主
public String readFromMaster(String key) {
// 配置连接池只连主节点
return redisTemplate.opsForValue().get(key);
}
// 方案2:等待复制完成
public void writeWithWait(String key, String value, int numReplicas, long timeout) {
redisTemplate.opsForValue().set(key, value);
// WAIT 命令:等待至少 numReplicas 个从节点确认
redisTemplate.execute((RedisCallback<Long>) connection ->
connection.waitReplicas(numReplicas, timeout)
);
}
// 方案3:读己之写(Read Your Writes)
public String readYourWrites(String key, String sessionId) {
// 先尝试从本地缓存读取刚写入的值
String localValue = localCache.get(sessionId + ":" + key);
if (localValue != null) {
return localValue;
}
// 降级到 Redis 读取
return redisTemplate.opsForValue().get(key);
}
}
6.2 数据一致性保证级别
| 级别 | 实现方式 | 性能 | 一致性 |
|---|---|---|---|
| 最终一致性 | 异步复制(默认) | 最高 | 最低 |
| 弱一致性 | 读主写主 | 较高 | 较高 |
| 强一致性 | WAIT 命令同步 | 较低 | 最高 |
七、架构选型对比
| 维度 | 主从复制 | 哨兵模式 | Cluster |
|---|---|---|---|
| 数据量 | < 10GB | < 10GB | > 10GB |
| 高可用 | 手动切换 | 自动切换 | 自动切换 |
| 写扩展 | 不支持 | 不支持 | 支持 |
| 读扩展 | 支持 | 支持 | 支持 |
| 复杂度 | 低 | 中 | 高 |
| 适用场景 | 开发测试 | 中小型生产 | 大型生产 |