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.4:只读
一般情况下,从节点对外应只提供读操作,可以通过以下配置启用节点的只读模式:
1 | slave-read-only = yes |
1.5:部署方案
redis主从可行的部署方案如下:
无论是哪种主从模式,都可以解决单点故障问题,但需要注意的是,一主多从虽然可以做读操作的负载均衡,但会加大主节点的负载(因为所有从节点都订阅了主节点的变更),为了解决这个问题,便有了树状部署方式,从节点继续向下拓展,这样可以有效降低主节点的负载(主节点只需要关心自己下一层的从节点即可)
我现在的公司通常为一主一从、集群部署,主从所在的物理机必定不是同一台,这样可以有效降低缓存雪崩的概率(两台物理机同时gg的概率很小),且没有开启持久化等影响主进程的操作,完全将redis当成缓存来用。
二、同步过程
- 保存主节点信息:当
slaveof
命令执行完毕后,复制流程还没有真正开始,而是将master的ip+port保存了下来。 - 主从建立socket连接:从节点有个维护复制相关逻辑的定时器,当其发现存在新的主节点后,会尝试与其建立连接,用来接收主节点发来的复制命令(建连失败时,定时任务会无限重试,除非建连成功或执行
slave no one
) - 发送
ping
请求:目的是检查socket是否可用、主节点是否受理(若不满足条件,从节点会断开socket,并在下次定时循环中重试) - 权限校验:在主节点设置了
requirepass
的情况下,则需要密码校验,从节点需要设置masterauth
来保证通过主节点的校验(若未通过校验,同3) - 同步数据集:一切就绪后,从节点会向主节点发送一个
psync
指令,主节点第一次会将自己全部的数据生成快照发送给从节点(rdb文件);2.8之前只支持sync
同步,sync无脑全量复制,比如从节点断连后重连,也会触发一次全量复制,从节点在全量复制时是无法对外提供服务的,为了解决这一痛点,redis在2.8支持了psync
,psync支持全量复制
和部分复制
,当第一次同步数据时才会全量复制,重连操作一般会利用主节点运行ID
、复制偏移量
和复制积压缓冲区
,并采用部分复制
的方式补发断连期间产生的增量数据(指令:psync [runid] [offset]
)。 - 增量复制:当5完成后,接下来主节点会持续的将写命令发送给从节点,来保证主从一致性
整体流程图(黄色部分为复制建设流程
,紫色为全量复制流程
,绿色为稳定期间的增量同步流程
,蓝色为从节点挂掉又恢复后的部分复制流程
):
三、常见问题
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节点,只是比较特殊,它们不存储数据且只接收部分命令),大致结构如下:
当某个sentinel发现自己所监控的某个节点不可达后,会对这个节点做下线标识,如果该节点是redis的master节点,它就会和其他sentinel协商,当大多数sentinel都认为这个主节点不可达时,就会选出一个sentinel代表来完成故障转移工作,同时会将这个变化实时同步给redis client,这个过程是全自动的,下面的流程图展示了这个过程(假设主节点挂掉):
4.2:安装&部署
假如我们现在要搭建图3里的Redis Sentinel架构,那要如何配置呢?
4.2.1:启动主节点
这是主节点主要的的配置信息(redis-6379.conf
):
1 | port 6379 # 端口号 |
启动+测试:
1 | redis-server redis-6379.conf |
4.2.2:启动从节点
两个从节点的配置除了端口号等细节外是一样的,我们这里拿其中一个举例:
1 | port 6380 |
启动+测试:
1 | redis-server redis-6380.conf |
然后确认一遍主从关系是否正确,这是主节点视角的验证:
1 | redis-cli -h 127.0.0.1 6379 info replication |
这是从节点视角的验证(以其中一个为例):
1 | redis-cli -h 127.0.0.1 6380 info replication |
到这里,主从架构就搭建完成了,接下来开始搭建sentinel环境。
4.2.3:部署sentinel
前面我们就了解到sentinel的本质也是redis节点,且不管几个sentinel,它们每个节点的部署方法都是一致的,所以我们只关注一个sentinel节点如何部署就好。
这是主要的配置信息(redis-sentinel-26379.conf):
1 | port 26379 |
启动:
1 | redis-sentinel redis-sentinel-26379.conf |
当三个sentinel节点都启动完毕后,输入以下指令检验其准确性:
1 | redis-cli -h 127.0.0.1 -p 26379 info Sentinel |
到这里整个sentinel+主从架构就搭建完成了,它们会按照之前说的那样工作;虽然redis可以单机多实例部署,但为了保证高可用,不建议将所有的节点都部署在同一台物理机上。
所有节点都启动后,sentinel配置文件发生了变化:
1 | port 26379 |
可以看到down-after-milliseconds
、parallel-syncs
、failover-timeout
消失了,取而代之的是sentinel发现的各种节点信息,也就是说每个sentinel都是有能力自动发现所有需要监控的节点的。
4.2.4:监控多个master
非常简单,只需要将原来的配置改成监听多个master的即可:
1 | port 26379 |
假如现在有一个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:故障转移
选出领导者,接下来就可以进行故障转移了: