4

Redis知识整合(三):高可用-主从

 2 years ago
source link: https://exceting.github.io/2021/05/24/Redis%E7%9F%A5%E8%AF%86%E6%95%B4%E5%90%88%EF%BC%88%E4%B8%89%EF%BC%89%E9%AB%98%E5%8F%AF%E7%94%A8-%E4%B8%BB%E4%BB%8E/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Redis知识整合(三):高可用-主从

要想提高一个有状态服务的可用性,最简单直接的办法就是扩展从节点,这样主节点挂了,从节点就可以上位并代替原先的节点继续对外提供服务,同时也可用从节点做负载均衡,作为目前最好用的分布式缓存之一的redis自然也支持主从配置,这篇文档的主要内容就是围绕这个话题展开的。

一、配置&部署

1.1:建立主从关系

如何让一个redis节点变成另一个节点的从节点呢?很简单,首先在从节点的配置文件中加上以下配置:

slaveof [master host] [master port]

↑这同样也是个命令,可以直接在从节点上执行

也可以在启动redis-server的命令后面加上:

--slaveof [master host] [master port]

注:slaveof是一个异步命令,开始只保存主节点信息,后续复制操作都是在从节点内异步执行的,info replication可以帮助我们查看当前的复制情况(主从节点都可以执行这个指令,只是输出信息不一样而已)。

1.2:废除主从关系

在从节点执行以下命令可废除与主节点的关联,并晋升为主节点:

slaveof no one

废除关系后,原从节点内的数据还在,只是失去了同步原主节点数据的能力。

1.3:切主

非常简单,只需要在从节点再次执行以下命令即可:

slaveof [new master host] [new master port]

执行后,发生以下变化:

  1. 断开与旧主节点的复制关系
  2. 建立与新主节点的复制关系
  3. 删除当前自身所有的数据
  4. 复制新主节点的数据

1.4:只读

一般情况下,从节点对外应只提供读操作,可以通过以下配置启用节点的只读模式:

slave-read-only = yes

1.5:部署方案

redis主从可行的部署方案如下:

无论是哪种主从模式,都可以解决单点故障问题,但需要注意的是,一主多从虽然可以做读操作的负载均衡,但会加大主节点的负载(因为所有从节点都订阅了主节点的变更),为了解决这个问题,便有了树状部署方式,从节点继续向下拓展,这样可以有效降低主节点的负载(主节点只需要关心自己下一层的从节点即可)

我现在的公司通常为一主一从、集群部署,主从所在的物理机必定不是同一台,这样可以有效降低缓存雪崩的概率(两台物理机同时gg的概率很小),且没有开启持久化等影响主进程的操作,完全将redis当成缓存来用。

