当前位置: 首页 > 后端技术 > Java

Redis如何实现高可用?(主从、哨兵、集群)

时间:2023-04-01 20:24:47 Java

高可用有两层意思:一是尽可能避免数据丢失,二是尽可能保证服务可用。AOF和RDB数据持久化尽可能保证数据不丢失,这么多节点保证尽可能提供服务。一般在实际生产中,不会将服务部署为单节点,主要有以下三个原因。容易出现单点故障,导致服务不可用。单个节点处理所有请求,吞吐量有限。最好的办法是把数据库做多份,部署在不同的服务器上,即使其中一台宕机也能继续提供服务。Redis实现高可用有三种部署模式:主从模式、哨兵模式和集群模式。1、主从模式既然一个服务宕机会导致服务不可用,那么是否可以考虑多台服务器来解决?Redis提供了主从模型。通过主从复制,将数据冗余复制到其他Redis服务器。Master节点负责读写操作,Slave节点只负责读操作。1、主从复制原理。主从模式采用读写分离。所有的数据写入操作只会在主库上进行。Master库有最新数据后,会同步到Slave库。这样,主从库中的数据就保持一致了。这里要思考的是主从库的同步是如何完成的呢?主库数据是一次性传给从库,还是分批同步?正常运行时如何同步?如果主从库之间的网络断开了,重连后需要全部重新同步还是只部分同步?主从复制包括全量复制和增量复制。redis2.8版本后也支持部分同步。(1)全量同步一般当Slave开始第一次连接到Master时,就认为是第一次连接,使用全量拷贝。全量拷贝流程如下:完成以上步骤后,就完成了Salve节点初始化的所有操作。Slaveserver此时可以接收到用户的Read请求。redis2.8版本以后,已经用psync代替了sync,因为sync命令会消耗大量的系统资源,而且不支持部分同步。psync效率更高,支持部分同步。下面详细描述部分同步。(2)增量同步Redis增量复制是指Slave初始化后开始正常工作时,将Master服务器的写操作同步到Slave服务器的过程。增量复制的过程主要是Master服务器每执行一次写命令就向Slave服务器发送相同的写命令,Slave服务器接收并执行收到的写命令。(3)部分同步redis2.8版本之前不支持部分同步。当主从服务器之间的连接断开时,必须在主从服务器之间进行全量数据同步。此时从服务器会清除所有数据。再次加载Master的RDB文件。但是从redis2.8开始,即使中途切断了主从连接,也不一定需要进行全同步。可以支持部分同步,提高效率。它的工作原理大致是这样的:通过这张图,我们来理解为什么2.8部分可以实现部分同步。Slave节点根据当前状态向Master节点发送psync命令:如果Slave节点从未执行过replicaof,那么Slave节点发送psync?-1,向Master节点发送全量复制请求;如果Slave节点之前执行过replicaof,则发送psync,runID为上次copy保存的Master节点的runID,offset为Slave节点在上次copy结束时保存的copyoffset。Master节点根据收到的psync命令和当前服务器状态决定进行全量或部分复制:runID与Slave节点发送的runID相同,Slave节点发送的slave_repl_offset后的数据存在于repl_backlog_buffer中buffer,然后回复CONTINUE,表示将进行部分复制,Slave节点只是等待Master节点发送丢失的数据;runID与Slave节点发送的runID不同,或者Slave节点发送的slave_repl_offset之后的数据已经不在Master节点的repl_backlog_buffer缓冲区中(在队列中被挤出),然后回复Slave节点FULLRESYNC,表示需要进行全量复制,其中runID表示Master节点当前的runID,offset表示Master节点当前的offset,Slave节点保存这两个值。准备启用。如果Slave库与Master库断开时间过长,其在Master库repl_backlog_buffer的slave_repl_offset位置的数据已经被覆盖,此时Slave库和Master库之间会进行一次全量拷贝。2、主从模式的优缺点。优点是实现读写分离,提高服务器性能。Salve可以卸载Master的读操作压力。当然写服务还是要Master来完成;当Master节点服务宕机时,Slave可以成为Master节点继续提供服务;缺点是在主从模式下,一旦Master节点因无法提供服务而出现故障,需要手动将Slave节点提升为Master节点,同时通知应用更新Master节点地址.显然,大多数业务场景都无法接受这种故障处理方式;redis的主节点和从节点中的数据是一样的,降低了内存的可用性,存储容量有限。主从复制写还是在Master节点上,所以写的压力并没有减轻。所以主从复制其实不能满足我们的高可用需求。2.哨兵模式在主从模式下,一旦Master节点出现故障无法提供服务,需要手动将Slave节点提升为Master节点。显然,大多数业务场景都无法接受这种故障处理方式。Redis从2.8开始正式提供了RedisSentinel(哨兵)架构来解决这个问题。Sentinel模式,由一个或多个Sentinel实例组成的Sentinel系统,它可以监控所有的Master节点和Slave节点,当被监控的Master节点进入下线状态时,会自动下线Master服务器下的某个节点,Slave节点是升级到新的主节点。但是如果一个sentinel进程监控Redis节点,可能会出现问题(单点问题)。因此可以使用多个sentinel来监控Redis节点,每个sentinel之间会进行监控。Sentinel是一个特殊的redis实例,不存储数据,只监控集群。简单来说,Sentinel模式有3个作用:通过发送命令,等待Redis服务器返回来监控其运行状态,包括Master服务器和Slave服务器;当Sentinel检测到Master节点宕机时,会自动将Slaver节点切换为Master节点,然后通过发布-订阅模式通知其他Slave节点,修改配置文件,让它们切换主机;如果只有一个sentinel来监控Redis服务器的进程,也会出现问题。为此,我们可以使用多个哨兵进行监控。它们还相互监视以实现高可用性。一、Sentinel的主要任务Sentinel主要有3个定时监控任务来完成对各个节点的发现和监控。任务一:每个Sentinel节点每隔10秒向Master节点和Slave节点发送info命令,获取拓扑结构图。配置Sentinel时,只需要配置Master节点的监控即可。通过向Master节点发送信息,可以得到Slave节点的信息。信息,当有新的Slave节点加入时,可以立即感知任务2,每个sentinel节点都会将sentinel节点的判断发送给Master节点,将当前sentinel节点的信息发送给redis数据节点的指定通道每隔2秒,每个sentinel节点也会订阅这个通道,了解其他sentinel节点的信息,判断Master节点,实际上是通过消息的发布和订阅完成的;任务3,每个sentinel每隔1秒向Master发送一条消息,Node、Slave节点和其他sentinel节点发送ping命令做心跳检测,这也是sentinel判断节点是否正常的重要依据。主观下线前文提到,sentinel节点每1秒向Master节点、Slave节点和其他sentinel节点发送ping进行心跳检测。当心跳检测时间超过down-after-milliseconds时,sentinel节点认为节点错误或宕机。离线,这叫主观离线;当然这并不是说master真的不能用了(网络波动可能会导致sentinel和服务的连接不正常),所以主观下线是不可靠的,可能会出现误判。Sentinel客观下线当主观下线的节点是Mater节点时,sentinel节点会通过命令sentinelis-masterdown-by-addr寻求Master节点上其他sentinel节点的判断。当超过法定人数(quorum)时,此时Sentinel节点认为Master节点确实有问题,因此客观下线。大多数Sentinel节点都同意下线操作,也就是说客观上已经下线了。3、自动故障转移机制如果Sentinel客观下线了,接下来是否需要选举新的Master?这项工作只需要一个哨兵完成,所以首先要做的就是选举一个哨兵领袖。1)leader选举原因:只有一个sentinel节点完成failover,所以需要选举。选举通过sentinelis-master-down-by-addr命令,希望成为leader:每个主观下线的Sentinel节点向其他Sentinel节点发送命令,要求将其设为leader。如果接收命令的Sentinel节点不同意通过其他Sentinel节点发送的命令会同意请求,否则会被拒绝。如果Sentinel节点发现自己的得票超过了Sentinel集合的一半,超过了quorum,它就会成为leader。如果在这个过程中有多个Sentinel节点成为leader,那么它会等待一段时间重新选举2)从slave节点中选出一个新的Master节点。master服务的所有slave服务信息都保存在sentinelstate数据结构中。leadersentinel根据以下规则从slave服务列表中选择一个新的master服务过滤掉主观下线的节点,选择slave-priority最高的节点。如果没有返回,继续选择replicationoffset最大的节点,因为replicationoffset越大,数据复制越完整。如果不是,则继续选择run_id最小的节点3)更新主从状态使用slaveofnoone命令,使选中的Slave节点成为Master节点;并使用slaveof命令让其他节点成为它的Slave节点。将下线的Master节点设置为新Master节点的Slave节点。恢复正常后,复制新的Master节点,成为新Master节点的Slave节点。4、脑裂导致数据丢失1)什么是脑裂脑裂是指某个master所在的机器突然脱离了正常的网络,无法连接到其他slave机器,但是master还在运行。这个时候sentinel可能会认为master挂了,然后开始选举把其他的slave切换到master上。这时候集群中就会出现两个Master,也就是所谓的脑裂。这时候虽然有一个slave切换成了master,但是client可能还没有切换到新的master就继续往旧的master写数据。因此,当旧的master再次恢复时,它会作为slave附着在新的master上,自己的数据会被清空,重新从新的master上复制数据。新的master没有后面client写入的数据,所以这部分数据就丢失了。2)为了解决脑裂问题,Redis提供了两个配置项来限制Master库的请求处理,分别是min-slaves-to-write和min-slaves-max-lag。(2.8后改为min-replicas-to-write和min-replicas-max-lag)min-slaves-to-write1min-slaves-max-lag10以上两种配置:至少需要一个slave,数据复制和同步的延迟不能超过10秒。如果slave超过1个,数据复制和同步的延时超过10秒,那么此时master将收不到任何请求。这样的配置,即使你的Master库是假故障,在假故障期间也无法响应sentinel心跳,也无法与Slave库同步,自然也就无法与Slave库进行ACK确认。原Master库将被限制接收客户端请求,客户端将无法在原Master库写入新数据。当然,这个配置根本不允许数据丢失,而是让数据尽可能少的丢失。5、哨兵模式的优缺点哨兵模式建立在主从模式的基础上,哨兵模式具有主从模式的所有优点。主从可自动切换,系统更健壮,可用性更高。缺点它有主从模式的缺点,每台机器上的数据都是一样的,内存的可用性低。需要维护一套额外的sentinelmode,实现起来会更复杂,增加维护成本。Redis很难支持在线扩容,当集群容量达到上限时,在线扩容会变得非常复杂。3、集群模式先说一个误区:Redis的集群模式本身并没有使用一致性哈希算法,而是使用槽。因为查了很多资料都是这么说的。至于为什么要用slots,我个人的理解是slots的数量是固定的,更方便数据迁移。哨兵模式在主从模式的基础上,实现了读写分离。还可以自动切换,系统可用性更高。但是每个节点存储的数据都是一样的,浪费内存。因此,Cluster集群在Redis3.0之后应运而生,实现了Redis的分布式存储。将数据分片,即在每个Redis节点上存储不同的内容,解决在线扩容的问题。一个RedisCluster由多个Redis节点组成,节点组中有两类节点,分别对应主节点和从节点。两者的准实时数据一致性由异步主从复制机制保证。一个节点组只有一个主节点,同时可以有0个或多个从节点。在这个节点组中,只有主节点为用户提供部分服务,读服务可以由主??节点或从节点提供。如上图所示,master对应的有3个master节点和3个slave节点。一般一组集群至少需要6个节点才能保证完全的高可用。其中三个master会分配不同的slot(代表数据分片区间)。当master出现故障时,slave会自动选举成为master来代替master节点继续提供服务。一、集群的一些特点1)redis集群模式是通过使用一个非中心节点来实现的,每个Master节点都会与其他Master节点保持连接。节点之间通过gossip协议交换信息,每个Master节点有一个或多个Slave节点;2)客户端连接集群时,直接连接redis集群的各个Master节点,根据hash算法对key取模。存储在不同的哈希槽中;3)利用集群中的数据分片,将redis集群划分为16384个哈希槽。如下图所示,这些哈希槽存储在三个Master节点中:Master1负责哈希槽0~5460,Master2负责哈希槽5461~10922,Master3负责哈希槽10922~16383。4)每个节点都会保存一张数据分布表,该节点会将自己的时隙信息发送给其他节点,数据分布表会在节点间不断传递;5)客户端连接集群时,通过集群中某个节点的地址进行连接。当client试图对这个节点执行一个命令,比如获取一个key值,如果key所在的slot恰好在这个节点上,则命令可以直接执行成功。如果slot不在节点中,节点会返回一个MOVED错误,同时告诉client这个slot对应的节点,client就可以去该节点执行命令了。2、master节点故障处理方法redis集群中的master节点故障处理方法类似于哨兵模式。当一个节点不能在约定的时间内成功完成与集群中另一个节点的ping报文通信时,该节点将被标记为主观down。在线状态,同时向整个集群广播这个信息。如果一个节点收到断开连接的节点数达到集群的大多数,则将节点标记为客观离线状态,并将离线节点的失败消息广播到集群。然后立即对故障节点进行主从切换。原Master节点恢复后,自动成为新Master节点的Slave节点。如果Master节点没有Slave节点,那么当它出现故障时,集群就会处于不可用状态。3.扩容问题我们如何动态启动集群中的某个节点。当一个节点加入集群后,hashslot是如何分配的?当一个新节点加入集群时,它会与集群中的某个节点握手,该节点会将集群中其他节点的信息通过gossip协议发送给新节点,新节点加入与这些节点完成握手后的集群。然后集群中的节点会拿出一部分哈希槽分配给新的节点,如下图:Master1负责1365-5460Master2负责6827-10922Master3负责12288-16383Master4负责0-1364,5461-6826,10923-12287当集群需要删除时创建节点,只需要将节点中的所有哈希槽移动到其他节点,然后去掉空白(不包含任何哈希槽)节点。4、关于gossip协议,gossip协议需要单独说明。在整个redis集群架构中,如果出现以下情况:新增节点、slot迁移节点、宕机、slave选举成为master。我们希望这些变化能够让整个集群中的每一个节点都能够尽快被发现,传播到整个集群,并且集群中的所有节点都达成共识,那么每个节点都需要相互连接并携带相关的信息传输状态数据。按照正常的逻辑,广播是用来向集群中的所有节点发送消息的,有点是集群中的数据同步比较快,但是每条消息都需要发送到所有节点,太消耗CPU和带宽了,所以这里使用了八卦协议。Gossip协议又称为EpidemicProtocol(流行病协议),有很多别名如:“谣言算法”、“流行病传播算法”等。它的特点是在一个节点数量有限的网络中,每个节点会“随机”(不是真正随机,而是根据规则选择通信节点)与一些节点进行通信,经过一些乱七八糟的通信,每个节点的状态都会达到一个一定时间内的共识,如下图所示。假设我们预先设定了如下规则:1.Gossip周期性地广播消息,周期限制为1秒。2.感染节点随机选择k个相邻节点(fan-out)广播消息。这里,扇出设置为3,每次最多传播到3个节点。3.每次传播消息时,选择一个尚未发送过的节点进行传播。4.收到消息的节点不再向发送节点传播,比如A->B,那么B传播时,就不会再向A发送了。这里一共有16个节点,节点1是初始感染节点。通过Gossip过程,最终所有节点都被感染:gossip协议包含多种消息,包括ping、pong、meet、fail等。Ping:每个节点都会频繁的向其他节点发送ping,包括自己的状态和自己维护的集群元数据,通过ping交换元数据;pong:返回ping和meet,包括自身的状态等信息,也可用于信息的广播和更新;fail:一个节点判断另一个节点发生故障后,向其他节点发送fail,通知其他节点指定节点宕机。meet:一个节点向新加入的节点发送一个meet,让新节点加入集群,然后新节点开始与其他节点通信,而无需发送组成网络所需的所有CLUSTERMEET命令。发送CLUSTERMEET消息以便每个节点都可以到达每个其他节点只需要通过已知的节点链。由于八卦消息是在心跳包中交换的,因此将创建节点之间的丢失链接。5、gossip的优缺点:gossip协议的优点是元数据的更新比较分散,不会集中在一个地方,更新请求会一个接一个地来,更新所有节点会有一定的延迟,这降低了压力;集中化、可扩展性、容错性、一致性收敛性和简单性。由于不能保证在某一时刻所有节点都会收到消息,但理论上所有节点最终都会收到消息,因此它是一个最终一致性协议。缺点:元数据更新延迟可能导致集群的某些操作滞后。消息延迟,消息冗余。参考文献[1]Redis高可用:主从数据同步原理:https://zhuanlan.zhihu.com/p/...[2]如何实现Redis高可用:https://zhuanlan.zhihu.com/p/...声明:公众号如果你想转载本文,发表文章的负责人必须告诉你,它是要的:后端虚拟世界。同时也可以向我索要markdown文稿和原图。其他情况严禁转载!关注关注号:BackendMetaverse。持续输出优质文章。