Loading

[08] Redis 主从复制&哨兵机制

1. 主从复制

主从复制,就是主机数据更新后根据配置和策略,自动同步到备机的 Master-Slave 机制,Master 以写为主,Slave 以读为主。

和 MySQL 主从复制的原因一样,Redis 虽然读取写入的速度都特别快,但是也会产生读压力特别大的情况。为了分担读压力,Redis 支持主从复制,Redis 的主从结构可以采用一主多从或者级联结构,Redis 主从复制可以根据是否是全量分为全量同步和增量同步。

【补充】https://www.cnblogs.com/daofaziran/p/10978628.html

1.1 案例演示

a. 修改配置

  1. 开启 daemonize yes 以允许后台运行;
  2. 注掉 bind 127.0.0.1 以允许来自任意一个网卡的 Redis 请求;
  3. 配置 protected-mode no 关闭保护模式;
  4. 指定端口 port <port>
  5. 指定当前工作目录 dir <绝对路径>
  6. 指定 pid 文件名称 pidfile <绝对路径.pid>
  7. 指定 log 文件名称 logfile <绝对路径.log>
  8. 指定持久化文件名称 dbfilename <xxx.rdb>
  9. Master 指定密码 requirepass <pwd> & Slave 指定访问主机的密码 masterauth "<pwd>"

Master 如果配置了 requirepass 参数(需要密码登陆),那么 Slave 就要配置 masterauth 来设置校验密码,否则的话 Master 会拒绝 Slave 的访问请求。

b. 基本命令

replicaof <IP> <Port>     # 写入redis.conf的方式配置主从关系(配从不配主)
slaveof <IP> <Port>       # 在实例运行期间通过命令配置主从关系(每次与Master断开之后,都需要重新连接)
slaveof no one            # 使当前数据库停止与其他数据库的同步,转成主数据库
info replication          # 查看复制节点的主从关系和配置信息

c. 运行时情况

(1)查看日志观察主从关系

(2)info replication 命令查看复制节点的主从关系和配置信息

(3)从机可以执行写命令吗?

(4)Master 启动,写到 k3,Slave1 跟着 Master 同时启动,也复写到 k3,这时 Slave2 才启动,那之前的是否也可以复制?

从机首次同步,会全量复制;后续增量复制。

(5)Master 挂了,Slave 会顶上吗?

从机不动,原地待命,从机数据可以正常使用;等待主机重启动归来。

(6)Master 重启后主从关系还在吗?从机还能否顺利复制?

(7)slaveof 在运行期间修改 Slave 节点的信息,如果该数据库已经是某个主数据库的从数据库,那么会停止和原主数据库的同步关系转而和新的主数据库同步。缺点是重启之后主从关系失效。

1.2 同步优化

由于所有的写操作都是先在 Master 上操作,然后同步更新到 Slave 上,所以从 Master 同步到 Slave 机器有一定的延迟,当系统很繁忙的时候,延迟问题会更加严重,Slave 机器数量的增加也会使这个问题更加严重。

为了缓解主从复制风暴(多个从节点同时复制主节点导致主节点压力过大),可以做如下架构,让部分从节点与从节点(与主节点同步)同步数据:

上一个 Slave 可以是下一个 Slave 的 Master,Slave 同样可以接收其他 Slaves 的连接和同步请求,那么该 Slave 作为了链条中下一个的 Master,可以有效减轻 Master 的写压力,去中心化降低风险。

若中途变更转向,会清除之前的数据,重新建立拷贝最新的。

既然 6380 也算 Master 了,那它可不可以有写操作呢?No!

这种“薪火相传”的复制方式风险是一旦某个 Slave 宕机,后面的 Slave 都没法备份!所以,当 Master 宕机后,后面的 Slave 可以通过 slaveof no one 升为 Master,其后面的 Slave 不用做任何修改。

1.3 复制原理

主从刚刚连接的时候,进行全量同步;全同步结束后,进行增量同步。当然,如果有需要,Slave 在任何时候都可以发起全量同步。Redis 策略是,无论如何,首先会尝试进行增量同步,如不成功,要求从机进行全量同步。

a. 全量同步

