介绍
-
redis的哨兵模式(Sentinel)是以主从复制为架构的运行模式,旨在提高redis集群的容错性,可搭配cluster模式组合使用,本文不涉及cluster
-
假设一个场景,redis配置了主从,程序中链接主节点地址进行写入查询操作,当主节点挂了后,为了让从节点顶上来,需要手动将程序中主节点ip换成从节点的,人工干预费事费力,还会造成一段时间内服务不可用
-
Sentinel 哨兵模式是一种特殊的模式,独立的进程,程序中链接哨兵服务的ip,将会为程序提供安全可靠的节点容灾切换,有些类似于服务发现
-
哨兵模式通常设置多个节点,其中挂了一个后,其它的也能顶上来,这也就要求程序中需要将哨兵的所有节点地址都写进去
环境概述
redis 主从节点
- 主 127.0.0.1:6388
- 从 127.0.0.1:6398
假如有3个sentinel实例依次为:
- 127.0.0.1:26379
- 127.0.0.1:26380
- 127.0.0.1:26381
哨兵节点配置文件,以 26379 为例
sentinel.conf
port 26379
dir /tmp
sentinel monitor mymaster 127.0.0.1 6388 2
sentinel down-after-milliseconds mymaster 6000
sentinel failover-timeout mymaster 18000
sentinel parallel-syncs mymaster 1
配置详解
sentinel monitor mymaster 127.0.0.1 6388 2
- 设置 Sentinel 监控名为
mymaster
的 Redis 主服务器。 - 127.0.0.1 是主服务器的 IP 地址,6388 是主服务器的端口号。
- 2 是最小数量的 Sentinel 节点,当这些节点都同意主服务器处于下线状态时,Sentinel 会开始故障转移。
sentinel down-after-milliseconds mymaster 6000
- 设置当 Sentinel 认为主服务器
mymaster
下线之前,需要等待的时间,单位是毫秒。 - 如果在 6000 毫秒内没有收到主服务器的响应,Sentinel 将认为主服务器处于下线状态。
sentinel failover-timeout mymaster 18000
- 设置故障转移操作的超时时间,单位是毫秒。
- 如果故障转移操作在 18000 毫秒内没有完成,Sentinel 将停止故障转移尝试。
sentinel parallel-syncs mymaster 1
- 这个命令设置在故障转移过程中,可以同时进行数据同步的从服务器的数量。
- 在这个例子中,1 表示只有一个从服务器可以同时进行数据同步。
实验准备,程序客户端体现
使用NewFailoverClient
实例化一个可故障转移的客户端
main.go
package main
import (
"fmt"
"github.com/go-redis/redis"
"time"
)
func main() {
client := redis.NewFailoverClient(&redis.FailoverOptions{
MasterName: "mymaster", // 与哨兵配置文件中 mymaster 对应
SentinelAddrs: []string{"127.0.0.1:26379", "127.0.0.1:26380", "127.0.0.1:26381"},
Password: "",
DB: 0,
})
for {
reply, err := client.Incr("pvcount").Result()
fmt.Printf("reply=%v err=%v\n", reply, err)
time.Sleep(1 * time.Second)
}
}
实验开始
实验1:redis节点主从切换
运行 main.go
在执行过程中,shutdown redis 主节点 127.0.0.1:6388
,
可以看到在reply等于93和94之间,客户端监测到了主从切换,并重新连接到新的主节点,这段时间大致等于sentinel配置down-after-milliseconds
的时长。
[root@dev example]# go run main.go
redis: 2024/07/16 00:09:53 sentinel.go:379: sentinel: discovered new sentinel="93db6069e9bde858a99b873048891912eecd45d4" for master="mymaster"
redis: 2024/07/16 00:09:53 sentinel.go:379: sentinel: discovered new sentinel="48ee106539cf267a79a84db621a01ee01b73ca5e" for master="mymaster"
redis: 2024/07/16 00:09:53 sentinel.go:332: sentinel: new master="mymaster" addr="127.0.0.1:6388"
reply=1 err=<nil>
reply=2 err=<nil>
reply=3 err=<nil>
reply=4 err=<nil>
reply=5 err=<nil>
reply=6 err=<nil>
...
reply=83 err=<nil>
reply=84 err=<nil>
reply=85 err=<nil>
reply=86 err=<nil>
reply=87 err=<nil>
reply=88 err=<nil>
reply=89 err=<nil>
reply=90 err=<nil>
reply=91 err=<nil>
reply=92 err=<nil>
reply=93 err=<nil>
reply=0 err=EOF
reply=0 err=dial tcp 127.0.0.1:6388: connect: connection refused
reply=0 err=dial tcp 127.0.0.1:6388: connect: connection refused
reply=0 err=dial tcp 127.0.0.1:6388: connect: connection refused
reply=0 err=dial tcp 127.0.0.1:6388: connect: connection refused
reply=0 err=dial tcp 127.0.0.1:6388: connect: connection refused
reply=0 err=dial tcp 127.0.0.1:6388: connect: connection refused
redis: 2024/07/16 00:11:33 sentinel.go:332: sentinel: new master="mymaster" addr="127.0.0.1:6398"
reply=94 err=<nil>
reply=95 err=<nil>
reply=96 err=<nil>
reply=97 err=<nil>
reply=98 err=<nil>
reply=99 err=<nil>
reply=100 err=<nil>
实验2:sentinel实例全部挂掉后,redis的读写操作
如果把3个sentinel实例全部 kill掉,则go-redis会记录一条日志,而对redis的读写操作仍然正常。
reply=233 err=<nil>
reply=234 err=<nil>
reply=235 err=<nil>
reply=236 err=<nil>
reply=237 err=<nil>
redis: 2024/07/16 22:06:45 pubsub.go:159: redis: discarding bad PubSub connection: EOF
reply=238 err=<nil>
reply=239 err=<nil>
reply=240 err=<nil>
实验3:sentinel实例部分挂掉后(剩余实例数目多于配置的quorum),redis实例的主从切换
quorum 对应哨兵配置文件中的同意个数 2
sentinel monitor mymaster 127.0.0.1 6388 2
如果只kill掉1个sentinel,则剩余两个sentinel还能正常监测redis主从切换。
例如下面是kill掉 127.0.0.1:26379
之后,再kill redis主节点 127.0.0.1:6388
的实验
reply=322 err=<nil>
reply=323 err=<nil>
reply=324 err=<nil>
reply=325 err=<nil>
reply=326 err=<nil>
reply=327 err=<nil>
reply=328 err=<nil>
reply=329 err=<nil>
reply=330 err=<nil>
reply=0 err=EOF
redis: 2024/07/16 22:08:19 sentinel.go:313: sentinel: GetMasterAddrByName name="mymaster" failed: EOF
redis: 2024/07/16 22:08:19 sentinel.go:313: sentinel: GetMasterAddrByName name="mymaster" failed: dial tcp 127.0.0.1:26379: connect: connection refused
redis: 2024/07/16 22:08:19 sentinel.go:287: sentinel: GetMasterAddrByName master="mymaster" failed: dial tcp 127.0.0.1:26379: connect: connection refused
redis: 2024/07/16 22:08:19 sentinel.go:379: sentinel: discovered new sentinel="9a023490096f5f87db1c7f445d883f56e75275db" for master="mymaster"
reply=0 err=dial tcp 127.0.0.1:6388: connect: connection refused
reply=0 err=dial tcp 127.0.0.1:6388: connect: connection refused
reply=0 err=dial tcp 127.0.0.1:6388: connect: connection refused
reply=0 err=dial tcp 127.0.0.1:6388: connect: connection refused
reply=0 err=dial tcp 127.0.0.1:6388: connect: connection refused
reply=0 err=dial tcp 127.0.0.1:6388: connect: connection refused
redis: 2024/07/16 22:08:24 sentinel.go:332: sentinel: new master="mymaster" addr="127.0.0.1:6398"
reply=331 err=<nil>
reply=332 err=<nil>
reply=333 err=<nil>
reply=334 err=<nil>
实验4:sentinel实例都正常的情况下,进行redis实例迁移
当3个sentinel都正常运行的情况下,动态增减从节点对客户端没有影响。
例如
- 增加一个从节点
127.0.0.1:6378
- 再把原从节点
127.0.0.1:6398
shutdown,客户端没有影响。 - 进而把主节点
127.0.0.1:6388
shutdown,迫使主从切换,此时127.0.0.1:6378
成为了新的主节点。
reply=525 err=<nil>
reply=526 err=<nil>
reply=527 err=<nil>
reply=528 err=<nil>
reply=529 err=<nil>
reply=530 err=<nil>
reply=531 err=<nil>
reply=0 err=EOF
reply=0 err=dial tcp 127.0.0.1:6398: connect: connection refused
reply=0 err=dial tcp 127.0.0.1:6398: connect: connection refused
reply=0 err=dial tcp 127.0.0.1:6398: connect: connection refused
reply=0 err=dial tcp 127.0.0.1:6398: connect: connection refused
reply=0 err=dial tcp 127.0.0.1:6398: connect: connection refused
reply=0 err=dial tcp 127.0.0.1:6398: connect: connection refused
reply=0 err=dial tcp 127.0.0.1:6398: connect: connection refused
redis: 2024/07/16 22:43:19 sentinel.go:332: sentinel: new master="mymaster" addr="127.0.0.1:6378"
reply=532 err=<nil>
reply=533 err=<nil>
reply=534 err=<nil>
reply=535 err=<nil>
reply=536 err=<nil>
reply=537 err=<nil>
结论
redis的go-redis
包操作哨兵模式是可靠的