Redis高可用
Redis高可用
Redis高可用概述
Redis实现高可用主要有三种模式:
- 主从复制
- Redis集群模式
- Redis哨兵模式
注:主从复制是数据同步方式,解决了单点故障的问题,但不能保证高可用(是高可用的基础)。主要用来实现 redis 数据的可靠性,防止主 redis 所在磁盘损坏,造成数据永久丢失。
Redis高可用,可以提供以下几个优势:
1)避免单点故障:通过配置和设置多个Redis节点,如果其中一个节点发生故障,其他节点可以接替工作,避免了单点故障对整个系统的影响。
2)数据冗余和复制:通过数据的复制和持久化备份,Redis能够在主节点出现故障时,自动切换到备用节点,并恢复数据,确保数据的持久性和可用性。
3)故障自动检测和故障转移:Redis的高可用方案通常具备故障检测和自动故障转移的功能,能够监控节点的健康状态,并在节点故障时自动将从节点升级为主节点。
因此总结来说,Redis高可用主要可以在生产环境帮助我们完成以下两个工作:
- 数据同步。主节点和从节点(备用节点)之间的数据需要进行同步。
- 主从切换。若主节点宕机,需要有一种机制可以切换从节点变成主节点。
保证在Redis节点宕机的情况下,也可以对外提供服务;
主从复制
主从同步概述
主从复制是数据同步方式,解决了单点故障的问题,但不能保证高可用(是高可用的基础)。主要用来实现 redis 数据的可靠性,防止主 redis 所在磁盘损坏,造成数据永久丢失。
主从之间采用异步复制的方式,以及采用读写分离的方式,主节点(master)可以进行读写操作,从节点(replica)一般是只读。也就是说,所有的数据修改只在主节点上进行,然后将最新的数据同步给节点,这样就使得主从服务器的数据是一致的。
Redis主从同步无法提供高可用能力和数据保护能力,因为Redis主节点发生故障后,需要手动进行故障转移;
当有新节点加入集群后,新节点主动和master节点建立连接然后同步数据;
同步原理
全量同步
- 全量数据同步是在从节点刚加入复制集群或者需要进行完整数据更新时执行的同步过程。
- 它的目标是将主节点上的所有数据完整地同步到从节点。全量数据同步的过程是将主节点上的所有内存数据通过快照(RDB文件)方式发送给从节点,从节点接收到快照后将其加载到自己的数据库中。
- 全量数据同步会消耗较大的网络带宽和时间,特别是在数据集较大的情况下。并且在全量数据同步过程中,从节点无法处理外部的读取请求,因为它正在重新加载大量的数据。
主从库间第一次全量同步,具体分成三个阶段:
- 当一个从库启动时,从库给主库发送
psync
命令进行数据同步(psync
命令包含:主库的runID
和复制进度offset
两个参数), - 当主库接收到psync 命令后将会保存RDB 文件并发送给从库,发送期间会使用缓存区(
replication buffer
)记录后续的所有写操作 ,从库收到数据后,会先清空当前数据库,然后加载从主库获取的RDB 文件, - 当主库完成 RDB 文件发送后,也会把将保存发送RDB文件期间写操作的
replication buffer
发给从库,从库再重新执行这些操作。这样一来,主从库就实现同步了。
另外,为了分担主库生成 RDB 文件和传输 RDB 文件压力,提高效率,可以使用 “主 - 从 - 从”模式将主库生成 RDB 和传输 RDB 的压力,以级联的方式分散到从库上。
增量同步
增量同步,基于环形缓冲区repl_backlog_buffer
缓存区实现。
在环形缓冲区,主库会记录自己写到的位置 master_repl_offset
,从库则会记录自己已经读到的位置slave_repl_offset
, 主库并通过master_repl_offset
和 slave_repl_offset
的差值的数据同步到从库。
主从库间网络断了, 主从库会采用增量复制的方式继续同步,主库会把断连期间收到的写操作命令,写入 replication buffer
,同时也会把这些操作命令也写入 repl_backlog_buffer
这个缓冲区,然后主库并通过master_repl_offset
和 slave_repl_offset
的差值数据同步到从库。
因为repl_backlog_buffer
是一个环形缓冲区,当在缓冲区写满后,主库会继续写入,此时,会出现什么情况呢?
覆盖掉之前写入的操作。如果从库的读取速度比较慢,就有可能导致从库还未读取的操作被主库新写的操作覆盖了,这会导致主从库间的数据不一致。因此需要关注 repl_backlog_size
参数,调整合适的缓冲空间大小,避免数据覆盖,主从数据不一致。
实现原理
主从复制主要由环形缓冲区、复制偏移量、RUN ID三个部分组成。
服务器 RUN ID
RUNID用于构建主从的关系。无论主库还是从库都有自己的 RUN ID , RUN ID 启动时自动产生, RUN ID 由 40 个随机的十六进制字组成。
当从库对主库初次复制时,主库将自身的 RUN ID 传送给从库,从库会将 RUN ID 保存。 当从库断线重连主库时,从库将向主库发送之前保存的 RUN ID :
- 从库 RUN ID 和主库 RUN ID 一致,说明从库断线前复制的就是当前的主库;主库尝试执行增量同步操作;
- 若不一致,说明从库断线前复制的主库并不时当前的主库,则主库将对从库执行全量同步操作。
复制偏移量 offset
主从都会维护一个复制偏移量:
- 主库向从库发送 N 个字节的数据时,将自己的复制偏移量上加 N;
- 从库接收到主库发送的 N 个字节数据时,将自己的复制偏移量加上 N。
通过比较主从偏移量得知主从之间数据是否一致;偏移量相同,则数据一致;偏移量不同,则数据不一致。
环形缓冲区
本质:固定长度先进先出队列。
当因某些原因(网络抖动或从库宕机)从库与主库断开连接,避免重新连接后开始全量同步,在主库设置了一个环形缓冲区;该缓冲区会在从库失联期间累计主库的写操作;当从库重连,会发送自身的复制偏移量到主库,主库会比较主从的复制偏移量“
- 若从库 offset 还在复制积压缓冲区中,则进行增量同步;
- 否则,主库将对从库执行全量同步。
从复制的优缺点
优点:
- 配置简单,易于实现。
- 实现数据冗余,提高数据可靠性。
- 读写分离,提高系统性能。
缺点:
- 主节点故障时,需要手动切换到从节点,故障恢复时间较长。
- 主节点承担所有写操作,可能成为性能瓶颈。
- 无法实现数据分片,受单节点内存限制。
配置Slave的作用
最低配:一主二从,当主节点宕机后,其中一个从节点升级为主节点,还能剩一个从节点。
- 数据冗余:热备份,持久化另一种方式,不至于主节点宕机后数据全部丢失;
- 故障恢复:master宕机,快速升级slave为master,需要手动操作;
- 读写分离:master写,slave,提高服务器负载能力,同时可以根据需求添加slave;
- 负载均衡:配合读写分离,读多写少场景,多个slave分担负载,大大提高并发
- 高可用基石:是实现哨兵和集群的基础
主从复制场景应用
主从复制模式适用于以下场景:
- 数据备份和容灾恢复:通过从节点备份主节点的数据,实现数据冗余。
- 读写分离:将读操作分发到从节点,减轻主节点压力,提高系统性能。
- 在线升级和扩展:在不影响主节点的情况下,通过增加从节点来扩展系统的读取能力。
总结:主从复制模式适合数据备份、读写分离和在线升级等场景,但在主节点故障时需要手动切换,不能自动实现故障转移。如果对高可用性要求较高,可以考虑使用哨兵模式或Cluster模式。
哨兵模式
概述
当主库挂了,redis写操作和数据同步无法进行,为了避免这样情况,可以在主库挂了后重新在从库中选举出一个新主库,并通知到客户端,redis提供了 哨兵机制,哨兵为运行在特殊模式下的 Redis 进程。
哨兵模式由一个或多个 sentinel实例构成 sentinel 系统。该系统通过 ping-pong
心跳检测的方法监视任意多个主库以及这些主库所属的从库。当主库处于下线状态,自动将该主库所属的某个从库升级为新的主库,从而实现高可用。
客户端来连接集群时,会首先连接 sentinel,通过 sentinel 来查询主节点的地址,然后再连接主节点进行数据交互。当主节点发生故障时,客户端会重新向 sentinel 索要主库地址,sentinel会将最新的主库地址告诉客户端。通过这样客户端无须重启即可自动完成节点切换。
Sentinel 节点个数是奇数,不存储数据,用来监控节点的状态和选举主节点,只提供一个数据节点服务。Sentinel 节点不仅监控 Redis 主从节点,同时还互相监控,形成多哨兵模式。
Sentinel 模式当中涉及的多个选举流程采用的是 raft 一致性算法。
原理
Redis会有主从自主切换机制,那如何实现的呢?
哨兵机制是实现主从库自动切换的关键机制,其主要分为三个阶段:
- 监控/主观下线: sentinel 会以每秒一次的频率向所有节点(其他sentinel、主节点、以及从节点)发送
ping
消息,然后通过接收返回判断该节点是否下线。如果在配置指定 down-after-milliseconds 时间内则被判断为主观下线。 - 客观下线 当一个 sentinel 节点将一个主节点判断为主观下线之后,为了确认这个主节点是否真的下线,它会向其他 sentinel 节点进行询问,如果收到一定数量(半数以上)的已下线回复,sentinel 会将主节点判定为客观下线,并通过领头 sentinel 节点对主节点执行故障转移。
- 故障转移 主节点被判定为客观下线后,开始领头 sentinel 选举。按照谁发现谁处理的原则选举领头 sentinel,需要半数以上的 sentinel 支持。选举领头 sentinel 后,开始执行对主节点故障转移:从从节点中选举一个从节点作为新的主节点,通知其他从节点复制连接新的主节点,若故障主节点重新连接,将作为新的主节点的从节点;
- 通知 : 哨兵会将新主库的信息发送给其他从库,让它们和新主库建立连接,并进行数据复制。同时,哨兵会把新主库的信息广播通知给客户端,让它们把请求操作发到新主库上。
哨兵判断主节点是否在线的方法就是通过主观下线和客观下线,只有半数以上的哨兵认为主节点下线后,整个哨兵集群才达成一致认为主节点下线
为什么会有这两种"主观下线"和“客观下线”的下线状态呢?
由于单机哨兵很容易产生误判,误判后主从切换会产生一系列的额外开销,为了减少误判,避免这些不必要的开销,采用哨兵集群,引入多个哨兵实例一起来判断,就可以避免单个哨兵因为自身网络状况不好,而误判主库下线的情况,
基于少数服从多数原则, 当有 N 个哨兵实例时,最好要有 N/2 + 1 个实例判断主库为“主观下线”,才能最终判定主库为“客观下线” (可以自定义设置阙值)。
那么哨兵之间是如何互相通信的呢?
哨兵集群中哨兵实例之间可以相互发现,基于 Redis
提供的发布 / 订阅机制(pub
/sub
机制),
哨兵可以在主库中发布/订阅消息,在主库上有一个名为“\__sentinel__:hello
”的频道,不同哨兵就是通过它来相互发现,实现互相通信的,而且只有订阅了同一个频道的应用,才能通过发布的消息进行信息交换。
哨兵 1连接相关信息(IP端口)发布到“\__sentinel__:hello
”频道上,哨兵 2 和 3 订阅了该频道。
哨兵 2 和 3 就可以从这个频道直接获取哨兵 1连接信息,以这样的方式哨兵集群就形成了,实现各个哨兵互相通信。
哨兵集群中各个实现通信后,就可以判定主库是否已客观下线。
在已判定主库已下线后,又如何选举出新的主库?
新主库选举按照一定条件筛选出的符合条件的从库,并按照一定规则对其进行打分,最高分者为新主库。
通常一定条件包括:
- 从库的当前在线状态,
- 判断它之前的网络连接状态,通过
down-after-milliseconds * num
(断开连接次数),当断开连接次数超过阈值,不适合为新主库。
一定规则包括:
- 从库优先级 , 通过
slave-priority
配置项,给不同的从库设置不同优先级,优先级最高的从库得分高 - 从库复制进度,和旧主库同步程度最接近的从库得分高,通过
repl_backlog_buffer
缓冲区记录主库master_repl_offset
和从库slave_repl_offset
相差最小高分 - 从库 ID 号 , ID 号小的从库得分高。
全都都基于在只有在一定规则中的某一轮评出最高分从库就选举结束,哨兵发起主从切换。
leader哨兵
选举完新的主库后,不能每个哨兵都发起主从切换,需要选举成leader哨兵,那如何选举leader哨兵执行主从切换?
选举leader
哨兵,也是基于少数服从多数原则"投票仲裁"选举出来,
- 当任何一个从库判定主库“主观下线”后,发送命令
s-master-down-by-addr
命令发送想要成为Leader的信号, - 其他哨兵根据与主机连接情况作出相对的响应,赞成票Y,反对票N,而且如果有多个哨兵发起请求,每个哨兵的赞成票只能投给其中一个,其他只能为反对票。
想要成为Leader 的哨兵,要满足两个条件:
- 第一,获得半数以上的赞成票;
- 第二,获得的票数同时还需要大于等于哨兵配置文件中的
quorum
值。
选举完leader哨兵并新主库切换完毕之后,那么leader哨兵怎么通知客户端?
还是基于哨兵自身的 pub/sub 功能,实现了客户端和哨兵之间的事件通知,客户端订阅哨兵自身消息频道 ,而且哨兵提供的消息订阅频道有很多,不同频道包含了:
事件 | 频道 |
---|---|
主库下线事件 | +sdown(实例进入“主观下线”状态) -sdown(实例退出“主观下线”状态) +odown(实例进入“客观下线”状态) -odown(实例退出“客观下线”状态) |
新主库切换 | + switch-master(主库地址发生变化) |
其中,当客户端从哨兵订阅消息主从库切换,当主库切换后,端户端就会接收到新主库的连接信息:
数据问题
Redis实现高可用,但实现期间可能产出一些风险:
- 主备切换的过程, 异步复制导致的数据丢失
- 脑裂导致的数据丢失
- 主备切换的过程,异步复制导致数据不一致
数据丢失-主从异步复制
因为master
将数据复制给slave
是异步实现的,在复制过程中,这可能存在master有部分数据还没复制到slave,master就宕机了,此时这些部分数据就丢失了。
总结:主库的数据还没有同步到从库,结果主库发生了故障,未同步的数据就丢失了。
数据丢失-脑裂
何为脑裂?当一个集群中的 master 恰好网络故障,导致与 sentinal 通信不上了,sentinal会认为master下线,且sentinal选举出一个slave 作为新的 master,此时就存在两个 master了。
此时,可能存在client还没来得及切换到新的master,还继续写向旧master的数据,当master再次恢复的时候,会被作为一个slave挂到新的master 上去,自己的数据将会清空,重新从新的master 复制数据,这样就会导致数据缺失。
总结:主库的数据还没有同步到从库,结果主库发生了故障,等从库升级为主库后,未同步的数据就丢失了。
数据丢失解决方案
数据丢失可以通过合理地配置参数 min-slaves-to-write 和 min-slaves-max-lag 解决,比如
min-slaves-to-write
1min-slaves-max-lag
10
如上两个配置:要求至少有 1 个 slave,数据复制和同步的延迟不能超过 10 秒,如果超过 1 个 slave,数据复制和同步的延迟都超过了 10 秒钟,那么这个时候,master 就不会再接收任何请求了。
优缺点
优点:
- 自动故障转移,提高系统的高可用性。
- 具有主从复制模式的所有优点,如数据冗余和读写分离。
缺点
redis 采用异步复制的方式,意味着当主节点挂掉时,从节点可能没有收到全部的同步消息,这部分未同步的消息将丢失。如果主从延迟特别大,那么丢失可能会特别多。sentinel 无法保证消息完全不丢失,但是可以通过配置来尽量保证少丢失。
# 主库必须有一个从节点在进行正常复制,否则主库就停止对外
#写服务,此时丧失了可用性
min-slaves-to-write 1
# 这个参数用来定义什么是正常复制,该参数表示如果在10s内
没有收到从库反馈,就意味着从库同步不正常;
min-slaves-max-lag 10
总结来说:
1)部署麻烦:哨兵模式的配置相对复杂,需要管理和维护多个哨兵节点以及与它们关联的 Redis 服务器。调试和故障排除也可能变得更加困难。
2)数据一致性:哨兵模式下的故障转移是异步进行的,这意味着在发生主服务器故障时,可能会有一段时间内的数据丢失。因此,在一些对数据一致性要求非常高的场景下,哨兵模式可能无法满足需求。
3)难以在线扩容的缺点,单个Redis Master的容量受限于单机配置
4)延迟增加:当主服务器故障时,哨兵节点需要通过选举机制选择新的主服务器,并通知其他从服务器切换到新的主服务器。这个过程需要时间(至少十几秒),会导致系统的延迟增加。
5)单点故障:哨兵节点是集群的核心,它们负责监控主服务器和从服务器的状态,并执行故障转移操作。然而,如果哨兵节点本身发生故障,整个系统的可用性将会受到影响。
哨兵模式场景应用
哨兵模式适用于以下场景:
- 高可用性要求较高的场景:通过自动故障转移,确保服务的持续可用。
- 数据备份和容灾恢复:在主从复制的基础上,提供自动故障转移功能。
总结:哨兵模式在主从复制模式的基础上实现了自动故障转移,提高了系统的高可用性。然而,它仍然无法实现数据分片。如果需要实现数据分片和负载均衡,可以考虑使用Cluster模式。
Cluster模式
概述
Redis的哨兵模式基本已经可以实现高可用,读写分离 ,但是在这种模式下每台 Redis 服务器都存储相同的数据,很浪费内存,所以在 redis3.0上加入了Cluster 集群模式,实现了 Redis 的分布式存储,也就是说每台 Redis 节点上存储不同的内容。下面是Cluster 集群模式的一些特点:
- Sentinel模式基本可以满足一般生产的需求,具备高可用性。但是当数据量过大到一台服务器存放不下的情况时,主从模式或sentinel模式就不能满足需求了,这个时候需要对存储的数据进行分片,将数据存储到多个Redis实例中。cluster模式的出现就是为了解决单机Redis容量有限的问题,将Redis的数据根据一定的规则分配到多台机器。
- Cluster可以说是sentinel+主从模式的结合体,通过cluster可以实现主从和master重选功能,所以如果配置两个副本三个分片的话,就需要六个Redis实例。因为Redis的数据是根据一定规则分配到cluster的不同机器的,当数据量过大时,可以新增机器进行扩容。
- 使用集群,只需要将redis配置文件中的cluster-enable配置打开即可,每个集群中至少需要三个主数据库才能正常运行,新增节点非常方便。
Cluster集群模式有如下一些特点:
- 多个redis节点网络互联,数据共享;
- 所有的节点都是一主一从(也可以是一主多从),其中从不提供服务,仅作为备用;
- 不支持同时处理多个key(如MSET/MGET),因为redis需要把key均匀分布在各个节点上,并发量很高的情况下同时创建key-value会降低性能并导致不可预测的行为;
- 支持在线增加、删除节点;
- 客户端可以连接任何一个主节点进行读写。
- 集群由多个Redis主从组成,每一个主从代表一个节点,每个节点负责一部分数据,他们之间通过一种特殊的二进制协议交互集群信息。
- Redis Cluster将所有数据分片,分成16384个槽位,Redis Cluster对key值使用crc16算法进行hash,然后用除留余数发模除16384得到具体的槽位,每个节点负责其中一部分槽位。
- 当客户端连接集群,会得到一份集群的槽位匹配信息,当客户端要查找key,可以直接定位到目标节点。
- Cluster去中心化,由多个节点组成,客户端连接时可以只用一个节点的地址,其余节点可通过该节点自动发现,但如果该节点挂了,就必须手动更换地址,因此连接多个地址安全性更高。
拓扑架构
拓扑图
如图,该集群由三个 redis 节点组成,每个节点负责整个集群的一部分数据,每个节点负责的数据多少可能不一样。这三个节点相互连接组成一个对等的集群,它们之间通过一种特殊的二进制协议交互集群信息。
容错
Redis Cluster拥有类似哨兵的功能,每个节点仍需设置若干从节点,主节点发生故障,集群可将slave升级为master;否则如果master挂了,集群完全不可用;
且Redis Cluster是去中心化,集群内某个节点不可用时,一个节点认为他失联并不代表所以节点都认为他失联,集群要进行一次商议,只有大多数节点认为他失联,才会认为其需要主从切换来容错。
动态扩容
假如原先集群中有3个节点,一共3000个数据,可能1-1000在第一个节点,1001-2000在第二个节点,2001-3000在第三个节点。
当新节点加入集群,需要手动将槽和数据迁移到新节点,可以使用redis-trib工具或手动命令迁移
特点
1)去中心化,主节点关系对等,支持动态扩容;
2)Cluster自动具备哨兵监控和故障转移(主从切换)能力
3)客户端与服务端缓存槽位信息,以服务端为准,客户节点缓存主要为了避免连接切换
4)可人为迁移数据
5)主节点处理读写命令
流程
1)连接集群中任意一个节点
2)若数据不在该节点,将收到连接切换的命令,继而连接到目标节点
3)故障转移(主节点下移)
- 集群结点间互相监控,交换节点的状态信息
- 若某主节点下线,将会被其他主节点标记下线
- 接着从下线主节点的从节点中选择一个数据最新的从节点作为主节点
- 从节点继承下线主节点的槽位信息,并广播该消息给集群中的其他节点
故障检测
集群中每个节点都会定期地向集群中的其他节点发送 ping
消息,如果接收 ping
消息的节点没有在规定时间内回复 pong
消息,那么这个没有回复 pong
消息的节点会被标记为 PFAIL
(probable fail)。
集群中各个节点会通过互相发送消息的方式来交换集群中各个节点的状态信息;如果在一个集群中,半数以上负责处理槽的主节点都将某个主节点 A 报告为疑似下线,那么这个主节点 A将被标记为下线( FAIL
);标记主节点 A 为下线状态的主节点会广播这条消息,其他节点(包括A节点的从节点)也会将A节点标识为FAIL
;
故障转移
当从节点发现自己的主节点进入 FAIL
状态,从节点将开始对下线主节点进行故障转移:
- 从数据最新的从节点中选举为主节点;
- 该从节点会执行 replica no one 命令,称为新的主节点;
- 新的主节点会撤销所有对已下线主节点的槽指派,并将这些槽全部指派给自己;
- 新的主节点向集群广播一条
pong
消息,这条pong
消息可以让集群中的其他节点立即知道这个节点已经由从节点变成主节点,并且这个主节点已经接管了之前下线的主节点; - 新的主节点开始接收和自己负责处理的槽有关的命令请求,故障转移结束
优缺点
优点:
- 数据分片,实现大规模数据存储。
- 负载均衡,提高系统性能。
- 自动故障转移,提高高可用性。
缺点:
- 配置和管理较复杂。
- 一些复杂的多键操作可能受到限制。
Cluster模式场景应用
Cluster模式适用于以下场景:
- 大规模数据存储:通过数据分片,突破单节点内存限制。
- 高性能要求场景:通过负载均衡,提高系统性能。
- 高可用性要求场景:通过自动故障转移,确保服务的持续可用。
总结:Cluster模式在提供高可用性的同时,实现了数据分片和负载均衡,适用于大规模数据存储和高性能要求的场景。然而,它的配置和管理相对复杂,且某些复杂的多键操作可能受到限制。
Sentinel节点为什么是至少三个且奇数个?
首先必须是集群,所以不能是1个。而选举过程要大多数同意才行(即最少一半+1个),而奇数个节点在同样选举条件上可以节省一台机器。
比如一共5台,有3台同意了就行;而4台,大多数同意也要3台。
Redis集群节点数为什么至少是6个?
6个包含一主一从,如果每个节点一主二从则需要9个
最少3个节点,是因为容错能力相同情况下,奇数节点更节省资源(3个节点的集群允许一个节点宕机;而4个节点的集群也允许一个节点宕机)
为什么Redis Cluster槽位设计成16384个?
2^14=16384,当槽位再扩就是32768、65536,此时每一次ping都要将槽位作为心跳包,信息太大,占用带宽
由于集群节点越多,心跳包携带的数据就越多。当节点超过1000,会导致网络拥堵。因此Redis作者不建议Redis Cluster节点数量超过1000,那么16384槽位也足够用
总结
- 主从复制模式:适用于数据备份和读写分离场景,配置简单,但在主节点故障时需要手动切换。
- 哨兵模式:在主从复制的基础上实现自动故障转移,提高高可用性,适用于高可用性要求较高的场景。
- Cluster模式:通过数据分片和负载均衡实现大规模数据存储和高性能,适用于大规模数据存储和高性能要求场景。