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

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

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

一、配置&部署

1.1:建立主从关系

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

1
slaveof [master host] [master port]

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

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

1
--slaveof [master host] [master port]

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

1.2:废除主从关系

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

1
slaveof no one

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

1.3:切主

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

1
slaveof [new master host] [new master port]

执行后,发生以下变化:

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

1.4:只读

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

1
slave-read-only = yes

1.5:部署方案

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

图1

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

我现在的公司通常为一主一从、集群部署,主从所在的物理机必定不是同一台,这样可以有效降低缓存雪崩的概率(两台物理机同时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完成后,接下来主节点会持续的将写命令发送给从节点,来保证主从一致性

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

图2

三、常见问题

3.1:传输延迟

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

1
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节点,只是比较特殊,它们不存储数据且只接收部分命令),大致结构如下:

图3

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

图4

4.2:安装&部署

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

4.2.1:启动主节点

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

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

启动+测试:

1
2
3
4
redis-server redis-6379.conf

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

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

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

启动+测试:

1
2
3
4
5
6
7
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

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

1
2
3
4
5
6
7
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
...

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

1
2
3
4
5
6
7
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):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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
就视为故障转移失败

启动:

1
2
3
redis-sentinel redis-sentinel-26379.conf
#
redis-server redis-sentinel-26379.conf --sentinel

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

1
2
3
4
5
6
7
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配置文件发生了变化:

1
2
3
4
5
6
7
8
9
10
11
12
13
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的即可:

1
2
3
4
5
6
7
8
9
10
11
12
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主从服务,拓扑图就变成了下面这样:

图5

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节点信息,具体过程如下:

图6

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

4.5:原理

4.5.1:监控

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

图7

4.5.2:下线协商

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

图8

4.5.3:选举领导者

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

图9

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

4.5.4:故障转移

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

图10