二、同步过程

  1. 保存主节点信息:当slaveof命令执行完毕后,复制流程还没有真正开始,而是将master的ip+port保存了下来。
  2. 主从建立socket连接:从节点有个维护复制相关逻辑的定时器,当其发现存在新的主节点后,会尝试与其建立连接,用来接收主节点发来的复制命令(建连失败时,定时任务会无限重试,除非建连成功或执行slave no one
  3. 发送ping请求:目的是检查socket是否可用、主节点是否受理(若不满足条件,从节点会断开socket,并在下次定时循环中重试)
  4. 权限校验:在主节点设置了requirepass的情况下,则需要密码校验,从节点需要设置masterauth来保证通过主节点的校验(若未通过校验,同3)
  5. 同步数据集:一切就绪后,从节点会向主节点发送一个psync指令,主节点第一次会将自己全部的数据生成快照发送给从节点(rdb文件);2.8之前只支持sync同步,sync无脑全量复制,比如从节点断连后重连,也会触发一次全量复制,从节点在全量复制时是无法对外提供服务的,为了解决这一痛点,redis在2.8支持了psync,psync支持全量复制部分复制,当第一次同步数据时才会全量复制,重连操作一般会利用主节点运行ID复制偏移量复制积压缓冲区,并采用部分复制的方式补发断连期间产生的增量数据(指令:psync [runid] [offset])。
  6. 增量复制:当5完成后,接下来主节点会持续的将写命令发送给从节点,来保证主从一致性

整体流程图(黄色部分为复制建设流程,紫色为全量复制流程,绿色为稳定期间的增量同步流程,蓝色为从节点挂掉又恢复后的部分复制流程):

三、常见问题

3.1:传输延迟

主节点一般通过网络传输同步数据,存在延迟的风险,redis通过以下配置项控制是否关闭TCP_NODELAY:

repl-disable-tcp-nodelay = yes ### 默认no
  • 开启:主节点为了节省带宽会合并较小的TCP数据包,默认发送时间取决于Linux内核,默认40ms,这种方式虽然节省了带宽但也加大了主从延迟,适用于主从网络环境较紧张的场景,比如主从跨机房部署。
  • 关闭:主节点产生的命令数据无论多大都会及时的发送给从节点,这样主从延迟会变小,但加大了网络带宽,适用于主从网络环境较好的场景,比如主从同机房部署。

3.2:主从配置不一致

主从节点有关内存阈值的配置一定要保持一致

3.3:避免复制风暴

如果一个主节点挂载了很多从节点,当这个主节点重启时,它的所有从节点势必会同时发起全量复制流程,虽然可以共享rdb文件,但这对主节点的带宽消耗是灾难性的,解决方案就是改变部署结构,参考图1中的树状结构部署,以减少直接挂载到主节点的从节点数量。

3.4:各缓冲区/超时时间不够用

可以估算出大致的阈值,然后设置一个大于该估算值的结果,比如估算rdb同步超时时间,假设网卡带宽峰值为100M/s,那么rdb传输耗时就是:repl_timeout = rdb大小 / 100MB;

再比如估算复制积压缓冲区应设大小,假如网络中断时长为net_break_time(秒),然后根据高峰期每秒的master_repl_offset推算出高峰期每秒的大概写入量write_size_per_sec,然后保证:repl_backlog_size > net_break_time * write_size_per_sec即可。

3.5:最终一致

诚如你所见,redis主从复制是最终一致的,既然是最终一致,那就有可能在极端情况下造成少量数据丢失,应注意这个问题,至少你不应该完全信任redis,需要做好防范措施,比如定期回源刷新数据,保证少量问题数据可以自动得到修复。

四、哨兵(Sentinel)

4.1:基本工作流程

前面的内容主要介绍了redis的主从模式,主从模式最主要的作用是提高redis的可用性,假如主节点挂了,从节点可以晋升为主节点继续对外提供服务,这个自动调整主从的过程可以通过Sentinel机制来实现,如果没有Sentinel,我们就得手动搞定这一切了,想想就可怕(redis在2.8版本才支持了Sentinel)。

Redis Sentinel高可用架构包含了多个sentinel节点和多个redis节点(主+从),每个sentinel节点都会对这些redis节点和其他sentinel节点进行监控(事实上sentinel节点也是redis节点,只是比较特殊,它们不存储数据且只接收部分命令),大致结构如下:

当某个sentinel发现自己所监控的某个节点不可达后,会对这个节点做下线标识,如果该节点是redis的master节点,它就会和其他sentinel协商,当大多数sentinel都认为这个主节点不可达时,就会选出一个sentinel代表来完成故障转移工作,同时会将这个变化实时同步给redis client,这个过程是全自动的,下面的流程图展示了这个过程(假设主节点挂掉):

4.2:安装&部署

假如我们现在要搭建图3里的Redis Sentinel架构,那要如何配置呢?

4.2.1:启动主节点

这是主节点主要的的配置信息(redis-6379.conf):

port 6379 # 端口号
daemonize yes # 以守护线程的方式在后台运行
logfile "log-6379.log" # redis日志输出文件
dbfilename "dump-6379.rdb" # rdb输出文件
dir "/opt/soft/redis/data/" # rdb输出文件所在的路径

启动+测试:

redis-server redis-6379.conf

redis-cli -h 127.0.0.1 -p 6379 ping
PONG
4.2.2:启动从节点

两个从节点的配置除了端口号等细节外是一样的,我们这里拿其中一个举例:

port 6380
daemonize yes
logfile "log-6380.log"
dbfilename "dump-6380.rdb"
dir "/opt/soft/redis/data/"
slaveof 127.0.0.1 6379 # 跟上面的主节点建立主从关系

启动+测试:

redis-server redis-6380.conf
redis-server redis-6381.conf

redis-cli -h 127.0.0.1 -p 6380 ping
PONG
redis-cli -h 127.0.0.1 -p 6381 ping
PONG

然后确认一遍主从关系是否正确,这是主节点视角的验证:

redis-cli -h 127.0.0.1 6379 info replication
# Replication
role:master
connected_slaves:2
slave0:ip=127.0.0.1,port=6380,state=online,offset=281,lag=1
slave1:ip=127.0.0.1,port=6381,state=online,offset=281,lag=0
...

这是从节点视角的验证(以其中一个为例):

redis-cli -h 127.0.0.1 6380 info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:up
...

到这里,主从架构就搭建完成了,接下来开始搭建sentinel环境。

4.2.3:部署sentinel

前面我们就了解到sentinel的本质也是redis节点,且不管几个sentinel,它们每个节点的部署方法都是一致的,所以我们只关注一个sentinel节点如何部署就好。

这是主要的配置信息(redis-sentinel-26379.conf):

port 26379
daemonize yes
logfile "log-26379.log"
sentinel monitor mymaster 127.0.0.1 6379 2 # 监听指定的主节点,将其命名为mymaster,且判断主节点失败至少需要2个sentinel节点同意
sentinel down-after-milliseconds mymaster 30000 # 定时检查发送的检查指令响应超出30s视为失败
sentinel parallel-syncs mymaster 1 # 含义参考下面的说明
sentinel failover-timeout mymaster 180000 # 含义参考下面的说明
说明:
sentinel monitor [master_name] [master_ip] [master_port] [quorum]
sentinel会定期监控主节点,上面的配置可以指定要监控的主节点,quorum比较特殊,它可以
代表判定主节点最终不可达所需要的票数,也可以代表至少需要max(quorum,num(sentinel)/2 + 1)
个sentinel参与才能选出sentinel的领导者来处理故障转移任务;
quorum的取值建议设为num(sentinel)/2+1
============================================================================
sentinel down-after-milliseconds [master_name] [time]
sentinel节点会定期发送ping命令来确定redis节点和sentinel节点是否可达,若ping所用
时间超出了配置的times(毫秒),则判为不可达
============================================================================
sentinel parallel-syncs [master_name] [nums]
当主节点故障时,sentinel选出新的主节点,此时从节点会向新主节点发起复制,nums用来控制
同时发起复制的从节点数量,可以通过调小该值达到降低主节点的网络/IO开销的目的
============================================================================
sentinel failover-timeout [master_name] [time]
故障转移超时时间,作用于故障转移的各个阶段,比如重新选主、命令从节点向主节点发起复制(不
含复制时间)、等待原主节点恢复后命令其发起复制,在这几个步骤里任意步骤超过failover-timout
就视为故障转移失败
redis-sentinel redis-sentinel-26379.conf
# 或
redis-server redis-sentinel-26379.conf --sentinel

当三个sentinel节点都启动完毕后,输入以下指令检验其准确性:

redis-cli -h 127.0.0.1 -p 26379 info Sentinel
# Sentinel
sentinel_masters 1
sentinel_tilt:0
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
master0:name=mymaster,status=ok,address=127.0.0.1:6379,slaves=2,sentinels=3

到这里整个sentinel+主从架构就搭建完成了,它们会按照之前说的那样工作;虽然redis可以单机多实例部署,但为了保证高可用,不建议将所有的节点都部署在同一台物理机上。

所有节点都启动后,sentinel配置文件发生了变化:

port 26379
daemonize yes
logfile "log-26379.log"
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel config-epoch mymaster 0
sentinel leader-epoch mymaster 0
# 发现2个slave
sentinel-know-slave mymaster 127.0.0.1 6380
sentinel-know-slave mymaster 127.0.0.1 6381
# 发现另外2个sentinel节点
sentinel known-sentinel mymaster 127.0.0.1 26380 xxxxxxxx(runid)
sentinel known-sentinel mymaster 127.0.0.1 26381 yyyyyyyy(runid)
sentinel current-epoch 0

可以看到down-after-millisecondsparallel-syncsfailover-timeout消失了,取而代之的是sentinel发现的各种节点信息,也就是说每个sentinel都是有能力自动发现所有需要监控的节点的。

4.2.4:监控多个master

非常简单,只需要将原来的配置改成监听多个master的即可:

port 26379
daemonize yes
logfile "log-26379.log"
sentinel monitor mymaster-1 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster-1 30000
sentinel parallel-syncs mymaster-1 1
sentinel failover-timeout mymaster-1 180000

sentinel monitor mymaster-2 127.0.0.1 6389 2
sentinel down-after-milliseconds mymaster-2 30000
sentinel parallel-syncs mymaster-2 1
sentinel failover-timeout mymaster-2 180000

假如现在有一个sentinel集群,同时服务了两套redis主从服务,拓扑图就变成了下面这样:

4.2.5:部署技巧
  • sentinel节点、主从节点应尽可能不部署到同一台物理机上
  • 至少部署3个且奇数个sentinel节点,这是为了提高故障判定的准确率(多sentinel节点协商可以降低误判),奇数是为了在满族要求的机器数量基础上节约一台机器。
  • 只配一套sentinel监听所有节点,还是专门以主节点为维度给每个主节点单独配置一套?只配一套可以降低维护成本,但是这样容错率很低,比如sentinel集群出问题会影响所有的redis节点,但反过来给每个主节点单独配置一套又拉高了维护成本和资源浪费,那该怎么取舍呢?这个可以分情况:如果是同一个业务下的主节点可以共用一套sentinel,反之多套。

4.3:哨兵专属指令集

指令 解释

sentinel masters [master_name] 列出所有被监控的主节点状态以及相关的统计信息,可指定主节点

sentinel slaves 列出所有被监控的从节点状态以及相关的统计信息

sentinel sentinels [master_name] 列出正在监听指定主节点的sentinel节点信息(但不包含当前sentinel节点)

sentinel get-master-addr-by-name [master_name] 输出指定主节点的ip+port

sentinel reset [pattern] 当前sentinel对符合pattern的主节点进行配置重置,清除主节点的相关状态,重新发现该主节点和其他sentinel节点

sentinel failover [master_name] 对指定主节点(在不和其他sentinel协商的情况下)进行强制故障转移,转移完成后,其他sentinel按照该结果更新自身配置

sentinel ckquorum [master_name] 检测当前sentinel节点数是否达到所配的quorum,若未达到,则无法进行故障转移工作

sentinel flushconfig 将sentinel节点的配置刷到磁盘上

sentinel remove [master_name] 取消当前sentinel节点对指定主节点的监控

sentinel monitor [master_name] [ip] [port] [quorum] 增加要监控的主节点,详细请参考上面配置文件里对该指令的介绍

sentinel set [master_name] 可以利用该指令动态的修改一些配置文件内的属性

sentinel is-master-down-by-addr 当某个sentinel节点发现主节点坏掉时,为了防止误判,会向其他sentinel节点发送此命令,来获取其他节点对于主节点的判定结果,超出quorum个sentinel判定主节点失效则视为真正失效,此时会发起故障转移,除此之外,它还可以让当前sentinel申请成为故障转移的领导者

4.4:客户端

对于客户端来讲,sentinel最重要的一个功能就是将变更后的主节点通知到客户端,如果客户端仍然按照之前的方式进行直连master肯定是不行的,客户端需要跟sentinel进行交互,获取可用的master节点信息,具体过程如下:

java里面利用Jedis的JedisSentinelPool和redisson的SentinelServersConfig就可以完成这个过程,具体使用不再赘述。

4.5:原理

4.5.1:监控

sentinel监控其他节点在前面的流程图中就一条线带过,但其实内部逻辑涵盖了三个定时任务,分别是:

4.5.2:下线协商

现在假如master挂掉,此时sentinel会如何处理呢?master挂掉,意味着任意sentinel都无法ping通它,此时sentinel节点会向其他sentinel节点发送sentinel is-master-down-by-addr指令,来获取其他节点对于master节点的判定结果,当超过quorum个sentinel节点都认为master挂掉时,才会真的进行故障转移,这是为了防止单点误判,过程如下:

4.5.3:选举领导者

当认定一个主节点挂掉后(客观下线),并不会立即进行故障转移,而是在sentinel节点间选举出来一个领导者,然后由领导者进行故障转移,这个选举过程采用raft算法实现,具体过程如下:

需要注意,图中仅列出了sentinel-2发起申请领导者角色,但实际上任意一个sentinel节点都会请求其他sentinel节点让自己变成领导者,每个sentinel有且仅有一票,最后谁得票多就选谁,如果一轮没选出领导者,那就继续进行下一轮选举,逻辑一致。

4.5.4:故障转移

选出领导者,接下来就可以进行故障转移了:


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK