原文地址:https://chanjarster.github.io…
本文介绍 Spring Boot 连接 Redis Sentinel 的例子。
本文关联的源码:github
基本信息
拓扑(M 代表 redis-master,S 代表 redis-sentinel,R 代表 redis-slave,C 代表 Spring Boot Application):
+---------------------------+
| |
+-----+ +-----+ +-----+
| M |-------| S |-------| R |
+-----+ +-----+ +-----+
| |
| +-----+
+----------| C |
+-----+
application.yaml
配置:
spring:
redis:
# host: redis-master
# port: 6379
password: abc
sentinel:
master: springboot
nodes:
- redis-sentinel:26379
注意这里不需要配置 master 的 host 和 port,这些信息会从 Redis Sentinel 中得到。
演示步骤
打包并构建镜像:mvn clean install dockerfile:build
进入 docker
目录,执行docker-compose up -d
观察 Spring Boot Application 的日志:docker logs -f docker_spring-boot_1
,会发现每隔 3 秒执行INCR foo
:
07:53:49.205 INFO hello.Application : INCR foo: 1
07:53:52.212 INFO hello.Application : INCR foo: 2
07:53:55.213 INFO hello.Application : INCR foo: 3
07:53:58.216 INFO hello.Application : INCR foo: 4
07:54:01.217 INFO hello.Application : INCR foo: 5
停止 redis-master:docker stop docker_redis-master_1
,会看到 Spring Boot Application 的 Redis 链接出现了问题:
07:54:37.206 INFO hello.Application : INCR foo: 17
07:54:40.204 INFO hello.Application : INCR foo: 18
07:54:42.238 INFO i.l.core.protocol.ConnectionWatchdog : Reconnecting, last destination was /10.0.19.4:6379
07:54:52.247 WARN i.l.core.protocol.ConnectionWatchdog : Cannot reconnect: io.netty.channel.ConnectTimeoutException: connection timed out: /10.0.19.4:6379
...
07:55:22.560 INFO i.l.core.protocol.ConnectionWatchdog : Reconnecting, last destination was 10.0.19.4:6379
07:55:22.842 WARN i.l.core.protocol.ConnectionWatchdog : Cannot reconnect: io.netty.channel.AbstractChannel$AnnotatedNoRouteToHostException: Host is unreachable: /10.0.19.4:6379
...
07:55:29.582 INFO i.l.core.protocol.ConnectionWatchdog : Reconnecting, last destination was 10.0.19.4:6379
07:55:32.353 WARN i.l.core.protocol.ConnectionWatchdog : Cannot reconnect: io.netty.channel.AbstractChannel$AnnotatedNoRouteToHostException: Host is unreachable: /10.0.19.4:6379
...
等待大约 60 秒,Redis Sentinel 介入,将 redis-slave 提拔为 master,链接恢复:
07:55:43.860 INFO i.l.core.protocol.ConnectionWatchdog : Reconnecting, last destination was 10.0.19.4:6379
07:55:43.882 INFO i.l.core.protocol.ReconnectionHandler : Reconnected to 10.0.19.6:6379
07:55:43.887 INFO hello.Application : INCR foo: 20
07:55:43.889 INFO hello.Application : INCR foo: 21
07:55:43.891 INFO hello.Application : INCR foo: 22
07:55:43.892 INFO hello.Application : INCR foo: 23
此时拓扑变成这样:
+-------------//------------+
| |
+-----+ +-----+ +-----+
| M |--//---| S |-------| [M] |
+-----+ +-----+ +-----+
| | |
| +-----+ |
+----//----| C |----------+
+-----+
清理容器:docker-compose down
。
Master 重启之后的问题
这个问题和 Spring Boot 没有关系,是 Redis 本身的。如果我们把前面停掉的 master 重启,sentinel 是不会感知到这个 master 的,因为这个 master 的 ip 变了(见这个 comment):
你可以观察重启之以后的 master 的 INFO:
$ docker exec docker_redis-master_1 redis-cli -a abc INFO replication
# Replication
role:master
...
可以看到它启动之后还是 master,不是 slave。这样的话就等于出现了两个 master,这就出问题了。
BTW,redis 的配置中可以使用 hostname,比如slaveof redis-master
。但是 redis-sentinel,使用的是 ip,即使你配置的是 hostname,最终也是 ip。执行下面命令可以看见 sentinel 的配置:
$ docker exec docker_redis-sentinel_1 cat /bitnami/redis-sentinel/conf/sentinel.conf | grep springboot
sentinel monitor springboot 10.0.25.2 6379 1
sentinel down-after-milliseconds springboot 60000
sentinel auth-pass springboot abc
sentinel config-epoch springboot 0
sentinel leader-epoch springboot 0
sentinel known-slave springboot 10.0.25.5 6379
解决办法 1:使用 host network
使用 host network 来部署 redis-master、redis-slave,使用 <host-ip>:<container-port>
来访问它们,因为 host 的 ip 是比较固定的,可以缓解这个问题。
用 host network 则还有一个限制:不能在同一个 host 上启动两个相同 container-port 的容器。
解决办法 2:publish 端口
把 redis-master、redis-slave 的端口 publish 到 host 上,redis.config 中把 slave-announce-ip
和slave-announce-port
设置为 host-ip 和 host-port,最后使用 <host-ip>:<host-port>
访问,同样也是利用 host 的 ip 固定特性来解决这个问题。