Redis 全量复制一般发生在 Slave 初始化阶段,这时 Slave 需要将 Master 上的所有数据都复制一份(Slave 自身原有数据会被 Master 数据覆盖清除)。具体步骤如下:

  • 从服务器连接主服务器,发送 SYNC 命令;
  • 主服务器接收到 SYNC 命名后,开始执行 BGSAVE 命令生成 RDB 文件并使用缓冲区记录此后执行的所有写命令;
  • 主服务器 BGSAVE 执行完后,向所有从服务器发送快照文件,并在发送期间继续记录被执行的写命令;
  • 从服务器收到快照文件后丢弃所有旧数据,载入收到的快照;
  • 主服务器快照发送完毕后开始向从服务器发送缓冲区中的写命令;
  • 从服务器完成对快照的载入,开始接收命令请求,并执行来自主服务器缓冲区的写命令;

完成上面几个步骤后就完成了从服务器数据初始化的所有操作,从服务器此时可以接收来自用户的读请求。

主从关系的保持,是要发心跳包维系的:repl-ping-replica-period 10,Master 发出 PING 包的周期,默认是 10s。

b. 增量同步

Redis 增量复制是指 Slave 初始化后开始正常工作过程中,主服务器发生的写操作同步到从服务器的过程。

增量复制的过程主要是主服务器每执行一个写命令就会向从服务器发送相同的写命令,从服务器接收并执行收到的写命令。

Master 会检查 backlog 里面的 offset,Master 和 Slave 都会保存一个复制的 offset 还有一个 MasterId,offset 是保存在 backlog 中的。Master 只会把已经复制的 offset 后面的数据复制给 Slave,类似断点续传。

1.4 复制小结

  • Replication Id:简称 replid,是数据集的标记,id 一致则说明是同一数据集。每一个 Master 都有唯一的 replid,Slave 则会继承 Master 节点的 replid;
  • offset:偏移量,随着记录在 repl_baklog 中的数据增多而逐渐增大。Slave 完成同步时也会记录当前同步的 offset。如果 Slave 的 offset 小于 Master 的 offset,说明 Slave 数据落后于 Master,需要更新。

主从全量同步:

主从增量同步(Slave 重启或后期数据变化):

  • 首次连接,全量复制;
  • 发送心跳,保持通信;
  • 全量完成,增量复制;
  • 从机下线,重连续传。

【复制缺点】

Master 挂了怎们办?

那就挂着。默认情况下,不会在 Slave 节点中自动重选一个 Master。

那每次都要人工干预?

无人值守自动选举新主变成刚需!引入哨兵机制。

2. 哨兵机制

2.1 哨兵作用

Sentinel 哨兵是特殊的 Redis 服务,不提供读写服务,主要用来监控 Redis 实例节点。

作用:

  • 【主从监控】监控主从 Redis 库运行是否正常;
  • 【消息通知】哨兵可以将故障转移的结果发送给客户端;
  • 【故障转移】如果 Master 异常,则会进行主从切换, 将其中一个 Slave 作为新 Master;
  • 【配置中心】Sentinel 充当 Redis 客户端的服务发现来源,当集群发生故障转移时,会将最新信息推送给 Redis 的客户端。

哨兵架构下 Client 端第一次从哨兵找出 Redis 的主节点,后续就直接访问 Redis 的主节点,不会每次都通过 Sentinel 代理访问 Redis 的主节点,当 Redis 的主节点发生变化,哨兵会第一时间感知到,如果故障了根据投票数自动将从库转换为主库,并且将新的 Redis 主节点通知给 Client 端(这里面 Redis 的 Client 端一般都实现了订阅功能,订阅 Sentinel 发布的节点变动消息)。

2.2 配置哨兵

(1)整体架构

(2)sentinel.conf 原始配置文件位置如下,复制该文件到 /opt/myRedis;

(3)修改 sentinel.conf 中的 sentinel monitor <master-name> <ip> <redis-port> <quorum> 以设置要监控的 Master 服务器(quorum 表示最少有几个哨兵认可客观下线,同意故障迁移的法定票数);

