问题
Redis哨兵模式选主策略是什么?优先级是否可变?
答案
1. 核心概念
选主(Leader Election)是哨兵模式在主节点故障时,从多个从节点中选出新主节点的过程。选主策略直接影响故障转移后的服务质量和数据完整性。
两层选举:
- 选举领头哨兵(Sentinel Leader):决定由哪个哨兵执行故障转移
- 选择新主节点(New Master):从从节点中挑选最合适的升级为主节点
2. 领头哨兵的选举(Sentinel Leader Election)
2.1 选举触发条件
当哨兵检测到主节点 客观下线(ODOWN) 时,触发领头哨兵选举。
2.2 选举算法(基于Raft)
时间线:
T1: 哨兵A首先检测到主节点主观下线(SDOWN)
T2: 哨兵A向其他哨兵发送 is-master-down-by-addr 命令询问
T3: 收到quorum个确认,判定客观下线(ODOWN)
T4: 哨兵A发起选举,向其他哨兵请求投票
T5: 各哨兵投票给最先请求的哨兵(先到先得)
T6: 获得超过半数投票的哨兵成为Leader
选举规则:
- 每个哨兵在一次选举周期内只能投票一次
- 先发起选举的哨兵优先获得投票(先到先得原则)
- 需要获得 超过半数(N/2 + 1)的投票才能成为Leader
- 选举超时后重新发起选举
示例(5个哨兵):
// 伪代码描述哨兵选举
public class SentinelElection {
private int totalSentinels = 5;
private int requiredVotes = totalSentinels / 2 + 1; // 3票
public Sentinel electLeader() {
Map<Sentinel, Integer> votes = new HashMap<>();
// 哨兵A首先发起选举
for (Sentinel sentinel : allSentinels) {
Sentinel vote = sentinel.requestVote();
votes.put(vote, votes.getOrDefault(vote, 0) + 1);
}
// 获得3票以上的哨兵成为Leader
for (Map.Entry<Sentinel, Integer> entry : votes.entrySet()) {
if (entry.getValue() >= requiredVotes) {
return entry.getKey(); // 返回Leader哨兵
}
}
return null; // 选举失败,重新发起
}
}
3. 新主节点的选择策略(重点)
领头哨兵选出后,按以下 三层过滤规则 选择新主节点:
3.1 第一优先级:从节点优先级(replica-priority)
配置参数:
# redis.conf 中配置(从节点)
replica-priority 100 # 默认值100,0表示永不晋升为主节点
规则:
- 优先级值越小,优先级越高
- 优先级为
0的从节点永远不会被选为主节点 - 所有从节点中,选择优先级最高(数值最小)的
示例:
Slave1: replica-priority 10 # 最高优先级
Slave2: replica-priority 50
Slave3: replica-priority 100
Slave4: replica-priority 0 # 永不晋升
选举结果:Slave1 被选为新主节点
3.2 第二优先级:复制偏移量(replication offset)
作用:当多个从节点的 replica-priority 相同时,比较复制偏移量。
规则:
- 选择复制偏移量最大的从节点(数据最新)
- 偏移量越大,说明从主节点同步的数据越多
查看复制偏移量:
redis-cli INFO replication
# 输出示例:
role:slave
master_repl_offset:12345678 # 从节点当前偏移量
示例:
Slave1: priority=100, offset=12345678
Slave2: priority=100, offset=12340000
Slave3: priority=100, offset=12345500
选举结果:Slave1(偏移量最大,数据最新)
3.3 第三优先级:runID字典序
作用:当优先级和偏移量都相同时,按 runID 的字典序排序。
规则:
- 选择
runID字典序最小的从节点 - 保证选举的确定性(避免随机性)
runID获取:
redis-cli INFO server | grep run_id
# 输出示例:
run_id:a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0
示例:
Slave1: priority=100, offset=12345678, runID=a1b2c3...
Slave2: priority=100, offset=12345678, runID=z9y8x7...
选举结果:Slave1(runID字典序更小)
4. 完整选主流程
/**
* 完整的从节点选主算法
*/
public class MasterElection {
public Slave selectNewMaster(List<Slave> slaves) {
// 1. 过滤掉主观下线的从节点
slaves = slaves.stream()
.filter(s -> !s.isSubjectivelyDown())
.collect(Collectors.toList());
// 2. 过滤掉优先级为0的从节点
slaves = slaves.stream()
.filter(s -> s.getReplicaPriority() > 0)
.collect(Collectors.toList());
// 3. 过滤掉与主节点断开连接超过10倍down-after-milliseconds的从节点
slaves = slaves.stream()
.filter(s -> s.getDisconnectTime() < 10 * downAfterMilliseconds)
.collect(Collectors.toList());
// 4. 按优先级、偏移量、runID排序
slaves.sort((s1, s2) -> {
// 4.1 比较优先级(值越小越优先)
if (s1.getReplicaPriority() != s2.getReplicaPriority()) {
return Integer.compare(s1.getReplicaPriority(), s2.getReplicaPriority());
}
// 4.2 比较复制偏移量(值越大越优先)
if (s1.getReplOffset() != s2.getReplOffset()) {
return Long.compare(s2.getReplOffset(), s1.getReplOffset());
}
// 4.3 比较runID(字典序越小越优先)
return s1.getRunId().compareTo(s2.getRunId());
});
// 5. 返回排序后的第一个从节点
return slaves.isEmpty() ? null : slaves.get(0);
}
}
5. 优先级是否可变?(重点)
答案:可以动态调整
5.1 修改方式
方式1:修改配置文件(需重启)
# redis.conf
replica-priority 50
# 重启Redis
redis-server /path/to/redis.conf
方式2:动态修改(无需重启,推荐)
# 通过CONFIG SET命令
redis-cli CONFIG SET replica-priority 50
# 持久化配置到文件
redis-cli CONFIG REWRITE
方式3:通过Jedis客户端
Jedis jedis = new Jedis("192.168.1.10", 6379);
jedis.configSet("replica-priority", "50");
jedis.configRewrite(); // 写入配置文件
5.2 动态调整场景
场景1:机房优先级调整
# 同机房的从节点设置更高优先级(值更小)
redis-cli -h 192.168.1.11 CONFIG SET replica-priority 10 # 本地机房
redis-cli -h 192.168.2.11 CONFIG SET replica-priority 100 # 异地机房
场景2:硬件升级后提升优先级
# 升级SSD、增加内存后,调高优先级
redis-cli CONFIG SET replica-priority 5
场景3:临时下线维护
# 维护前设置优先级为0,避免被选为主节点
redis-cli CONFIG SET replica-priority 0
# 维护完成后恢复
redis-cli CONFIG SET replica-priority 100
5.3 查看当前优先级
# 方式1:CONFIG GET
redis-cli CONFIG GET replica-priority
# 方式2:INFO replication
redis-cli INFO replication | grep slave_priority
6. 实战建议
6.1 优先级配置最佳实践
# 生产环境推荐配置
主节点所在机房的从节点: replica-priority 10
备用机房的从节点: replica-priority 50
灾备机房的从节点: replica-priority 100
只读备份节点: replica-priority 0
6.2 注意事项
- 避免所有从节点优先级相同
- 导致选主完全依赖复制偏移量,可能选中性能较差的节点
- 合理设置优先级为0
- 用于只读备份、数据分析等不希望参与主节点选举的场景
- 监控复制偏移量
- 即使优先级高,若复制延迟严重(偏移量小),也可能丢失数据
- 配合min-replicas保证数据安全
# 至少有1个从节点复制成功才允许写入 min-replicas-to-write 1 min-replicas-max-lag 10
7. 面试答题总结
标准回答模板:
Redis哨兵选主分为两步:
- 选举领头哨兵:基于Raft算法,先发起选举的哨兵获得投票,超过半数投票成为Leader
- 选择新主节点:按以下三层规则过滤:
- 优先级:
replica-priority值越小优先级越高(0表示永不晋升)- 复制偏移量:优先级相同时,选择偏移量最大的(数据最新)
- runID:前两者都相同时,选择runID字典序最小的
优先级可动态调整:
- 通过
CONFIG SET replica-priority <value>实时修改- 用于机房优先级、硬件升级、临时维护等场景
工程实践:
- 本地机房从节点设置高优先级(如10)
- 异地机房设置低优先级(如100)
- 只读备份节点设置为0
常见追问:
- 为什么优先级值越小越优先? → 遵循Unix惯例(如nice值),0表示特殊含义(不参与选举)
- 如何保证选出的新主节点数据最新? → 通过复制偏移量比较,偏移量大的数据更新
- 领头哨兵选举和新主节点选举的区别? → 前者是哨兵之间选举(Raft),后者是从节点之间比较(优先级规则)