目录


一、高可用基础-主从复制


1、Redis 主从复制

单机的 redis,能够承载的 QPS 大概就在上万到几万不等。对于缓存来说,一般都是用来支撑读高并发的。因此架构做成主从(master-slave)架构,一主多从,主负责写,并且将数据复制到其它的 slave 节点,从节点负责读。所有的读请求全部走从节点。这样也可以很轻松实现水平扩容,支撑读高并发

redis-master-slave

redis replication -> 主从架构 -> 读写分离 -> 水平扩容支撑读高并发

2、Redis Replication 的核心机制

  • redis 采用异步方式复制数据到 slave 节点,不过 redis2.8 开始,slave node 会周期性地确认自己每次复制的数据量;
  • 一个 master node 是可以配置多个 slave node 的;
  • slave node 也可以连接其他的 slave node;
  • slave node 做复制的时候,不会 block master node 的正常工作;
  • slave node 在做复制的时候,也不会 block 对自己的查询操作,它会用旧的数据集来提供服务;但是复制完成的时候,需要删除旧数据集,加载新数据集,这个时候就会暂停对外服务了;
  • slave node 主要用来进行横向扩容,做读写分离,扩容的 slave node 可以提高读的吞吐量。

注意,如果采用了主从架构,那么建议必须开启 master node 的持久化,不建议用 slave node 作为 master node 的数据热备,因为那样的话,如果你关掉 master 的持久化,可能在 master 宕机重启的时候数据是空的,然后可能一经过复制, slave node 的数据也丢了。

另外,master 的各种备份方案,也需要做。万一本地的所有文件丢失了,从备份中挑选一份 rdb 去恢复 master,这样才能确保启动的时候,是有数据的,即使采用了后续讲解的高可用机制,slave node 可以自动接管 master node,但也可能 sentinel 还没检测到 master failure,master node 就自动重启了,还是可能导致上面所有的 slave node 数据被清空。

3、Redis 主从复制的核心原理

当启动一个 slave node 的时候,它会发送一个 PSYNC 命令给 master node。

如果这是 slave node 初次连接到 master node,那么会触发一次 full resynchronization 全量复制。此时 master 会启动一个后台线程,开始生成一份 RDB 快照文件,

同时还会将从客户端 client 新收到的所有写命令缓存在内存中。RDB 文件生成完毕后, master 会将这个 RDB 发送给 slave,slave 会先写入本地磁盘,然后再从本地磁盘加载到内存中,

接着 master 会将内存中缓存的写命令发送到 slave,slave 也会同步这些数据。

slave node 如果跟 master node 有网络故障,断开了连接,会自动重连,连接之后 master node 仅会复制给 slave 部分缺少的数据。

redis-master-slave-replication

过程原理

  1. 当从库和主库建立MS关系后,会向主数据库发送SYNC命令
  2. 主库接收到SYNC命令后会开始在后台保存快照(RDB持久化过程),并将期间接收到的写命令缓存起来
  3. 当快照完成后,主Redis会将快照文件和所有缓存的写命令发送给从Redis
  4. 从Redis接收到后,会载入快照文件并且执行收到的缓存的命令
  5. 之后,主Redis每当接收到写命令时就会将命令发送从Redis,从而保证数据的一致

缺点

所有的slave节点数据的复制和同步都由master节点来处理,会照成master节点压力太大,使用主从从结构来解决


二、高可用方案-哨兵模式


1、哨兵模式介绍

哨兵模式架构图如下(哨兵除了监控 Redis 服务器之外,哨兵之间也会互相监控):

img

在 Redis 主从模式下,master 节点负责写请求,然后异步同步给 slave 节点,从节点负责处理读请求。如果 master 宕机了,需要手动将从节点晋升为主节点,并且还要切换客户端的连接数据源。这就无法达到高可用,而通过哨兵模式就可以解决这一问题。

哨兵模式是 Redis 的高可用方式,哨兵节点是特殊的 redis 服务,不提供读写服务,主要用来监控 redis 实例节点。

哨兵架构下 client 端第一次从哨兵找出 redis 的主节点,后续就直接访问 redis 的主节点,不会每次都通过 sentinel 代理访问 redis 的主节点,当 redis 的主节点挂掉时,哨兵会第一时间感知到,并且在 slave 节点中重新选出来一个新的 master,然后将新的 master 信息通知给 client 端,从而实现高可用。

这里面 redis 的 client 端(就是我们的应用服务)一般都实现了订阅功能,订阅 sentinel 发布的节点变动消息。

哨兵的主要工作任务:

  • 监控:哨兵会不断地检查 Redis 的 Master 和 Slave 是否运作正常;
  • 提醒:当被监控的某个 Redis 节点出现问题时,哨兵可以通过 API 向管理员或者其他应用程序发送通知;
  • 自动故障迁移:当一个 Master 不能正常工作时,哨兵会进行自动故障迁移操作,将失效 Master 的其中一个 Slave 升级为新的 Master,并让其他 Slave 从新的 Master 同步数据,当客户端试图连接失效的 Master 时,Sentinel 集群也会向客户端返回新 Master 的地址。

2、哨兵模式的搭建

2-1、配置 sentinel.conf 文件

1)配置监听的 Master 节点

在 sentinel.conf 配置文件中配件需要监听的主从 Redis 的 master 节点:

sentinel monitor <master‐name> <ip> <redis‐port> <quorum>
  • master‐name:主节点 master 的名字;
  • quorum:哨兵集群中多少个 sentinel 认为 master 失效才判定为客观下线,一般配节点数 / 2 + 1,也就是说大于半数。

2)配置密码(如果主从 master 设置了密码):

sentinel auth-pass <master-name> <password>

由于 Master 节点挂了之后,哨兵会从 Slave 节点进行选举新的 Master。所以,如果 slave 也配置了密码的话,那么最好在其他的 Redis 节点都配置上 master auth xxx,保证挂了的 Redis 服务重启之后能正常加入主从中去。

3)配置心跳检测的主观下线时间:

sentinel down-after-milliseconds <master-name> <time>
  • time:主观下线阈值,单位为毫秒 ms。

4)配置 Redis 从服务器数量:

sentinel parallel-syncs mymaster 2

2-2、根据配置文件启动 Sentinel

./redis-server sentinel.conf --sentinel &

启动之后,使用命令 ./redis-cli 进入 Redis 客户端,然后输入 info 命令,可以查看到哨兵的状态信息如下:

master0:name=mymaster,status=ok,address=192.168.0.34:6379,slaves=2,sentinels=1

再使用同样的配置文件,启动另外两个哨兵,在查看信息之后会发现哨兵数量变成3个:

master0:name=mymaster,status=ok,address=192.168.0.34:6379,slaves=2,sentinels=3

2-3、Java客户端连接哨兵

在配置文件配置哨兵节点即可:

spring:
	redis:
		sentinel:
			master: mymaster # 哨兵配置中集群名字
			nodes: 哨兵ip1:哨兵端口1,哨兵ip2:哨兵端口2,哨兵ip3:哨兵端口3

3、哨兵模式的原理

哨兵是一个分布式系统,可以在一个架构中运行多个哨兵进程,这些进程使用流言协议gossip protocols)来传播 Master 是否下线的信息,并使用投票协议agreement protocols)来决定是否执行自动故障迁移,以及选择哪个 Slave 作为新的 Master。

3-1、心跳机制

1)Sentinel 与 Redis 节点:

Sentinel 是一个特殊的 Redis 节点。在哨兵模式创建时,需要通过配置指定 Sentinel 与 Redis Master Node 之间的关系,然后 Sentinel 会从主节点上获取所有从节点的信息,之后 Sentinel 会定时向主节点和从节点发送 info 命令获取其拓扑结构和状态信息。

2)Sentinel 与 Sentinel 之间:

基于 Redis 的订阅发布功能, 每个 Sentinel 节点会向主节点的 sentinel:hello 频道上发送该 Sentinel 节点对于主节点的判断以及当前 Sentinel 节点的信息 ,同时每个 Sentinel 节点也会订阅该频道, 来获取其他 Sentinel 节点的信息以及它们对主节点的判断。

通过以上两步所有的 Sentinel 节点以及它们与所有的 Redis 节点之间都已经彼此感知到,之后每个 Sentinel 节点会向主节点、从节点、以及其余 Sentinel 节点定时发送 ping 命令作为心跳检测, 来确认这些节点是否可达。

3-2、Master 节点下线判断

每个 sentinel 哨兵节点每隔1s 向所有的 master、slave 以及其他 sentinel 节点发送一个 ping 命令,作用是通过心跳检测,检测主从服务器的网络连接状态。

如果 master 节点回复 PING 命令的时间超过 down-after-milliseconds 设定的阈值(默认30s),则这个 master 会被 sentinel 标记为主观下线,修改其 flags 状态为SRI_S_DOWN

当 sentinel 哨兵节点将 master 标记为主观下线后,会向其余所有的 sentinel 发送 sentinel is-master-down-by-addr 消息,询问其他 sentinel 是否同意该 master 下线。