我们知道,网络是不可靠的,有时候一个 sentinel 会因为网络堵塞而误以为一个 Master 已经死掉了,在 Sentinel 集群环境下需要多个 Sentinel 互相沟通来确认某个 Master 是否真的死了,quorum 参数是进行客观下线的一个依据,意思是至少有 quorum 个 Sentinel 认为这个 Master 有故障,才会对这个 Master 进行下线以及故障转移。因为有的时候,某个 Sentinel 节点可能因为自身网络原因,导致无法连接 Master,而此时 Master 并没有出现故障,所以,这就需要多个 Sentinel 都一致认为该 Master 有问题,才可以进行下一步操作,这就保证了公平性和高可用。

(4)若 Master 设置了密码,需配置连接 Master 服务的密码 sentinel auth-pass <master-name> <password>

(5)其他可选配置

sentinel down-after-milliseconds <master-name> <milliseconds>
# 指定多少毫秒之后,主节点没有应答哨兵,此时哨兵主观上认为主节点下线

sentinel parallel-syncs <master-name> <nums>
# 表示允许并行同步的slave个数,当Master挂了后,哨兵会选出新的Master,此时剩余的slave会向新的master发起同步数据

sentinel failover-timeout <master-name> <milliseconds>
# 故障转移的超时时间,进行故障转移时,如果超过设置的毫秒,表示故障转移失败

sentinel notification-script <master-name> <script-path>
# 配置当某一事件发生时所需要执行的脚本

sentinel client-reconfig-script <master-name> <script-path>
# 客户端重新配置主节点参数脚本

# Example sentinel.conf ==================================================================

# 哨兵sentinel实例运行的端口 默认26379
port 26379

# 哨兵sentinel的工作目录
dir /tmp

# 哨兵sentinel监控的redis主节点的 ip port
# master-name:可以自己命名的主节点名字只能由字母A-z、数字0-9 、这三个字符".-_"组成
# quorum:配置多少个sentinel哨兵统一认为master主节点失联,那么这时客观上认为主节点失联
# sentinel monitor <master-name> <ip> <redis-port> <quorum>
sentinel monitor mymaster 127.0.0.1 6379 2

# 当在Redis实例中开启了requirepass foobared 授权密码
# 这样所有连接Redis实例的客户端都要提供密码
# 设置哨兵sentinel 连接主从的密码,注意必须为主从设置一样的验证密码
# sentinel auth-pass <master-name> <password>
sentinel auth-pass mymaster MySUPER--secret-0123passw0rd

# 指定多少ms之后,主节点没有应答哨兵sentinel,此时哨兵主观上认为主节点下线,默认30s
# sentinel down-after-milliseconds <master-name> <milliseconds>
sentinel down-after-milliseconds mymaster 30000

# 这个配置项指定了在发生failover主备切换时,最多可以有多少个slave同时对新的master进行同步,
# 这个数字越小,完成failover所需的时间就越长,但是如果这个数字越大,就意味着越多的slave因为
# replication而不可用。可以通过将这个值设为1来保证每次只有一个slave处于不能处理命令请求的状态。
# sentinel parallel-syncs <master-name> <numslaves>
sentinel parallel-syncs mymaster 1

# 故障转移的超时时间 failover-timeout 可以用在以下这些方面:
# 1) 同一个sentinel对同一个master两次failover之间的间隔时间。
# 2) 当一个slave从一个错误的master那里同步数据开始计算时间。直到slave被纠正为向正确的master那里同步数据时。
# 3) 当想要取消一个正在进行的failover所需要的时间。
# 4) 当进行failover时,配置所有slaves指向新的master所需的最大时间。不过,即使过了这个超时,
# slaves依然会被正确配置为指向master,但是就不按parallel-syncs所配置的规则来了默认三分钟
# sentinel failover-timeout <master-name> <milliseconds>
sentinel failover-timeout mymaster 180000

