当前位置: 首页 > 科技观察

实现故障恢复自动化:Redis哨兵技术详解

时间:2023-03-15 19:05:22 科技观察

我们在前面的《读完这篇文章,就基本搞定了Redis主从复制》分享中提到,Redis主从复制的功能包括数据热备份、负载均衡、故障恢复等;但是主从复制存在问题,故障恢复不能自动化。本文要介绍的sentinel是基于Redis主从复制的。其主要功能是解决主节点故障恢复的自动化问题,进一步提高系统的高可用性。注:本文内容基于Redis3.0版本。一、功能与架构1、功能在介绍Sentinel之前,先从宏观的角度回顾一下Redis实现高可用的相关技术。它们包括:持久性、复制、哨兵和集群。它们的主要功能和问题是:持久化:持久化是最简单的高可用方式(有时甚至不被归类为高可用方式),其主要作用是数据备份,就是将数据存储在硬盘上,保证数据不会丢失由于进程退出而丢失。复制:复制是Redis高可用的基础。哨兵和集群都是基于复制来实现高可用的。Replication主要实现数据的多机备份,以及读操作的负载均衡和简单的故障恢复。缺点是故障恢复不能自动化;写操作不能负载均衡;存储容量受限于单机。Sentinel:基于复制,Sentinel实现了故障自动恢复。缺点是写操作不能负载均衡;存储容量受限于单机。集群:Redis通过集群,解决了写操作无法负载均衡,存储容量受限于单机的问题,实现了比较完善的高可用解决方案。细节可以回顾一下:再来说说哨兵。RedisSentinel,即Redis哨兵,是在Redis2.8版本中引入的。Sentinel的核心功能是主节点的自动故障转移。以下是Redis官方文档中对sentinel功能的描述:监控(Monitoring):Sentinel会不断的检查主节点和从节点是否运行正常。自动故障转移(AutomaticFailover):当主节点无法正常工作时,Sentinel会启动自动故障转移操作,将故障主节点的其中一个从节点升级为新的主节点,并让其他从节点复制新的主节点代替。主节点。配置提供者(ConfigurationProvider):客户端初始化时,通过连接哨兵获取当前Redis服务的主节点地址。通知:哨兵可以将故障转移的结果发送给客户端。其中,监控和自动故障转移功能使Sentinel能够及时检测到主节点的故障并完成转移;而配置提供者和通知功能需要体现在与客户端的交互中。这里解释一下文章中“客户端”这个词的用法:在上一篇文章中,只要是通过API访问Redis服务端的,都会被称为客户端,包括Redis-cli、Java客户端Jedis、等,为了方便区分,本文中的客户端不包括Redis-cli,但比Redis-cli更复杂:Redis-cli使用Redis提供的底层接口,而客户端封装了这些接口和功能,为了充分利用Sentry的配置提供程序和通知功能。2.架构一个典型的Sentinel架构图如下:它由两部分组成:Sentinel节点:Sentinel系统由一个或多个Sentinel节点组成。哨兵节点是不存储数据的特殊Redis节点。数据节点:主节点和从节点都是数据节点。2.部署这部分会部署一个简单的哨兵系统,包括1个主节点,2个从节点和3个哨兵节点。为了方便,这些节点都部署在一台机器上(局域网IP:192.168.92.128),以端口号区分;并且节点的配置尽可能简单。1、部署主从节点哨兵系统中的主从节点与普通主从节点配置相同,无需额外配置。以下是主节点(port=6379)和两个从节点(port=6380/6381)的配置文件。配置比较简单,不再详述:#redis-6379.confport6379daemonizeyeslogfile"6379.log"dbfilename"dump-6379.rdb"#redis-6380.confport6380daemonizeyeslogfile"6380.log"dbfilename"dump-6380.rdb"slaveof192.168.92.1286379#redis-6381.confport6381daemonizeyeslogfile"6381.log"dbfilename"dump-6381.rdb92.slaveof168.92.1286379配置完成后,依次启动主节点和从节点:redis-serverredis-6379.confredis-serverredis-6380.confredis-serverredis-6381.conf节点启动后,连接master节点查看主从状态是否正常,如下图:2.部署哨兵节点哨兵节点本质上是特殊的Redis节点,三个哨兵节点的配置几乎相同,主要区别在于端口号(26379/26380/26381),下面以26379节点为例进行介绍节点的配置和启动方式;配置部分i越简单越好,更多配置后面会介绍:#sentinel-26379.confport26379daemonizeyeslogfile"26379.log"sentinelmonitormymaster192.168.92.12863792其中sentinelmonitormymaster192.168.92.12863792个配置意思:sentinel节点监听主节点192.1628.93。master节点的名字是mymaster,2的含义与master节点的故障判断有关:必须至少有两个sentinel节点同意,才能判断master节点的故障,进行failover。sentinel节点的启动方式有两种,两者的功能完全一样:redis-sentinelsentinel-26379.confredis-serversentinel-26379.conf--sentinel按上面配置启动后方法,整个哨兵系统启动。可以通过Redis-cli连接哨兵节点验证,如下图:可以看出26379哨兵节点已经在监听mymaster主节点(即192.168.92.128:6379),发现其2个从属节点和另外2个哨兵节点。这个时候再看哨兵节点的配置文件,就会发现有一些变化。以26379为例:其中dir只是显式声明了数据和日志所在的目录(只有sentinel上下文中的日志);known-slave和known-sentinel表示sentinel已经发现slave节点和其他sentinel;带epoch的参数与配置epoch有关(配置epoch是一个从0开始的计数器,每进行一次***sentinelelection就会+1;***sentinelelection是在操作failover阶段,原理后面会介绍)。3、failoversentinel、configurationprovider、notification四个功能的演示需要客户端配合。本文将在下一章详细介绍客户端访问哨兵系统的方法。本节将演示Sentinel在主节点出现故障时的监控和自动故障转移能力。Step1:首先使用kill命令杀掉master节点:Step2:在Sentinel节点上使用infoSentinel命令立即查看,会发现master节点并没有切换过来,因为Sentinel发现主节点发生故障并进行传输,需要一段时间。Step3:过一段时间后,再次在sentinel节点上执行infoSentinelcheck,发现master节点已经切换到6380节点。但同时可以发现哨兵节点认为新的主节点还有2个从节点。这是因为sentinel将6379节点设置为从节点,同时将6380切换为主节点;虽然6379从节点已经挂了,但是由于sentinel并没有客观的让从节点下线(原理部分会介绍其含义),所以认为从节点一直存在。当6379节点重启后,会自动成为6380节点的从节点。下面验证一下。Step4:重启6379节点,可以看到6379节点已经成为6380节点的从节点。Step5:在failover阶段,sentinel和master-slave节点的配置文件会被重写。对于主从节点,主要是slaveof配置的变化:新的主节点没有slaveof配置,其从节点为新主节点的slaveof。对于哨兵节点,除了主从节点信息发生变化外,epoch也会发生变化。下图中可以看到epoch相关的参数都是+1。4.总结哨兵系统的构建过程。有几点需要注意:哨兵系统中的主从节点与普通的主从节点没有区别。故障发现和转移由哨兵控制和完成。Sentinel节点本质上是Redis节点。对于每个哨兵节点,只需要配置监控主节点即可自动发现其他哨兵节点和从节点。在哨兵节点启动和故障转移阶段,每个节点的配置文件都会被重写(ConfigRewrite)。在本章的例子中,一个哨兵只监控一个主节点;实际上,一个sentinel可以通过配置多个sentinelmonitor来监控多个master节点。3、客户端访问sentinel系统上一节演示了sentinel的两个功能:监控和自动故障转移。本节结合客户端来演示哨兵的另外两个功能:配置提供者和通知。一、代码示例在介绍客户端原理之前,先以Java客户端Jedis为例,演示一下如何使用:下面的代码可以连接到我们刚刚搭建的Sentinel系统,并进行各种读写操作:publicstaticvoidtestSentinelthrowsException{StringmasterName="mymaster";Setsentinels=newHashSet<>;sentinels.add("192.168.92.128:26379");sentinels.add("192.168.92.128:26380");sentinels.add("192.168.92.128:26381");JedisSentinelPoolpool=newJedisSentinelPool(masterName,sentinels);//初始化过程做了很多工作Jedisjedis=pool.getResource;jedis.set("key1","value1");pool.close;}(注意:代码只演示了如何连接哨兵,异常处理,资源关闭等不予考虑)2.Client原理Jedisclient对sentinel提供了很好的支持。如上代码所示,我们只需要为Jedis提供一组sentinel节点和masterName即可构造一个JedisSentinelPool对象;然后就可以像普通的Redis连接池一样使用了:通过pool.getResource获取连接,执行特定的命令。在整个过程中,我们的代码可以在不显式指定主节点地址的情况下连接到主节点;代码中没有failover的体现,可以在sentinel完成failover后自动切换master节点。之所以能做到这一点,是因为在JedisSentinelPool的构造函数中,已经进行了相关的工作,主要包括以下两点:遍历sentinel节点,获取master节点的信息:遍历sentinel节点,通过获取其中一个哨兵节点+masterName主节点的信息;该功能是通过调用sentinel节点的sentinelget-master-addr-by-name命令实现的。命令示例如下:一旦获取到master节点的信息,就停止遍历(所以一般来说,遍历到第一个Sentinel节点,循环就停止了)。增加对哨兵的监控:这样当发生故障转移时,客户端可以收到哨兵的通知,完成主节点的切换。具体方法是:利用Redis提供的发布-订阅功能,为每个sentinel节点开启一个单独的线程,订阅sentinel节点的+switch-master通道,收到消息时重新初始化连接池。3.小结通过客户端原理的介绍,可以加深对哨兵功能的理解,如下:配置提供者:客户端可以通过哨兵节点+masterName获取主节点信息,以及哨兵的作用这里是配置提供者。需要注意的是,哨兵只是一个配置提供者,并不是代理。两者的区别在于:如果是配置提供者,客户端通过sentinel获取主节点信息后会直接与主节点建立连接,后续的请求(如set/get)会直接发送到主节点;如果是作为代理,客户端的每一个请求都会发送给哨兵,哨兵通过主节点处理请求。举个例子就很好理解,Sentinel的作用是配置provider,而不是proxy。在之前部署的sentinel系统中,修改sentinel节点的配置文件如下:sentinelmonitormymaster192.168.92.12863792tosentinelmonitormymaster127.0.0.163792然后,将上述客户端代码放到局域网中的另一台机器上如果你运行,会发现客户端无法连接到master节点;这是因为Sentinel是配置提供者,客户端可以通过它查询master节点的地址为127.0.0.1:6379,客户端会和127.0.0.1:6379建立Redis连接,自然连接不上。如果Sentinel是代理,则不会出现此问题。通知:故障转移完成后,哨兵节点会将新的主节点信息发送给客户端,以便客户端及时切换主节点。4.基本原理上面介绍了Sentinel部署和使用的基本方法。这部分介绍了Sentinel实现的基本原理。1.哨兵节点支持的命令作为运行在特殊模式下的Redis节点,哨兵节点支持的命令不同于普通Redis节点。在运维中,我们可以使用这些命令来查询或修改哨兵系统;但更重要的是,哨兵系统无法实现故障发现和故障转移等各种功能,而这些功能离不开哨兵节点之间的通信,而通信中很大一部分是通过哨兵节点支持的命令来完成的。下面介绍哨兵节点支持的主要命令:基本查询:通过这些命令可以查询哨兵系统的拓扑结构、节点信息、配置信息等。infosentinel:获取所有被监控主节点的基本信息。sentinelmasters:获取所有受监控主节点的详细信息。sentinelmastermymaster:获取被监控主节点mymaster的详细信息。sentinelslavesmymaster:获取被监控主节点mymaster的从节点详细信息。sentinelsentinelsmymaster:获取被监控主节点mymaster的sentinel节点的详细信息。sentinelget-master-addr-by-namemymaster:获取被监控主节点mymaster的地址信息,如前所述。sentinelis-master-down-by-addr:Sentinel节点可以通过这个命令询问master节点是否下线,从而客观判断是否下线。添加/删除master节点的监控:sentinelmonitormymaster2192.168.92.128163792:与部署sentinel节点时配置文件中的sentinelmonitor功能完全一致,不再详述。sentinelremovemymaster2:取消当前sentinel节点对master节点mymaster2的监听。强制故障转移:sentinelfailovermymaster:该命令可以强制对mymaster进行故障转移,即使当前主节点运行良好;比如当前master节点所在的机器即将报废,可以使用failover命令提前进行failover。2.基本原理关于Sentinel的原理,关键是理解以下几个概念:定时任务:每个Sentinel节点维护3个定时任务。定时任务的作用如下:通过向主从节点发送info命令获取活动的主从结构;通过发布订阅功能获取其他sentinel节点的信息;向其他节点发送ping命令进行心跳检测,判断是否下线。主观下线:在心跳检测的定时任务中,如果其他节点在一定时间内没有回复,哨兵节点会主观下线。顾名思义,主观下线就是哨兵节点“主观”判断下线;主观线下的对应是客观线下。客观下线:sentinel节点主观下线master节点后,会使用sentinelis-master-down-by-addr命令向其他sentinel节点询问master节点的状态;值,则主节点将客观下线。需要注意的是,客观下线是主节点特有的概念;如果从节点或哨兵节点发生故障,被哨兵主观下线,则不会有后续的客观下线和故障转移操作。top哨兵节点的选举:当master节点被客观判断为下线时,各个哨兵节点会协商选举出一个top哨兵节点,由top节点对其进行failover操作。所有监控主节点的哨兵都有可能被选为***,选举使用的算法是Raft算法;Raft算法的基本思想是先到先得:即在一轮选举中,哨兵A向B发送***的申请,如果B还没有同意其他哨兵,A将同意成为***。选举的具体过程在此不再详述。一般来说,挑选哨兵的过程是很快的。谁先完成线下目标,谁通常就会成为领导者。Failover:被选出的Sentinels开始failover操作,大致可以分为三个步骤:从slave节点中选择一个新的master节点:选择的原则是先过滤掉不健康的slave节点;然后选择优先级为***的slave节点(由slave-priority指定);如果无法区分优先级,则选择复制偏移量为***的从节点;如果仍然无法区分,则选择runid最小的slave节点。更新主从状态:使用slaveofnoone命令使选中的从节点成为主节点;并使用slaveof命令让其他节点成为它的从节点。将下线的主节点(即6379)设置为新主节点的从节点,当6379再次上线时,将成为新主节点的从节点。通过以上关键概念,你就可以基本了解Sentinel的工作原理了。为了更形象的说明,下图是***sentinel节点的日志,包括从节点启动到故障转移完成。五、配置及实用建议1、配置下面介绍与Sentinel相关的几个配置。配置一:sentinelmonitor{masterName}{masterIp}{masterPort}{quorum}sentinelmonitor是sentinel的核心配置,在上一篇部署sentinel节点的时候已经讲解过,其中:masterName指定了sentinel的名称master节点,masterIp和masterPort指定主节点地址,quorum是判断主节点客观下线的哨兵数量阈值:当判断主节点下线的哨兵数量达到quorum时,master节点将客观地离线。推荐值是sentinel数量的一半加1。配置二:sentineldown-after-milliseconds{masterName}{time}sentineldown-after-milliseconds与主观下线的判断有关:sentinel使用ping命令执行对其他节点进行心跳检测,如果其他节点超过down-after-milliseconds配置规定的时间内没有回复,Sentinel会主观下线。该配置对主节点、从节点和哨兵节点的主观下线判断有效。down-after-milliseconds默认值为30000,即30s;可以根据不同的网络环境和应用需求进行调整:该值越大,主观离线判断越宽松,优点是误判的可能性小,缺点是故障发现的时间而failover变长了,client的等待时间也会变长。例如,如果应用对可用性要求很高,可以适当降低该值,以在出现故障时尽快完成传输;如果网络环境比较差,可以适当提高阈值,避免频繁误判。配置3:sentinelparallel-syncs{masterName}{number}sentinelparallel-syncs与故障转移后从节点的复制有关:指定每次向新主节点发起复制操作的从节点数量。例如,假设主节点切换完成后,有3个从节点向新的主节点发起复制;如果parallel-syncs=1,从节点将开始一个一个地复制;如果parallel-syncs=3,那么3个从节点Nodes会一起开始复制。parallel-syncs的值越大,从节点完成复制的速度越快,但对主节点的网络负载和硬盘负载的压力也越大;应根据实际情况设置。比如主节点负载低,从节点对服务可用性要求高,可以适当调大parallel-syncs的值。parallel-syncs默认值为1。配置4:sentinelfailover-timeout{masterName}{time}sentinelfailover-timeout与failover超时判断有关,但不使用该参数判断整个failover的超时时间phase,但是几个子阶段超时,比如如果master节点提升slave节点的时间超过timeout,或者slave节点向新的master节点发起复制操作的时间(不包括timeforcopyingdata)超过超时,故障转移超时将失败。failover-timeout默认值为180000,即180s;如果超时,下次该值将翻倍。配置5:除了上述参数外,还有一些其他的参数,比如安全验证相关的参数,这里就不介绍了。2.实用建议Sentinel节点的个数要多于一个。一方面,它增加了哨兵节点的冗余,防止哨兵本身成为高可用的瓶颈;另一方面减少线下的误判。此外,这些不同的哨兵节点应该部署在不同的物理机器上。Sentinel节点的个数应该是奇数,这样Sentinel可以通过投票来“决定”:选举***的决策,客观下线的决策等,每个sentinel节点的配置要保持一致,包括硬件、参数等;此外,所有节点都应使用ntp或类似服务以确保时间准确一致。Sentinel的配置提供者和通知客户端功能需要客户端的支持,比如上面提到的Jedis;如果开发者使用的库没有提供相应的支持,可能需要开发者自己实现。当Sentry系统中的节点部署在Docker(或其他可能进行端口映射的软件)中时,需要特别注意端口映射可能导致Sentry系统无法正常工作,因为Sentinel的工作是基于与其他节点的通信,Docker的端口映射可以防止Sentinel连接到其他节点。例如,Sentinels根据声明的IP和端口相互发现。如果在Docker中部署了一个SentinelA并进行了端口映射,其他Sentinel无法使用A声明的端口连接到A。6.总结本文首先介绍哨兵的作用:监控、故障转移、配置提供者和通知;然后描述哨兵系统的部署方法,以及通过客户端访问哨兵系统的方法;然后简要说明sentinelRationale的基本实现;***给出了一些关于Sentinel实践的建议。Sentinel在主从复制的基础上,引入了主节点的自动故障转移,进一步提高了Redis的高可用;但是Sentinel的缺陷也很明显:Sentinel不能自动对从节点进行故障转移,在读写分离场景下,从节点故障会导致读服务不可用,需要我们做额外的监控和切换操作在从节点上。此外,Sentinel还没有解决写操作无法负载均衡、存储容量受单机限制的问题;这些问题的解决都需要使用集群,欢迎大家关注社区的后续内容。参考https://redis.io/topics/sentinelhttp://www.redis.cn/《Redis开发与运维》