Docker实现Redis主从配置、哨兵模式
1.概述
一般的文档,都把redis的集群方式分成三种:主从、哨兵、集群。但是这么分类很不严谨,哨兵模式,单独使用是没有意义的,哨兵的作用有两个:
1.通过发送命令,让Redis服务器返回监控其运行状态,包括主服务器和从服务器。
2.当哨兵监测到master宕机,会自动将slave切换成master,然后通过发布订阅模式通知其他的从服务器,修改配置文件,让它们切换主机。
说白了,哨兵就是一个打辅助的,本身并不提供数据存储功能,能独立使用的方式只有两种,主从模式和集群模式,所以我认为将redis分为两类比较合适:
1.主从集群配合哨兵使用
2.分布式(分区)集群
本文将介绍redis主从集群及哨兵模式的使用。
2.主从集群
2.1搭建主从集群
主从集群,将数据库分为两中角色,一种是主数据库(master),另一种是从数据库(slave)。主数据库可以进行读写操作,从数据库只能有读操作(并不一定,只是推荐这么做,后续会说明)。当主数据库有数据写入,会将数据同步复制给从节点,一个主数据库可以同时拥有多个从数据库,而从数据库只能拥有一个主数据库。值得一提的是,从节点也可以有从节点,级联结构。
redis主从复制示意图.png
2.1.1下载镜像
docker pull redis
2.1.2创建集群网络
因为我们将采用桥接的网络来配置我们的容器,如果不创建和指定,一但容器重启ip地址就会改变,直接导致我们的构建的容器网络失效
docker network create redis_group
2.1.3配置文件(redis.conf)
最小化的主配置文件(/etc/redis/redis-master.conf):
bind 0.0.0.0
requirepass 123456
# 哨兵模式主从切换时使用
masterauth 123456
最小化的第一个从配置文件(/etc/redis/redis-slave1.conf):
bind 0.0.0.0
requirepass 123456
masterauth 123456
replicaof redis-master 6379
最小化的第二个从配置文件(/etc/redis/redis-slave2.conf):
bind 0.0.0.0
requirepass 123456
masterauth 123456
replicaof redis-master 6379
2.1.4启动
因为在一台机器上部署,所以映射为不同的端口号
# 启动主节点
docker run -itd --name redis-master -e TZ="Asia/Shanghai" --network redis_group --network-alias redis-master -v /etc/redis:/etc/redis -p 6379:6379 --restart=always redis redis-server /etc/redis/redis-master.conf
# 启动从节点1
docker run -itd --name redis-slave1 -e TZ="Asia/Shanghai" --network redis_group --network-alias redis-slave1 -v /etc/redis:/etc/redis -p 6380:6379 --restart=always redis redis-server /etc/redis/redis-slave1.conf
# 启动从节点2
docker run -itd --name redis-slave2 -e TZ="Asia/Shanghai" --network redis_group --network-alias redis-slave2 -v /etc/redis:/etc/redis -p 6381:6379 --restart=always redis redis-server /etc/redis/redis-slave2.conf
2.1.5查看信息
info replication
2.2 复制原理
当从节点启动后,会向主数据库发送SYNC命令。同时主数据库收到SYNC命令后会开始在后台保存快照(即RDB持久化,在主从复制时,会无条件触发RDB),并将保存快照期间接收到的命令缓存起来,当快照完成后,redis会将快照文件和所有缓存命令发送给从数据库。从数据库接收到快照文件和缓存命令后,会载入快照文件和执行命令,也就是说redis是通过RDB持久化文件和redis缓存命令来实现主从复制。一般在建立主从关系时,第一次同步会进行复制初始化。
以上过程为复制初始化,复制初始化结束后,主数据库每当收到写命令时,就会将命令同步给从数据库,保证主从数据一致性。
这里需要提一句,在Redis2.6之前,每次主从数据库断开连接后,Redis需要重新执行复制初始化,在数据量大的情况下,非常低效。而在Redis2.8之后,在断线重连后,主数据库只需要将断线期间执行的命令传送给从数据库。
redis主从复制原理.png
2.3乐观复制
Redis采用了乐观复制的策略,也就是在一定程度内容忍主从数据库的内容不一致。具体来说,Redis在主从复制的过程中,本身就是异步的,在主数据库执行完客户端请求后会立即将结果返回给客户端,并异步的将命令同步给从数据库,但是这里并不会等待从数据库完全同步之后,再返回客户端。这一特性虽然保证了主从复制期间性能不受影响,但是也会产生一个数据不一致的时间窗口,如果在这个时间窗口期间网络突然断开连接,就会导致两者数据不一致。如果不在配置文件中添加其他策略,那就默认会采用这种方式,乐观二字也就体现在这里(是不是有点想当然的认为自己不会这么倒霉的停在这个空窗期)。
保证从节点的数量和延迟性功能,通过min-replicas-to-write、min-replicas-max-lag参数配置定义:
min-replicas-to-write 3
min-replicas-max-lag 10
min-replicas-to-write:健康的slave的个数小于N,mater就禁止写入。master最少得有多少个健康的slave存活才能执行写命令。这个配置虽然不能保证N个slave都一定能接收到master的写操作,但是能避免没有足够健康的slave的时候,master不能写入来避免数据丢失,默认为0,关闭该功能
min-replicas-max-lag:延迟小于min-replicas-max-lag秒的slave才认为是健康的slave
2.4 增量复制
增量复制是基于以下4点实现的:
1.主节点除了备份RDB文件之外还会维护着一个环形积压队列,以及环形队列的写索引和从节点同步的全局offset,环形队列用于存储最新的操作数据。
2.从数据库会存储主数据库的运行id,每个redis实例会拥有一个唯一的运行id,当实例重启后,就会自动生成一个新的id。
3.主节点在复制同步阶段,主数据库每将一个命令传递给从数据库时,都会将命令存放到积压队列,并记录当前积压队列中存放命令的偏移量。
4.从数据库接收到主数据库传来的命令时,会记录下偏移量。
在2.8之后,主从复制不再发送SYNC命令,取而代之的是PSYNC,格式为:“PSYNC ID offset”。
当从节点和主节点断开重连之后,会把从节点维护的offset,也就是上一次同步到哪里的这个值告诉主节点,同时会告诉主节点上次和当前从节点连接的主节点的runid,满足下面两个条件,Redis不会全量复制,也就是说,不满足以下条件还是会全量复制。
1.从节点传递的run id和master的run id一致。
2.主节点在环形队列上可以找到对应offset的值。
积压队列本质上是一个固定长度的循环队列,默认情况下积压队列的大小为1MB,可以通过配置文件:
repl-backlog-size 1mb
来设置,积压队列越大,允许主从数据库断线的时间就越长
Redis同时也提供了当没有slave需要同步的时候,多久可以释放环形队列,默认一小时:
repl-backlog-ttl 3600
3.哨兵模式
作用:
1.监控主数据库和从数据库是否能够正常运行
2.主数据库出现故障时自动将从数据库转换为主数据库。
普通的主从模式,当主数据库崩溃时,需要手动切换从数据库成为主数据库:
1.SLAVEOF NO ONE(将从属服务器用作新的主服务器):使得这个从属服务器关闭复制功能,并从从属服务器转变回主服务器,原来同步所得的数据集不会被丢弃。
2.SLAVEOF host port(将当前服务器转变为指定服务器的从属服务器):使当前服务器停止对旧主服务器的同步,丢弃旧数据集,转而开始对新主服务器进行同步。
手动重启和恢复都相对麻烦,这时候就需要哨兵登场了,哨兵是一个独立的进程:
redis主从复制示意图(哨兵模式).drawio.png
哨兵本身也有单点故障的问题,所以在一个一主多从的Redis系统中,可以使用多个哨兵进行监控,哨兵不仅会监控主数据库和从数据库,哨兵之间也会相互监控:
redis主从复制示意图(哨兵模式).drawio.png
3.1配置哨兵模式
3.1.1配置文件(sentinel.conf)
最小化的主配置文件(/etc/redis/redis-sentinel-master.conf):
bind 0.0.0.0
# mymaster 可以自定义,redis-master redis服务器地址/网络名称,6379 redis服务器端口,2 最小投票数quorum,指明当有多少个sentinel认为一个master失效时,master才算真正失效。
sentinel monitor mymaster redis-master 6379 2
# 设置连接master和slave时的密码,注意的是sentinel不能分别为master和slave设置不同的密码,因此master和slave的密码应该设置相同。
sentinel auth-pass mymaster 123456
# 允许Sentinel接受主机名
SENTINEL resolve-hostnames yes
最小化的第一个从配置文件(/etc/redis/redis-sentinel-slave1.conf):
bind 0.0.0.0
# mymaster 可以自定义,redis-master redis服务器地址/网络名称,6379 redis服务器端口,2 最小投票数quorum,指明当有多少个sentinel认为一个master失效时,master才算真正失效。
sentinel monitor mymaster redis-master 6379 2
# 设置连接master和slave时的密码,注意的是sentinel不能分别为master和slave设置不同的密码,因此master和slave的密码应该设置相同。
sentinel auth-pass mymaster 123456
# 允许Sentinel接受主机名
SENTINEL resolve-hostnames yes
最小化的第二个从配置文件(/etc/redis/redis-sentinel-slave2.conf):
bind 0.0.0.0
# mymaster 可以自定义,redis-master redis服务器地址/网络名称,6379 redis服务器端口,2 最小投票数quorum,指明当有多少个sentinel认为一个master失效时,master才算真正失效。
sentinel monitor mymaster redis-master 6379 2
# 设置连接master和slave时的密码,注意的是sentinel不能分别为master和slave设置不同的密码,因此master和slave的密码应该设置相同。
sentinel auth-pass mymaster 123456
# 允许Sentinel接受主机名
SENTINEL resolve-hostnames yes
3.1.2启动
因为在一台机器上部署,所以映射为不同的端口号
# 启动主节点
docker run -itd --name redis-sentinel-master -e TZ="Asia/Shanghai" --network redis_group --network-alias redis-sentinel-master -v /etc/redis:/etc/redis -p 26379:26379 --restart=always redis redis-sentinel /etc/redis/redis-sentinel-master.conf
# 启动从节点1
docker run -itd --name redis-sentinel-slave1 -e TZ="Asia/Shanghai" --network redis_group --network-alias redis-sentinel-slave1 -v /etc/redis:/etc/redis -p 26380:26379 --restart=always redis redis-sentinel /etc/redis/redis-sentinel-slave1.conf
# 启动从节点2
docker run -itd --name redis-sentinel-slave2 -e TZ="Asia/Shanghai" --network redis_group --network-alias redis-sentinel-slave2 -v /etc/redis:/etc/redis -p 26381:26379 --restart=always redis redis-sentinel /etc/redis/redis-sentinel-slave2.conf
遗留问题:
1.redis主从数据目录映射到windows目录(linux未尝试)后报错:
1:S 21 Oct 2021 21:18:08.976 * Reconnecting to MASTER redis-master:6379 after failure
1:S 21 Oct 2021 21:18:08.977 * MASTER <-> REPLICA sync started
1:S 21 Oct 2021 21:18:08.977 * Non blocking connect for SYNC fired the event.
1:S 21 Oct 2021 21:18:08.977 * Master replied to PING, replication can continue...
1:S 21 Oct 2021 21:18:08.978 * Partial resynchronization not possible (no cached master)
1:S 21 Oct 2021 21:18:08.978 * Full resync from master: 9cf40aee6f9fb9a0c5390dd466b4ec7dbad08692:56
1:S 21 Oct 2021 21:18:09.072 * MASTER <-> REPLICA sync: receiving 175 bytes from master to disk
1:S 21 Oct 2021 21:18:09.075 * MASTER <-> REPLICA sync: Flushing old data
1:S 21 Oct 2021 21:18:09.075 * MASTER <-> REPLICA sync: Loading DB in memory
1:S 21 Oct 2021 21:18:09.083 # Failed trying to load the MASTER synchronization DB from disk
同步失败了,Failed trying to load the MASTER synchronization DB from disk,然后slave又不断在重试。
2.故障转移失败
1:X 21 Oct 2021 21:49:59.672 # +try-failover master mymaster 172.18.0.2 6379
1:X 21 Oct 2021 21:49:59.703 # +vote-for-leader 6f79241aa46c9caa1f2d6b670b759ab5bcdcdef4 1
1:X 21 Oct 2021 21:49:59.760 # 5d7df79c5345dc6fc8584e8fd409338d3004589a voted for 6f79241aa46c9caa1f2d6b670b759ab5bcdcdef4 1
1:X 21 Oct 2021 21:49:59.763 # fe7840408e89d46653fe8dbd2aaa998ea6f8bb11 voted for 6f79241aa46c9caa1f2d6b670b759ab5bcdcdef4 1
1:X 21 Oct 2021 21:49:59.780 # +elected-leader master mymaster 172.18.0.2 6379
1:X 21 Oct 2021 21:49:59.780 # +failover-state-select-slave master mymaster 172.18.0.2 6379
1:X 21 Oct 2021 21:49:59.832 # -failover-abort-no-good-slave master mymaster 172.18.0.2 6379
1:X 21 Oct 2021 21:49:59.891 # Next failover delay: I will not start a failover before Thu Oct 21 21:56:00 2021