# SCRIPTS EXECUTION
# 配置当某一事件发生时所需要执行的脚本,可以通过脚本来通知管理员,例如当系统运行不正常时发邮件通知相关人员。
# 对于脚本的运行结果有以下规则:
# - 若脚本执行后返回1,那么该脚本稍后将会被再次执行,重复次数目前默认为10
# - 若脚本执行后返回2,或者比2更高的一个返回值,脚本将不会重复执行。
# - 若脚本在执行过程中由于收到系统中断信号被终止了,则同返回值为1时的行为相同。
# 一个脚本的最大执行时间为60s,如果超过这个时间,脚本将会被一个SIGKILL信号终止,之后重新执行。
# 通知型脚本: 当sentinel有任何警告级别的事件发生时(比如说redis实例的主观失效和客观失效等等),
# 将会去调用这个脚本,这时这个脚本应该通过邮件,SMS等方式去通知系统管理员关于系统不正常运行的
# 信息。调用该脚本时,将传给脚本两个参数,一个是事件的类型,一个是事件的描述。如果sentinel.conf文件中
# 配置了这个脚本路径,那么必须保证这个脚本存在于这个路径,并且是可执行的,否则sentinel无法正常启动成功。
# 通知脚本(shell编程)
# sentinel notification-script <master-name> <script-path>
sentinel notification-script mymaster /var/redis/notify.sh

# 客户端重新配置主节点参数脚本
# 当一个master由于failover而发生改变时,这个脚本将会被调用,通知相关的客户端关于
# master地址已经发生改变的信息。 以下参数将会在调用脚本时传给脚本: 
# <master-name> <role> <state> <from-ip> <from-port> <to-ip> <to-port>
# 目前<state>总是“failover”, <role>是“leader”或者“observer”中的一个。
# 参数 from-ip, from-port, to-ip, to-port是用来和旧的master和新的master(即旧的slave)
# 通信的这个脚本应该是通用的,能被多次调用,不是针对性的。
# sentinel client-reconfig-script <master-name> <script-path>
# 一般都是由运维来配置
sentinel client-reconfig-script mymaster /var/redis/reconfig.sh

(6)本案例配置的 sentinel.conf

(7)注意,由于会涉及重设 Master,所以须在每台机器的 redis.conf 中都设置 masterauth xxx。因为 Master 并不是固定不变的,主从复制案例中并没有给 6379 机器加该配置,所以现在要加上。以防后续 oldMaster 重连报 master_link_status:down 错误。

2.3 启动哨兵

redis-sentinel sentinel26379.conf --sentinel
redis-sentinel sentinel26380.conf --sentinel
redis-sentinel sentinel26381.conf --sentinel

查看日志:

再重新去看配置文件:

2.4 故障恢复

a. 切换流程

(1)6379 执行 shutdown 命令模拟主机宕机

(2)对 6380 和 6381 发送命令,看看这两台从机数据是否 OK;

【补充】Error: Server closed the connection / Error: Broken pipe

pipe 是管道的意思,管道里面是数据流,通常是从文件或网络套接字读取的数据。当该管道从另一端突然关闭时,会发生数据突然中断,即是“broken”。“Broken pipe”的意思是对端的管道已经断开,往往发生在远端把这个读/写管道关闭了,你无法再对这个管道进行读写操作。

(3)查看此时的 Sentinel 日志

(4)之前宕机的 Master 机器重启回来,谁将会是新老大?会不会双 Master 冲突?

不会。查看 Sentinel 日志可知:

(5)配置文件的内容在运行期间会被 Sentinel 动态修改;

Master-Slave 切换后,master_redis.conf、 slave_redis.conf 和 sentinel.conf 的内容都会发生改变,即 old_master_redis.conf 中会多一行 slaveof 的配置,sentinel.conf 的监控目标会随之调换。

b. 切换原理

Sentinel 基于心跳机制监测服务状态,每隔 1s 向集群的每个实例发送 ping 命令:

  • 主观下线:如果某 Sentinel 节点发现某实例未在规定时间响应,则认为该实例主观下线。
  • 客观下线:若超过指定数量(quorum)的 Sentinel 都认为该实例主观下线,则该实例客观下线。quorum 值最好超过 Sentinel 实例数量的一半。

1. SDown 主观下线(Subjectively Down)

所谓主观下线(Subjectively Down, 简称 SDOWN)指的是单个 Sentinel 实例对服务器做出的下线判断,即单个 Sentinel 认为某个服务下线(有可能是接收不到订阅,之间的网络不通等等原因)。主观下线就是说如果服务器在 down-after-milliseconds 给定的毫秒数之内没有回应 PING 命令或者返回一个错误消息, 那么这个 Sentinel 会主观的(单方面地)认为这个 Master 不可以用了。


