docker-compose搭建Redis高可用架构
之前,我们已经用 Docker安装Redis 单机环境,我们继续沿用 5.0.14 版本的镜像。
但是,由于“单点故障”,生产环境通常部署的是集群服务。Redis的高可用架构一般有这么几种:主从模式、哨兵模式、redis sharding 模式、redis cluster模式。
redis sharding 模式是“Redis客户端”集群模式,因此本文不做搭建。
本文默认你已经安装好Docker,且拉取了Redis镜像,因此这些步骤就不多赘述了。
准备工作
- 从 GitHub 上下载 redis.conf
- 从 GitHub 上下载 sentinel.conf
下载上述配置文件,可以帮助你了解配置参数
但是,我接下来没有准备使用配置文件来启动Redis进程,而是直接使用命令行参数
命令行参数的名称和含义相同
一、主从模式
1.1 docker-compose.yml
以下是我的 F:\DockerCluster\redis\master-slave\docker-compose.yml :
version: '3.8'
services:
master:
image: redis:5.0.14
container_name: redis-master
command: redis-server --requirepass abc123 --masterauth abc123
ports:
- 6380:6379
slave1:
image: redis:5.0.14
container_name: redis-slave-1
ports:
- 6381:6379
command: redis-server --replicaof redis-master 6379 --requirepass abc123 --masterauth abc123
slave2:
image: redis:5.0.14
container_name: redis-slave-2
ports:
- 6382:6379
command: redis-server --replicaof redis-master 6379 --requirepass abc123 --masterauth abc123
- version 通过查阅 Compose file Reference 可以知道 Docker Engine 版本高于 19.03.0,应该对应 version '3.8';
- --requirepass abc123 是 redis-server 命令的参数。它表示客户端在执行其他命令前,需要通过 auth abc123 验证密码并获取授权;
- --masterauth abc123 也是 redis-server 命令的参数。它表示因为 master 节点有密码保护,所以 slave 节点在同步副本时,也需要通过密码校验;
- --replicaof redis-master 6379 也是 redis-server 命令的参数。如果不设置这句话,则启动的 redis 服务器就是 master 节点;相反,通过
replicaof <masterip> <masterport>
可以指定当前启动的 redis 服务器是 slave 服务器,且它对应的 master 服务器的ip为redis-master容器的ip,port为6379;
1.2 启动主从节点
在 docker-compose.yml 所在的目录下执行:
docker-compose up -d
如图所示:
1.3 验证
-
进入 redis-master 容器,用 redis-cli 查看的所有的键:
-
进入 redis-slave-1 容器,用 redis-cli 查看的所有的键:
-
进入 redis-slave-2 容器,用 redis-cli 查看的所有的键:
-
在 redis-master 容器的 redis-cli 中,用 set hello world 将字符串值 "world" 关联到键 hello 。
-
检查 redis-slave-1 的同步情况:
-
检查 redis-slave-2 的同步情况:
master 节点允许读写,但是 slave 是只读的,如图所示,redis-slave-1 执行 set key value 命令时报错:
1.4 info replication
用 redis-cli 连接 master 节点,输入 info replication 命令,可以查看副本信息,主节点上保存了 slave 节点的信息。
1.5 主从切换
用 docker stop redis-master 停掉主节点之后,在连接 redis-slave-1 的 redis-cli 输入 info replication
可以看到 master_link_status:down,slave 节点失去了与 master 节点的连接。
输入 replicaof no one 命令,手动将从节点 redis-slave-1 设置成主节点,如下图所示:
但是,此时 redis-slave-2 不会直接认 redis-slave-1 为新的主节点,如下图所示:
我们可以继续在 redis-slave-1 上用 set whoishe geekziyu 保存一个新的字符串,再用 info replication 查看,发现 master_replid 没有改变,master_repl_offset 从 9564 增长到 9627 :
接着,用 docker start redis-master 重启原主节点:
1.6 恢复主节点
1.6.1 错误的恢复方法
我尝试在 redis-slave-1 直接输入命令 replicaof redis-master 6379
这样做,直接导致 redis-slave-1 上的数据丢失,且 redis-master 重启时没有数据,所以现在整个集群都没有数据了...(如果在生产环境,可以背大锅了)
1.6.2 正确的恢复方法
解决方案:当原来的主节点从宕机中进行恢复,则将临时主节点的数据进行保存,将AOF文件与RDB文件拷贝替换原主节点下的AOF文件与RDB文件。
为此,我首先用 docker-compose down 停止了容器:
接着,修改了 docker-compose.yml :
version: '3.8'
services:
master:
image: redis:5.0.14
container_name: redis-master
command: redis-server --requirepass abc123 --masterauth abc123 --appendonly yes
ports:
- 6380:6379
volumes:
- F:\DockerCluster\redis\master-slave\data\master:/data
slave1:
image: redis:5.0.14
container_name: redis-slave-1
ports:
- 6381:6379
command: redis-server --replicaof redis-master 6379 --requirepass abc123 --masterauth abc123 --appendonly yes
volumes:
- F:\DockerCluster\redis\master-slave\data\slave1:/data
slave2:
image: redis:5.0.14
container_name: redis-slave-2
ports:
- 6382:6379
command: redis-server --replicaof redis-master 6379 --requirepass abc123 --masterauth abc123 --appendonly yes
volumes:
- F:\DockerCluster\redis\master-slave\data\slave2:/data
- RDB 持久化功能现在是默认开启的,所以不需要配置;
- AOF 持久化,需要设置 appendonly yes;
- 增加了 volumes 映射,把宿主机 Windows 的文件夹路径映射到 Redis 容器的 /data 目录;
重启输入 docker-compose up -d 启动集群:
redis-cli 访问 redis-master,设置了 5 组字符串:
直接做一次 docker stop redis-master 再进行 docker start redis-master,Redis 会自动从 rdb.dump 文件恢复数据。
如果,我们 docker stop redis-master,并重现一下 redis-slave1 切换成主节点的场景:
之后,我们
- 把 F:\DockerCluster\redis\master-slave\data\slave1\dump.rdb 拷贝并覆盖 F:\DockerCluster\redis\master-slave\data\master\dump.rdb;
- 把 F:\DockerCluster\redis\master-slave\data\slave1\appendonly.aof 拷贝并覆盖 F:\DockerCluster\redis\master-slave\data\master\appendonly.aof
然后 docker start redis-master 启动容器:
参考文档:Redis 持久化之RDB和AOF
这篇文章中还模拟了这种场景————胡乱修改 appendonly.aof 中的内容,会导致redis-server重启失败。
针对这种情况,使用 redis-check-aof --fix appendonly.aof 可以校验并修复 appendonly.aof 文件。
二、哨兵模式
从上面的演示中,我们也看到了手动切换还是非常麻烦的,哨兵模式提供了 监控(Monitoring)、提醒(Notification)、自动故障迁移(Automatic failover)。
-
监控:Sentinel实例会不断检测主从节点是否正常运行。
-
提醒:当某个节点出现异常宕机时,Sentinel实例会向管理员或者其他应用发送提醒。
-
自动故障迁移:当主节点宕机时,Sentinel实例会将该主节点下的其中一个从节点升级为新的主节点,并且原先其他从节点重新发起socket请求成为新的主节点的从节点。
-
配置中心:向客户端返回新主节点的地址,就可以正常上使用新的主节点来处理请求了。
配置哨兵模式,需要部署额外的3台哨兵服务。
文件结构如下所示:
2.1 docker-compose
version: '3.8'
services:
sentinel1:
image: redis:5.0.14
container_name: redis-sentinel-1
ports:
- 26379:26379
command: redis-sentinel /usr/local/etc/redis/sentinel.conf
volumes:
- F:\DockerCluster\redis\sentinel\sentinel1.conf:/usr/local/etc/redis/sentinel.conf
sentinel2:
image: redis:5.0.14
container_name: redis-sentinel-2
ports:
- 26380:26379
command: redis-sentinel /usr/local/etc/redis/sentinel.conf
volumes:
- F:\DockerCluster\redis\sentinel\sentinel2.conf:/usr/local/etc/redis/sentinel.conf
sentinel3:
image: redis:5.0.14
container_name: redis-sentinel-3
ports:
- 26381:26379
command: redis-sentinel /usr/local/etc/redis/sentinel.conf
volumes:
- F:\DockerCluster\redis\sentinel\sentinel3.conf:/usr/local/etc/redis/sentinel.conf
networks:
default:
external:
name: master-slave_default
最后这个 networks:default:external:name 要怎么填呢?
docker inspect redis-master
如下图所示,因此我填写的是 master-slave_default:
关于 networks:default:external:name 参数的作用,如下图所示:
这里希望把新增的3台“哨兵”容器,加入到已经存在的主从集群的网络 master-slave_default 中去。
2.2 sentinel.conf
另外,图中红框中的 "IPAddress" 也可以用在 sentinel1.conf 中:
port 26379
dir /tmp
sentinel monitor mymaster 172.22.0.2 6379 2
sentinel auth-pass mymaster abc123
sentinel down-after-milliseconds mymaster 30000
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 180000
sentinel deny-scripts-reconfig yes
sentinel2.conf / sentinel3.conf 和 sentinel1.conf 的内容一样的!
-
sentinel monitor <master-name> <ip> <port> <quorum>
: 告诉哨兵监视这个 master 节点- master-name 是对某个master+slave组合的一个区分标识(一套sentinel是可以监听多套master+slave这样的组合的);
- ip 和 port 分别指master节点的 ip 和 端口号;
- quorum 这个参数是进行客观下线的一个依据,意思是至少有 quorum 个sentinel主观的认为这个master有故障,才会对这个master进行下线以及故障转移;
-
sentinel auth-pass <master-name> <password>
: 设置sentinel连接的master和slave的密码,这个需要和redis.conf文件中设置的密码一样 -
sentinel down-after-milliseconds <master-name> <milliseconds>
: 这个配置其实就是进行主观下线的一个依据,如果这台sentinel超过 milliseconds 这个时间都无法连通master包括slave(slave不需要客观下线,因为不需要故障转移)的话,就会主观认为该master已经下线(实际下线需要客观下线的判断通过才会下线) -
sentinel parallel-syncs <master-name> <numreplicas>
: 当在执行故障转移时,设置 numreplicas 个slave同时进行切换master,该值越大,则可能就有越多的slave在切换master时不可用,可以将该值设置为1,即一个一个来,这样在某个slave进行切换master同步数据时,其余的slave还能正常工作,以此保证每次只有一个从服务器处于不能处理命令请求的状态。 -
sentinel failover-timeout <master-name> <milliseconds>
: 执行故障迁移的时间超过 milliseconds,即在指定时间内没有大多数的sentinel 反馈master下线,该故障迁移计划则失效 -
sentinel deny-scripts-reconfig yes
: 默认情况下,SENTINEL SET将无法在运行时更改通知脚本和客户端重新配置脚本。这避免了一个微不足道的安全问题,客户机可以将脚本设置为任何值,并触发故障转移以执行程序。 -
SENTINEL SET <name> <option> <value>
: 这个命令很像Redis的CONFIG SET命令,用来改变指定master的配置。支持多个<option>
和<value>
。例如以下实例:SENTINEL SET objects-cache-master down-after-milliseconds 1000
2.3 启动哨兵集群
接着就是执行 docker-compose up -d 命令:
如果File Sharing 的弹框点慢了,还会有包括,再执行一次 docker-compose up -d 命令就好了。
启动成功后,sentinel1.conf / sentinel2.conf / sentinel3.conf 这三个文件会被追加一些内容。
接着,我们用 docker stop redis-master 停止了原来的 master 节点的运行,过了一会,redis-slave-2 被自动选为了新的 master 节点:
redis-cli -h localhost -p 26379 这个命令可以连接 redis-sentinel 哨兵服务。
三、redis cluster集群模式
首先需要对Redis Cluster模型有一个认识Redis Cluster master-replica model:
- Redis Cluster中的每个节点负责哈希槽的子集,因此,例如,您可能有一个包含3个节点的集群,其中:
- 节点A包含从0到5461的哈希槽。
- 节点B包含从5462到10922的哈希槽。
- 节点C包含从10923到16383的哈希槽。
- 为了在主节点子集出现故障或无法与大多数节点通信时保持可用,Redis Cluster使用 主副本模型,其中每个哈希槽具有1个(主节点本身)到N个副本(N-1个附加副本节点)。
- 在具有节点A、B、C的示例集群中,如果节点B出现故障,集群将无法继续,因为我们不再能够提供5462-10922范围内的哈希槽。
- 但是,在创建集群时(或稍后),我们会向每个主节点添加一个副本节点,这样最终的集群就由A、B、C组成,A、B、C是主节点,A1、B1、C1是副本节点。这样,如果节点B出现故障,系统就能够继续。
- 节点B1复制B,如果B失败,集群将提升节点B1作为新的主节点,并将继续正常运行。
- 但是,请注意,如果节点B和B1同时出现故障,Redis群集将无法继续运行。
接下来,就是模拟搭建这样一个集群。
3.1 docker-compose.yml
version: '3.8'
services:
redis-node-master-1:
image: redis:5.0.14
container_name: cluster-redis-1
ports:
- 6381:6379
command: "redis-server --requirepass abc123 --masterauth abc123 --appendonly yes \
--cluster-enabled yes \
--cluster-config-file nodes.conf \
--cluster-node-timeout 15000"
volumes:
- F:\DockerCluster\redis\cluster\data\redis1:/data
redis-node-master-2:
image: redis:5.0.14
container_name: cluster-redis-2
ports:
- 6382:6379
command: "redis-server --requirepass abc123 --masterauth abc123 --appendonly yes \
--cluster-enabled yes \
--cluster-config-file nodes.conf \
--cluster-node-timeout 15000"
volumes:
- F:\DockerCluster\redis\cluster\data\redis2:/data
redis-node-master-3:
image: redis:5.0.14
container_name: cluster-redis-3
ports:
- 6383:6379
command: "redis-server --requirepass abc123 --masterauth abc123 --appendonly yes \
--cluster-enabled yes \
--cluster-config-file nodes.conf \
--cluster-node-timeout 15000"
volumes:
- F:\DockerCluster\redis\cluster\data\redis3:/data
redis-node-slave-1:
image: redis:5.0.14
container_name: cluster-redis-1-slave
ports:
- 6384:6379
command: "redis-server --requirepass abc123 --masterauth abc123 --appendonly yes \
--cluster-enabled yes \
--cluster-config-file nodes.conf \
--cluster-node-timeout 15000"
volumes:
- F:\DockerCluster\redis\cluster\data\redis1-slave:/data
redis-node-slave-2:
image: redis:5.0.14
container_name: cluster-redis-2-slave
ports:
- 6385:6379
command: "redis-server --requirepass abc123 --masterauth abc123 --appendonly yes \
--cluster-enabled yes \
--cluster-config-file nodes.conf \
--cluster-node-timeout 15000"
volumes:
- F:\DockerCluster\redis\cluster\data\redis2-slave:/data
redis-node-slave-3:
image: redis:5.0.14
container_name: cluster-redis-3-slave
ports:
- 6386:6379
command: "redis-server --requirepass abc123 --masterauth abc123 --appendonly yes \
--cluster-enabled yes \
--cluster-config-file nodes.conf \
--cluster-node-timeout 15000"
volumes:
- F:\DockerCluster\redis\cluster\data\redis3-slave:/data
- 先来说一下 yml配置文件中换行问题,为了保持可读性,我选择让 command 中的内容换行,需要注意三点
- 在
yml
中在每个需要换行的末尾 加上一个\
; - 因为多个指令参数之间需要有空格分隔,所以除最后一行以外其他行是以
[空格]\
结尾的; - 把整个字符串用双引号 ("") 括起来
- 在
- 关于 volumes 的使用
- 冒号(:)前,通常是你的宿主机,比如 Windows/Ubuntu/MacOS 的路径;
- 冒号(:)后,则是你用docker创建的容器,也就是用 docker ps 能查看到的那些容器内的路径;
- Windows系统的路径分隔符是 ****,而 Linux系统的路径分隔符则是 /;
- cluster-enabled yes 该配置表示启用群集支持:开启后,将Redis服务实例作为cluster节点启动
- 普通Redis服务实例不能是Redis Cluster的一部分,只有作为Redis Cluster节点启动的服务实例才可以。
- cluster-config-file nodes.conf 该配置用于指定集群配置文件名
- 每个Redis Cluster节点都有一个集群配置文件,此文件不可手动编辑。它由Redis节点创建和更新。
- 每个Redis Cluster节点都需要不同的集群配置文件。确保在同一系统中运行的实例没有重叠的集群配置文件名。
- cluster-node-timeout 15000 该配置单位为毫秒,超过 cluster-node-timeout 无法访问才能被视为处于故障状态
- 大多数其他内部时间限制是该节点超时的倍数。
启动就不多说了,还是 docker-compose up -d :
3.2 cluster nodes
进入 cluster-redis-1 容器:
docker exec -it cluster-redis-1 /bin/bash
接着在容器内执行:
redis-cli
127.0.0.1:6379> auth abc123
127.0.0.1:6379> cluster nodes
结果如下图所示:
此时,当前节点并不知道其他 cluster 节点的存在。
3.3 节点握手 cluster meet
cluster meet <ip> <port>
注意,我试了一下,这里直接使用容器名是不行的:
所以,我又另外开了一个 cmd 来查看容器ip:
docker inspect cluster-redis-1 | findstr IPAddress
docker inspect cluster-redis-2 | findstr IPAddress
docker inspect cluster-redis-3 | findstr IPAddress
docker inspect cluster-redis-1-slave | findstr IPAddress
docker inspect cluster-redis-2-slave | findstr IPAddress
docker inspect cluster-redis-3-slave | findstr IPAddress
我查到,在我的Docker中,
- cluster-redis-1 的IP地址为 172.28.0.4;
- cluster-redis-2 的IP地址为 172.28.0.6;
- cluster-redis-3 的IP地址为 172.28.0.3;
- cluster-redis-1-slave 的IP地址为 172.28.0.7;
- cluster-redis-2-slave 的IP地址为 172.28.0.5;
- cluster-redis-3-slave 的IP地址为 172.28.0.2;
进入 cluster-redis-1 容器,执行 redis-cli,然后在Redis客户端中执行以下操作
127.0.0.1:6379> cluster meet 172.28.0.2 6379
127.0.0.1:6379> cluster meet 172.28.0.3 6379
127.0.0.1:6379> cluster meet 172.28.0.4 6379
127.0.0.1:6379> cluster meet 172.28.0.5 6379
127.0.0.1:6379> cluster meet 172.28.0.6 6379
127.0.0.1:6379> cluster meet 172.28.0.7 6379
温馨小贴士:这里 cluster meet 使用的是 Docker网络 ip,而不是宿主机 ip,会导致你在宿主机上的客户端代码无法连接到 Redis集群,
所以,如果你希望宿主机上的客户端可以访问到该 Redis 集群,那么 ip 用你的宿主机 ip,端口使用进程对应的宿主机映射端口
可以参考一下这篇文章中的配置
最后,再执行 cluster nodes 查看节点:
通过 cluster nodes 打印的信息可以得知 cluster-redis-1 和其他的 Redis Cluster 节点已经是 connected 状态。
但是问题是,所有集群节点都是 master,所以接下来要设置主从关系。
3.4 设置从节点 cluster replicate
我就在 cluster-redis-1 里面执行:
设置 cluster-redis-1-slave 为 cluster-redis-1 的从节点
# redis-cli -h 172.28.0.7 -p 6379 -a abc123 cluster replicate 623bdaf73485a97e95e4c2f620c309fd308de677
设置 cluster-redis-2-slave 为 cluster-redis-2 的从节点:
# redis-cli -h 172.28.0.5 -p 6379 -a abc123 cluster replicate 46a194cefaed112b59c5a492eb08926245174051
设置 cluster-redis-3-slave 为 cluster-redis-3 的从节点:
# redis-cli -h 172.28.0.2 -p 6379 -a abc123 cluster replicate a646c788f7dc3ec7add05e8ecd78fb779de99a87
在 cluster-redis-1 容器中,再次查看 cluster nodes:
此时,主从关系已经设置好了。
3.4 设置插槽cluster addslots
登入 cluster-redis-1 容器,添加插槽 redis-cli -a abc123 cluster addslots {0..5461}:
登入 cluster-redis-2 容器,添加插槽 redis-cli -a abc123 cluster addslots {5462..10922}:
登入 cluster-redis-3 容器,添加插槽 redis-cli -a abc123 cluster addslots {10923..16383}:
参考文档:
为什么redis-cluster使用16384 slots?
redis 集群分配 slot (error) ERR Invalid or out of range slot 错误
3.5 cluster slots
登入 cluster-redis-1 容器,执行 redis-cli,完整密码验证后再输入 cluster slots 查看Redis Cluster插槽情况:
如图所示:
- 172.28.0.4(主)和172.28.0.7(副)负责插槽 0~5641
- 172.28.0.6(主)和172.28.0.5(副)负责插槽 5462~10922
- 172.28.0.3(主)和172.28.0.2(副)负责插槽 10923~16383
3.6 cluster info 查看集群信息
如果出现 cluster_state:fail,那么可能是0~16383插槽中还没有未分配负责的 master节点。
详情参考 redis cluster info显示cluster_state:fail解决方案
3.7 redis-cli -c
127.0.0.1:6379> set mynameis geekziyu
(error) MOVED 14286 172.28.0.3:6379 --> 提示该key需要存储到14286号槽中,负责这个槽的节点是172.28.0.3
127.0.0.1:6379> cluster keyslot mynameis
(integer) 14286
- CLUSTER keyslot <key>:列出key被放置在哪个槽上。
打印 -> Redirected to slot [14286] located at 172.28.0.3:6379 之后,连接的Redis节点就已经改变了,因此需要再次验证密码