Sentinel 原理(官网)
通过运行监控服务器来保证服务的可用性
从 Redis2.8 版本起,提供了一个稳定版本的 Sentinel(哨兵),用来解决高可用的问题。它是一个特殊状态的 redis 实例。
我们会启动一个或者多个 Sentinel 的服务(通过 src/redis-sentinel),它本质上只是一个运行在特殊模式之下的 Redis,Sentinel 通过 info 命令得到被监听 Redis 机器的master,slave 等信息。
为了保证监控服务器的可用性,我们会对 Sentinel 做集群的部署。Sentinel 既监控所有的 Redis 服务,Sentinel 之间也相互监控
注意:Sentinel 本身没有主从之分,只有 Redis 服务节点有主从之分
服务下线
Sentinel 默认以每秒钟 1 次的频率向 Redis 服务节点发送 PING 命令。如果在down-after-milliseconds 内都没有收到有效回复,Sentinel 会将该服务器标记为下线(主观下线)
# sentinel.conf
sentinel down-after-milliseconds <master-name> <milliseconds>
这个时候 Sentinel 节点会继续询问其他的 Sentinel 节点,确认这个节点是否下线,如果多数 Sentinel 节点都认为 master 下线,master 才真正确认被下线(客观下线),这个时候就需要重新选举 master
故障转移
如果 master 被标记为下线,就会开始故障转移流程
既然有这么多的 Sentinel 节点,由谁来做故障转移的事情呢?
故障转移流程的第一步就是在 Sentinel 集群选择一个 Leader,由 Leader 完成故障转移流程。Sentinle 通过 Raft 算法,实现 Sentinel 选举
- Raft 算法(算法演示)
在分布式存储系统中,通常通过维护多个副本来提高系统的可用性,那么多个节点之间必须要面对数据一致性的问题。Raft 的目的就是通过复制的方式,使所有节点达成一致,但是这么多节点,以哪个节点的数据为准呢?所以必须选出一个 Leader
大体上有两个步骤:领导选举,数据复制
Raft 是一个共识算法(consensus algorithm)。比如比特币之类的加密货币,就需要共识算法。Spring Cloud 的注册中心解决方案 Consul 也用到了 Raft 协议
Raft 的核心思想:先到先得,少数服从多数
Sentinle 的 Raft 算法和 Raft 论文略有不同
1、master 客观下线触发选举,而不是过了 election timeout 时间开始选举
2、Leader 并不会把自己成为 Leader 的消息发给其他 Sentinel。其他 Sentinel 等待 Leader 从 slave 选出 master 后,检测到新的 master 正常工作后,就会去掉客观下线的标识,从而不需要进入故障转移流程
- 故障转移
怎么让一个原来的 slave 节点成为主节点?
1、选出 Sentinel Leader 之后,由 Sentinel Leader 向某个节点发送 slaveof no one命令,让它成为独立节点。
2、然后向其他节点发送 slaveof x.x.x.x xxxx(本机服务,IP:端口),让它们成为这个节点的子节点,故障转移完成。
这么多从节点,选谁成为主节点?
关于从节点选举,一共有四个因素影响选举的结果,分别是断开连接时长、优先级排序、复制数量、进程 id
如果与哨兵连接断开的比较久,超过了某个阈值,就直接失去了选举权。如果拥有选举权,那就看谁的优先级高,这个在配置文件里可以设置(replica-priority 100),数值越小优先级越高
如果优先级相同,就看谁从 master 中复制的数据最多(复制偏移量最大),选最多的那个,如果复制数量也相同,就选择进程 id 最小的那个
Sentinel 功能总结
- 监控:Sentinel 会不断检查主服务器和从服务器是否正常运行
- 通知:如果某一个被监控的实例出现问题,Sentinel 可以通过 API 发出通知
- 自动故障转移(failover):如果主服务器发生故障,Sentinel 可以启动故障转移过程。把某台服务器升级为主服务器,并发出通知
- 配置管理:客户端连接到 Sentinel,获取当前的 Redis 主服务器的地址
Sentinel 实战
Sentinel 配置
为了保证 Sentinel 的高可用,Sentinel 也需要做集群部署,集群中至少需要三个Sentinel 实例(推荐奇数个,防止脑裂)
hostname | ip地址 | 节点角色&端口 |
---|---|---|
master | 198.168.1.203 | Master:6379 / Sentinel:26379 |
slave1 | 198.168.1.204 | Slave:6379 / Sentinel:26379 |
slave2 | 198.168.1.205 | Slave:6379 / Sentinel:26379 |
以 Redis 安装路径/usr/local/soft/redis-5.0.5/为例
在 204 和 205 的 src/redis.conf 配置文件中添加:
slaveof 192.168.1.203 6379
在 203、204、205 创建 sentinel 配置文件(安装后根目录下默认有 sentinel.conf)
cd /usr/local/soft/redis-5.0.5
# 创建日志目录
mkdir logs
# 创建备份目录
mkdir rdbs
mkdir sentinel-tmp
vim sentinel.conf
三台服务器编辑相同的内容(sentinel.conf)
daemonize yes
port 26379
protected-mode no
dir "/usr/local/soft/redis-5.0.5/sentinel-tmp"
sentinel monitor redis-master 192.168.1.203 6379 2
sentinel down-after-milliseconds redis-master 30000
sentinel failover-timeout redis-master 180000
sentinel parallel-syncs redis-master 1
参数 | 说明 |
---|---|
protected-mode | 是否允许外部网络访问 |
dir | sentinel 的工作目录 |
sentinel monitor | sentinel 监控的 redis 主节点 |
down-after-milliseconds(毫秒) | master 宕机多久,才会被 Sentinel 主观认为下线 |
sentinel failover-timeout(毫秒) | 1.同一个 sentinel 对同一个 master 两次 failover 之间的间隔时间。 2. 当一个 slave 从一个错误的 master 那里同步数据开始计算时间。直到slave 被纠正为向正确的 master 那里同步数据时。 3.当想要取消一个正在进行的 failover 所需要的时间。 4.当进行 failover 时,配置所有 slaves 指向新的 master 所需的最大时间。 |
parallel-syncs | 这个配置项指定了在发生 failover 主备切换时最多可以有多少个 slave 同时对新的 master 进行 同步,这个数字越小,完成 failover 所需的时间就越长,但是如果这个数字越大,就意味着越 多的 slave 因为 replication 而不可用。可以通过将这个值设为 1 来保证每次只有一个 slave 处于不能处理命令请求的状态 |
Sentinel 验证
启动 Redis 服务和 Sentinel
cd /usr/local/soft/redis-5.0.5/src
# 启动 Redis 节点
./redis-server ../redis.conf
# 启动 Sentinel 节点
./redis-sentinel ../sentinel.conf
# 或者
./redis-server ../sentinel.conf --sentinel
查看集群状态
redis> info replication
203机器执行
204和205机器执行
模拟 master 宕机,在 203 执行:
redis> shutdown
205 被选为新的 Master,只有一个 Slave 节点
注意: sentinel.conf 里面的 redis-master 被修改了
基于Java Sentinel 连接使用
master name 来自于 sentinel.conf 的配置
private static JedisSentinelPool createJedisPool() {
String masterName = "redis-master";
Set < String > sentinels = new HashSet < String > ();
sentinels.add("192.168.1.203:26379");
sentinels.add("192.168.1.204:26379");
sentinels.add("192.168.1.205:26379");
pool = new JedisSentinelPool(masterName, sentinels);
return pool;
}
Spring Boot 连接 Sentinel
spring.redis.sentinel.master=redis-master
spring.redis.sentinel.nodes=192.168.1.203:26379,192.168.1.204:26379,192.168.1.205:26379
无论是 Jedis 还是 Spring Boot(2.x 版本默认是 Lettuce),都只需要配置全部哨兵的地址,由哨兵返回当前的 master 节点地址
哨兵机制的不足
- 主从切换的过程中会丢失数据,因为只有一个 master
- 只能单点写,没有解决水平扩容的问题
- 如果数据量非常大,这个时候我们需要多个 master-slave 的 group,把数据分布到不同的 group 中