Master 在多长时间内一直没有给 Sentinel 返回有效信息,则认定该 Master 主观下线。也就是说如果多久没联系上 redis-server,认为这个 redis-server 进入到失效(SDOWN)状态。

2. ODown 客观下线(Objectively Down)

ODOWN 需要一定数量的 Sentinel 达成一致意见才能认为一个 Master 客观上已经宕掉。

quorum 参数是进行客观下线的一个依据,法定人数/法定票数。意思是至少有 quorum 个 Sentinel 认为这个 Master 有故障才会对这个 Master 进行下线以及故障转移。因为有的时候,某个 Sentinel 节点可能因为自身网络原因导致无法连接 Master,而此时 Master 并没有出现故障,所以这就需要多个 Sentinel 都一致认为该 Master 有问题,才可以进行下一步操作,这就保证了公平性和高可用。

3. 选举哨兵 Leader

当主节点被判断客观下线以后,各个哨兵节点会进行协商,先选举出一个领导者哨兵节点(Leader)并由该领导者节点,也即被选举出的 Leader 进行 failover(故障迁移)。

查看 3 个哨兵的日志文件:

哨兵 Leader 是如何选出来的?

监视该主节点的所有哨兵都有可能被选为 Leader,选举使用的算法是 Raft 算法;算法的基本思路是先到先得:即在一轮选举中,哨兵 A 向 B 发送成为领导者的申请,如果 B 没有同意过其他哨兵,则会同意 A 成为领导者。详细过程略。

4. Sentinel Leader 推动故障切换流程并选出一个新 Master

  • Sentinel Leader 会对选举出的新 Master 执行 slaveof no one 操作,将其提升为 Master;
  • Sentinel Leader 向其它 slave 发送命令,让剩余的 slave 成为新的 Master 的 Slave;
  • Sentinel Leader 将之前已下线的老 Master 设置为新选出的新 Master 的从节点,当老 Master 重新上线后,它会成为新 Master 的 Slave;

上述的 failover 操作均由 Sentinel Leader 自己独自完成,完全无需人工干预。

2.5 脑裂问题

集群脑裂是由于 Master 和 Sentinel 处于不同的网络分区,使得 Sentinel 没有能够心跳感知到主节点,所以通过选举的方式提升了一个 Slave 为主,这样就存在了两个 Master,就像大脑分裂了一样,这样会导致客户端还在老的主节点那里写入数据,新节点无法同步数据,当网络恢复后,Sentinel 会将老的 Master 降为 Slave,这时再从新 Master 同步数据,就会导致数据丢失。

我们可以修改 Redis 的配置,可以设置最少的从节点数量以及缩短主从数据同步的延迟时间,达不到要求就拒绝请求,就可以避免大量的数据丢失。

  • min-replicas-to-write 1 表示 Slave 节点至少有 1 个才能同步数据;
  • min-replicas-max-lag 5 表示主从数据复制和同步的延迟不能超过 5s;

2.6 使用建议

  • 哨兵节点的数量应为多个,哨兵本身应该集群,保证高可用;
  • 各个哨兵节点的配置应一致;
  • 如果哨兵节点部署在 Docker 等容器里面,尤其要注意端口的正确映射;
  • “哨兵集群+主从复制”并不能保证数据零丢失。

【补充】为什么 Sentinel 的数量应该配置为奇数个?

一个 Sentinel 选举成为 Leader 的最低票数为 Max {quorum, Sentinel节点数/2+1},如果 Sentinel 集群只有 2 个 Sentinel 节点,则 Sentinel.CNT/2 + 1 = 2/2 + 1 = 2,即 Leader 最低票数至少为 2,当该 Sentinel 集群中由一个 Sentinel 节点故障后,仅剩的一个 Sentinel 节点是永远无法成为 Leader。

也可以由此公式可以推导出,Sentinel 集群允许 1 个 Sentinel 节点故障则需要 3 个节点的集群;允许 2 个节点故障则需要 5 个节点集群。

posted @ 2020-09-05 12:17  tree6x7  阅读(254)  评论(0编辑  收藏  举报