Redis哨兵Sentinel高可用

Redis哨兵Sentinel高可用

Redis 的五种数据结构各有特色,用对了才能发挥它的优势。很多人只用到了 String 和 Hash,却不知道 List、Set、ZSet 在特定场景下更合适。本文从应用场景出发,讲什么时候用什么类型。

一、Sentinel架构

#

1.1 基本架构

┌─────────┐    ┌─────────┐    ┌─────────┐
│Sentinel1│───>│Sentinel2│───>│Sentinel3│
│ :26379 │ │ :26379 │ │ :26379 │
└────┬────┘ └────┬────┘ └────┬────┘
│ │ │
└──────────────┼──────────────┘

┌─────┴─────┐
▼ ▼
┌─────────┐ ┌─────────┐
│ Master │ │ Slave │
│ :6379 │ │ :6379 │
└─────────┘ └─────────┘

#

1.2 Sentinel的作用

  1. 监控:持续检查Master和Slave是否正常运行
  2. 通知:通过API向管理员或其他应用发送通知
  3. 自动故障转移:Master故障时,自动将Slave提升为Master
  4. 配置提供者:为客户端提供当前Master地址

二、Sentinel配置

#

2.1 Sentinel节点配置

# sentinel.conf

# 端口
port 26379

# Sentinel工作目录
dir /var/lib/redis/sentinel

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

# quorum: 判断Master失效需要多少个Sentinel同意
# 如quorum=2,至少2个Sentinel认为Master下线才进行故障转移

# Master密码(如果Master有密码)
sentinel auth-pass mymaster password

# 判断Master下线的时间(毫秒)
sentinel down-after-milliseconds mymaster 5000

# 并行同步的Slave数量
sentinel parallel-syncs mymaster 1

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

# 通知脚本(可选)
sentinel notification-script mymaster /var/redis/notify.sh

# 故障转移后执行的脚本(可选)
sentinel client-reconfig-script mymaster /var/redis/failover.sh

#

2.2 启动Sentinel

# 方式一
redis-sentinel /etc/redis/sentinel.conf

# 方式二
redis-server /etc/redis/sentinel.conf --sentinel

#

2.3 最少Sentinel数量

推荐至少3个Sentinel

  • 1个:无法判断客观下线(自己说自己)
  • 2个:如果网络分区,可能出现脑裂
  • 3个:quorum=2,可以安全地进行故障转移

三、故障检测机制

#

3.1 主观下线(SDOWN)

每个Sentinel独立判断:

Sentinel1 ──PING──> Master

├── 回复+PONG → 正常
├── 回复-LOADING → 正常(正在加载)
├── 回复-MASTERDOWN → 正常(Master知晓故障)
└── 未回复/回复错误 → 主观下线

判断条件:
- 超过down-after-milliseconds未收到有效回复

#

3.2 客观下线(ODOWN)

Sentinel1发现Master主观下线

├── 向其他Sentinel发送:
│ SENTINEL is-master-down-by-addr <ip> <port> <current_epoch> <runid>

├── Sentinel2回复:1(同意)
├── Sentinel3回复:1(同意)

└── 同意数 >= quorum → 客观下线

注意:
- 需要包括Sentinel自己
- 如quorum=2,需要Sentinel自己+另一个Sentinel同意

#

3.3 检测命令

# Sentinel之间通过以下命令通信:

# 1. PING:检测节点存活
PING

# 2. SENTINEL命令:交换信息
SENTINEL is-master-down-by-addr 192.168.1.100 6379 0 *

# 3. PUBLISH/SUBSCRIBE:通过__sentinel__:hello频道
# 每个Sentinel定期发布自己的信息和Master状态

四、故障转移流程

#

4.1 选举Leader Sentinel

Sentinel发现Master客观下线

├── 发送SENTINEL is-master-down-by-addr请求
│ (带上自己的runid,请求成为Leader)