sentinel is-master-down-by-addr <ip> <port> <current_epoch> <runid>
  • ip:主观下线的服务ip
  • port:主观下线的服务端口
  • current_epoch:sentinel的纪元
  • runid:如果是 * 则表示检测服务下线状态,如果是 sentinel 的运行 id,则表示用来选举领头 sentinel。

每个 sentinel 收到命令之后,会根据发送过来的 ip 和 port 自行去检查判断 master 是否下线,得出结果后回复自己是否认为该 master 节点已经下线了。回复命令同样是 is-master-down-by-addr,参数需要附带判断的结果状态 down_state(1表示已下线,0表示未下线)。

sentinel 收到回复之后,判断同意 master 节点进入主观下线的 sentinel 数量是否大于等于quorum,如果是则 master 会被标记为客观下线,即认为该节点已经不可用。

3-3、选举领头 sentinel

在 master 节点被判定客观下线后,就需要选举一个领头 sentinel 来负责故障转移。具体的选举过程如下:

  1. 判断客观下线的 sentinel 节点向其他 sentinel 节点发送 SENTINEL is-master-down-by-addr ip port current_epoch runid 命令(注意:这时的 runid 是自己的 run id,每个 sentinel 节点都有一个自己运行时 id);
  2. 目标 sentinel 回复是否同意 master 下线并选举领头 sentinel,选择领头 sentinel 的过程符合先到先得的原则。举例:sentinel1 判断了客观下线,向 sentinel2 发送了第一步中的命令,sentinel2 回复了 sentinel1,说选你为领头,这时候 sentinel3 也向 sentinel2发送第一步的命令,sentinel2 会直接拒绝回复;
  3. 当sentinel发现选自己的节点个数超过 majority 的个数的时候,自己就是领头节点;如果没有一个sentinel达到了majority的数量,等一段时间,重新选举。

3-4、故障转移

有了领头 sentinel 之后,下面就是要做故障转移了,故障转移即:从 Slave 中选举新的 Master 然后下线旧的故障 Master。

在进行选择之前需要先剔除掉一些不满足条件的 slaver:

  • 剔除列表中已经下线的 Slave 节点;
  • 剔除有 5s 没有回复 sentinel 的 info 命令的 slave 节点;
  • 剔除与已经下线的 Master 服务连接断开时间超过 down-after-milliseconds * 10 + master 宕机时长的 slave 节点。

Master 节点选举过程:

  1. 选择优先级最高的节点,通过 sentinel 配置文件中的 replica-priority 配置项,这个参数越小,表示优先级越高;
  2. 如果第一步中的优先级相同,选择 offset 最大的,offset 表示主节点向从节点同步数据的偏移量,越大表示同步的数据越多;
  3. 如果第二步 offset 也相同,选择 run id 较小的。

新的 master 节点选择出来之后,还需要做一些事情:

  • 领头 sentinel 会对选出来的从节点执行 slaveof no one 命令让其成为主节点;
  • 领头 sentinel 向别的 slave 发送 slaveof 命令,告诉它们新的 master 节点信息,以后就需要向这个新的 master 复制数据;
  • 之前故障的 master 重新上线时,领头 sentinel 同样会给起发送 slaveof 命令,将其变成从节点;

哨兵的数量问题:

Redis哨兵数量对于Redis集群的高可用性至关重要。在现实中,我们一般会将Redis哨兵节点的数量设置为3个或5个,因为这些节点数量都可以提供足够的容错能力和可靠性。如果Redis哨兵节点数量过少,可能会导致Redis集群的高可用性出现问题。例如,当Redis集群中的主节点出现故障时,如果只有一个哨兵节点,则可能会出现 奇数问题,导致集群无法自动选举出新的主节点。

在确定Redis哨兵节点的数量时,需要考虑以下几个因素:

  • Redis集群的规模。如果Redis集群规模较大,那么哨兵节点的数量应该相应地增加。
  • 可用性要求。如果对Redis集群的高可用性要求较高,那么哨兵节点的数量应该相应地增加。
  • 性能要求。哨兵节点的数量越多,Redis集群的性能也会相应地下降,因此需要根据实际情况进行平衡。

在实际应用中,我们可以根据以上因素进行综合考虑,来确定Redis哨兵节点的数量(建议哨兵至少需要 3 个实例,来保证自己的健壮性)。当然,在此过程中,我们还需要注意哨兵节点的分布式部署,保证其可靠性和容错能力。

注意:哨兵 + redis 主从的部署架构,是不保证数据零丢失的,只能保证 redis 集群的高可用性。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