├── 其他Sentinel回复:
│ 如果当前epoch更大 → 拒绝
│ 如果已投票给其他Sentinel → 拒绝
│ 否则 → 同意

└── 获得多数票(> Sentinel总数/2)→ 成为Leader

示例:3个Sentinel
- Sentinel1请求成为Leader
- Sentinel2同意
- Sentinel3同意
- Sentinel1获得2票(> 3/2 = 1.5),成为Leader

#

4.2 选择新Master

Leader Sentinel选择新Master:

1. 筛选条件:
- Slave必须在线
- Slave的down-after-milliseconds内有过回复

2. 排序规则(优先级从高到低):
a. replica-priority最小(配置值)
b. 复制偏移量最大(数据最新)
c. RunID最小(字典序)

#

4.3 执行故障转移

1. 对新Master执行:SLAVEOF NO ONE
→ 提升为Master

2. 对其他Slave执行:SLAVEOF new_master_ip port
→ 重新指向新Master

3. 更新Sentinel配置
→ 修改sentinel monitor指向新Master

4. 通知客户端(通过Pub/Sub)
+switch-master mymaster old_ip old_port new_ip new_port

#

4.4 故障转移示例

初始状态:
Master: 192.168.1.100:6379
Slave: 192.168.1.101:6379
Slave: 192.168.1.102:6379

Master宕机

├── Sentinel检测到客观下线

├── 选举Leader Sentinel

├── 选择192.168.1.101为新Master
│ (因为复制偏移量最大)

├── 对192.168.1.101:SLAVEOF NO ONE

├── 对192.168.1.102:SLAVEOF 192.168.1.101 6379

└── 通知客户端切换

最终状态:
Master: 192.168.1.101:6379
Slave: 192.168.1.102:6379

五、客户端连接

#

5.1 Jedis Sentinel

@Configuration
public class RedisSentinelConfig {

@Bean
public JedisSentinelPool jedisSentinelPool() {
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(100);
poolConfig.setMaxIdle(20);

return new JedisSentinelPool("mymaster", sentinels, poolConfig, "password");
}
}

// 使用
@Autowired
private JedisSentinelPool pool;

public void setValue(String key, String value) {
try (Jedis jedis = pool.getResource()) {
jedis.set(key, value);
}
}

#

5.2 Lettuce Sentinel

@Configuration
public class RedisConfig {

@Bean
public RedisConnectionFactory 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("password");

return new LettuceConnectionFactory(sentinelConfig);
}
}

#

5.3 Spring Boot配置

spring:
redis:
sentinel:
master: mymaster
nodes:
- 192.168.1.100:26379
- 192.168.1.101:26379
- 192.168.1.102:26379
password: password
lettuce:
pool:
max-active: 100
max-idle: 20
min-idle: 5

六、Sentinel管理命令

#

6.1 查看状态

# 查看Sentinel监控的Master
redis-cli -p 26379 SENTINEL masters

# 查看特定Master的Slave
redis-cli -p 26379 SENTINEL slaves mymaster

# 查看Sentinel节点
redis-cli -p 26379 SENTINEL sentinels mymaster

# 查看Master地址
redis-cli -p 26379 SENTINEL get-master-addr-by-name mymaster

# 检查故障转移状态
redis-cli -p 26379 SENTINEL failover-abort mymaster

#

6.2 手动故障转移

# 手动触发故障转移(用于维护)
redis-cli -p 26379 SENTINEL failover mymaster

# 移除Master监控
redis-cli -p 26379 SENTINEL remove mymaster

# 添加Master监控
redis-cli -p 26379 SENTINEL monitor mymaster 192.168.1.100 6379 2

七、常见问题

#

7.1 脑裂问题

现象:网络分区导致出现两个Master

网络分区前:
Sentinel1 ── Sentinel2 ── Sentinel3
│ │ │
Master ───── Slave1 ──── Slave2

网络分区后:
分区A: 分区B:
Sentinel1 Sentinel2 ── Sentinel3
│ │ │
Master Slave1 ───── Slave2
(继续服务) (提升为Master)

结果:两个Master同时服务,数据不一致!

解决

# 配置最小Slave数
min-replicas-to-write 1
min-replicas-max-lag 10

# 这样Master如果没有Slave连接,会停止写入

#

7.2 故障转移后客户端连接失败

原因

  • 客户端缓存了旧的Master地址
  • Sentinel通知有延迟

解决

// 使用Sentinel模式的客户端库
// 客户端自动从Sentinel获取最新Master地址

// Jedis Sentinel会自动处理
// Lettuce Sentinel会自动处理

#

7.3 故障转移频繁触发

排查

# 查看Sentinel日志
tail -f /var/log/redis/sentinel.log

# 查看Master状态
redis-cli -p 26379 SENTINEL master mymaster

# 常见原因:
# 1. down-after-milliseconds设置过小
# 2. 网络不稳定
# 3. Master负载高,响应慢

#

7.4 Sentinel配置不生效

# Sentinel配置通过SENTINEL SET动态修改后,会自动保存到配置文件
# 但需要确保配置文件可写

# 检查配置文件权限
ls -la /etc/redis/sentinel.conf

# 手动刷新配置
redis-cli -p 26379 SENTINEL flush-config

八、Sentinel监控

#

8.1 监控脚本

#!/bin/bash
# check_sentinel.sh

for sentinel in sentinel1 sentinel2 sentinel3; do
status=$(redis-cli -h $sentinel -p 26379 PING)
if [ "$status" != "PONG" ]; then
echo "Sentinel $sentinel 异常" | mail -s "Sentinel告警" admin@company.com
fi
done

# 检查Master状态
master_info=$(redis-cli -h sentinel1 -p 26379 SENTINEL master mymaster)
flags=$(echo "$master_info" | grep "flags" | head -1)

if echo "$flags" | grep -q "down"; then
echo "Master mymaster 状态异常: $flags" | mail -s "Redis Master告警" admin@company.com
fi

#

8.2 Prometheus监控

# 使用redis_exporter监控
scrape_configs:
- job_name: 'redis-sentinel'
static_configs:
- targets: ['sentinel1:26379', 'sentinel2:26379', 'sentinel3:26379']

九、总结

概念 说明
SDOWN 主观下线,单个Sentinel的判断
ODOWN 客观下线,多个Sentinel达成共识
quorum 判断ODOWN所需的最小同意数
epoch 配置纪元,用于Leader选举和配置版本
Leader选举 Raft算法变种,获得多数票的Sentinel成为Leader
配置项 推荐值 说明
quorum Sentinel数/2 + 1 确保多数同意
down-after-milliseconds 5000 根据网络质量调整
parallel-syncs 1-3 同时同步的Slave数
failover-timeout 60000 故障转移超时

Sentinel的核心价值:

  1. 自动故障检测(主观下线→客观下线)
  2. 自动故障转移(选举→切换→通知)
  3. 客户端透明(自动获取最新Master)
  4. 多Sentinel保证决策可靠性

Sentinel的局限:

  1. 只有一个Master写(不能水平扩展写)
  2. 需要至少3个Sentinel保证可靠性
  3. 脑裂问题需要额外配置解决
  4. 故障转移期间有短暂不可用

核心要点

  1. String:简单的键值对,适合缓存、计数器

  2. Hash:存储对象属性,适合用户信息、配置

  3. List:有序列表,适合消息队列、最新列表

  4. Set:无序去重,适合共同好友、抽奖

  5. ZSet:有序集合,适合排行榜、积分系统

总结

选择合适的数据结构是使用 Redis 的关键。在实际项目中,根据业务需求选择合适的类型,可以提升性能和开发效率。


   转载规则


《Redis哨兵Sentinel高可用》 小乐 采用 知识共享署名 4.0 国际许可协议 进行许可。
  目录