Redis集群介绍及测试思路

Redis集群介绍及测试思路
作者:京东零售 李磊
Redis集群介绍
Redis集群一般有四种方式,分别为:主从复制、哨兵模式、Cluster以及各大厂的集群方案。在3.0版本之前只支持单实例模式,3.0之后支持了集群方式。在3.0之前各大厂为了解决单实例Redis的存储瓶颈问题各自推出了自己的集群方案,其核心思想就是数据分片,主要有客户端分片、代理分片、服务端分片。这里咱们只介绍前三种方式:主从、哨兵、Cluster。
1、主从复制
Redis单节点的数据是存储在一台服务器上的,如果服务器出现故障,会导致数据不可用,而且读写都是在同一台服务器上,请求量大时会出现I/O瓶颈。为了避免单点故障和读写不分离,Redis提供了复制功能来实现Master中的数据向Slave数据库的同步。Master可以有多个Slave节点,Slave节点也可以有Slave节点,从节点是级联结构,如下图所示:
主从复制工作原理
一般情况下为了让数据读写分离,Master节点用来执行写操作,Slave节点提供读操作,Master执行写操作时将变化的数据同步到Slave,其工作原理如下图所示:
Redis主从复制基本原理有三种:全量复制、基于长连接的命令传播、增量复制。
首先介绍一下全量复制,当主从服务器刚建立连接的时候,会按照三个阶段完成数据的第一次同步。假设现在有实例1(192.168.1.1)和实例2(192.168.1.2),当我们在实例2上执行“replicaof 192.168.1.1 6379”命令后,实例2就变成了实例1的从库,并开始从实例1上复制数据,有如下三个阶段:
第一个阶段,是主从库之间建立连接、协商同步的过程,为全量复制做准备。具体来说,从库给主库发送psync命令,表示要进行数据同步,主库根据这个命令的参数来启动复制。psync命令包含了主库的runID和复制进度offset两个参数。
•runID:是每个Redis实例启动时自动生成的一个随机ID,用来唯一标记这个实例。当从库和主库第一次复制时,因为不知道主库的runID,所以将runID设置为“?”。
•offset:设置为-1,表示第一次复制。
主库收到psync命令后,会用FULLRESYNC响应命令带上两个参数:主库runID和主库目前的复制进度offset,返回给从库,从库收到响应后会记录下这两个参数。FULLRESYNC响应表示第一次复制采用的全量复制,也就是说,主库会把当前所有的数据都复制给从库。
第二个阶段,主库将所有数据同步给从库,从库收到数据后,首先清空现有数据,然后在本地完成数据加载。这个过程依赖于内存快照生成的RDB文件。具体来说,主库执行bgsave命令,生成RDB文件,接着将文件发给从库。
第三个阶段,主库会把第二阶段执行过程中新接收到的写命令,再发送给从库。具体来说,当主库完成RDB文件发送后,就会把此时replication buffer中的修改和新增操作发给从库,从库再重新执行这些操作。这样一来,主从库就实现同步了。
以上是全量复制的基本流程,一旦主从库完成了全量复制,它们之间就会一直维护一个网络连接,主库会通过这个连接将后续陆续收到的命令操作再同步给从库,这个过程也称为基于长连接的命令传播,可以避免频繁建立连接的开销。
长连接是基于网络的,那么它就存在网络断开的风险,在Redis2.8之前,如果主从库在命令传播时出现了网络闪断,那么从库会和主库重新进行一次全量复制,开销非常大。在Redis2.8开始,网络闪断之后,主从库会采用增量复制的方式继续同步,就只会把主从网络断连期间主库收到的命令同步给从库。
增量复制核心在于repl_backlog_buffer这个缓冲区。当主从库断连后,主库会把断连期间收到的写操作命令写入replication buffer,同时也会写入repl_backlog_buffer这个缓冲区。repl_backlog_buffer是一个环形缓冲区,主库记录自己写到的位置,从库也记录自己读到的位置。主从连接恢复之后,从库首先给主库发送psync命令,并把自己当前的slave_repl_offset发给主库,主库会判断自己的master_repl_offset和slave_repl_offset之间的差距,一般来说master_repl_offset会大于slave_repl_offset。此时,主库只用把master_repl_offset和slave_repl_offset之间的命令操作同步给从库就行。
2、哨兵模式
sentinel,中文名哨兵。Redis的sentinel系统用于管理多个Redis实例,该系统主要执行以下四个任务:
1.监控(Monitoring):Sentinel会不断的检查主服务器和从服务器是否正常运作。
2.自动故障转移(Automatic failover):当主节点不能正常工作时,哨兵会开始自动故障转移操作,它会将失效主节点的其中一个从节点升级为新的主节点,并让其他从节点改为复制新的主节点。
3.通知(Notification):哨兵可以将故障转移的结果发送给客户端。
4.配置提供者(Configuration provider):客户端在初始化时,通过连接哨兵来获得当前Redis服务的主节点地址。
其中,监控和自动故障转移功能,使得哨兵可以及时发现主节点故障并完成转移;而配置提供者和通知功能,则需要在与客户端的交互中才能体现。
哨兵用于实现Redis集群的高可用性,本身也是分布式的,作为一个哨兵集群去运行。Sentinel的进程之间使用流言协议(gossip protocols)来接收关于主服务器是否下线的信息, 并使用投票协议(agreement protocols)来决定是否执行自动故障迁移, 以及选择哪个从服务器作为新的主服务器。下面分别介绍一下监控和自动故障转移的基本原理:
Sentinel集群监控原理
1.每个 Sentinel 以每秒一次的频率向它所知的主从服务器以及其它 Sentinel 实例发送一个 PING 命令。
2.如果一个实例距离最后一次有效回复 PING 命令的时间超过指定的值, 那么这个实例会被 Sentinel 标记为主观下线。
3.正在监视这个主服务器的所有 Sentinel 要以每秒一次的频率确认主服务器的确进入了主观下线状态。
4.有足够数量的 Sentinel 在指定的时间范围内同意这一判断, 那么这个主服务器被标记为客观下线。
5.每个 Sentinel 会以每 10 秒一次的频率向它已知的所有主从服务器发送 INFO 命令。当一个主服务器被 Sentinel 标记为客观下线时, Sentinel 向下线主服务器的所有从服务器发送 INFO 命令的频率会从 10 秒一次改为每秒一次。
6.Sentinel 和其它 Sentinel 协商主节点的状态,如果主节点处于 ODOWN(客观下线) 状态,则投票自动选出新的主节点,将剩余的从节点指向新的主节点进行数据复制。
7.当没有足够数量的 Sentinel 同意主服务器 下线时, 主服务器的客观下线状态就会被移除。主服务器重新向 Sentinel 的 PING 命令返回有效回复时,主服务器主观下线状态就会被移除 。
哨兵是如何对Slave进行监控的呢?当然是通过Master来实现的,哨兵向Master发送INFO命令,Master收到命令后便将Slave列表告诉哨兵。然后哨兵根据Slave列表信息与每一个Slave建立连接,并且根据这个连接持续监控Slave。
Sentinel集群故障自动转移
故障转移简单来说有以下三个流程:
1.Sentinel系统挑选出现故障的主服务器属下的其中一个从服务器,并将选中的从服务器升级为新的主服务器。
2.Sentinel系统向出现故障的主服务器属下的所有从服务器发送新的复制命令,让他们成为新的主服务器的从服务器,当所有从服务器都开始复制新的主服务器时,故障转移操作执行完毕。
3.Sentinel系统还会继续监听已下线的故障服务器,如果它重新上线时,会将它设置为新的主服务器的从服务器。
示意图
如上图所示,Server1为Master节点,Server2、Server3、Server4为主服务器Server1的从节点,而Sentinel系统正在监视所有4个服务器
故障转移
如上图所示,主服务server1挂掉了,处于下线状态,那么server2、server3、server4对主服务器的复制操作将被终止,Server2被Sentinel系统升级为新的Master,然后将Server2和Server3转为新Master的从服务器,完成故障转移。同时继续监听已下线的Server1。
如上图所示当Server1恢复后,Sentinel系统将它设置为新的主服务器Server2的从服务器,集群恢复原有状态。
3、Cluster集群
Redis的哨兵模式基本已经可以实现高可用,读写分离 ,但是在这种模式下每台Redis服务器都存储相同的数据,很浪费内存。所以在redis3.0上加入了Cluster集群模式,实现了Redis的分布式存储,也就是说每台 Redis 节点上存储不同的内容。Redis集群是由多个主从节点群组成的分布式服务集群,具有复制、高可用和分片特性。这种集群模式没有中心节点,可水平扩展,主要是针对海量数据、高并发、高可用的场景。
Cluster集群模式主要有以下三个特性:
1.分片存储:Redis3.0加入了 Redis 的集群模式,实现了数据的分布式存储,对数据进行分片,将不同的数据存储在不同的master节点上面,从而解决了海量数据的存储问题。
2.指令转换:Redis集群采用去中心化的思想,没有中心节点的说法,对于客户端来说,整个集群可以看成一个整体,可以连接任意一个节点进行操作,就像操作单一Redis实例一样,不需要任何代理中间件,当客户端操作的key没有分配到该node上时,Redis会返回转向指令,指向正确的Redis节点。
3.主从和哨兵:Redis也内置了高可用机制,支持N个master节点,每个master节点都可以挂载多个slave节点,当master节点挂掉时,集群会提升它的某个slave节点作为新的master节点。
如上图所示,Redis集群可以看成多个主从架构组合起来的,每一个主从架构可以看成一个节点。
Redis集群数据分片原理
集群的整个数据库被分为 16384 个槽(slot),数据库中的每个键都属于这 16384 个槽的其中一个,集群中的每个节点可以处理 0 个或最多 16384 个槽。
Key 与哈希槽映射过程可以分为两大步骤:
1.根据键值对的 key,使用 CRC16 算法,计算出一个 16 bit 的值。
2.将 16 bit 的值对 16384 执行取模,得到 0 ~ 16383 的数表示 key 对应的哈希槽。
另外,Cluster 还允许用户强制某个 key 挂在特定槽位上,通过在 key 字符串里面嵌入 tag 标记,这就可以强制 key 所挂在的槽位等于 tag 所在的槽位。
Cluster集群请求路由方式
客户端直连 Redis 服务,进行读写操作时,若Key 对应的 Slot在当前直连的节点上,则可直接读写,但也有可能并不在当前直连的节点上,则经过“重定向”才能转发到正确的节点,如下图所示
和普通的查询路由相比,Redis Cluster 借助客户端实现的请求路由是一种混合形式的查询路由,它并非从一个 Redis 节点到另外一个 Redis,而是借助客户端转发到正确的节点。实际应用中,可以在客户端缓存 Slot 与 Redis 节点的映射关系,当接收到 MOVED 响应时修改缓存中的映射关系。如此,基于保存的映射关系,请求时会直接发送到正确的节点上,从而减少一次交互,提升效率。
那么客户端具体是怎么确定访问的数据到底分布在哪个实例上呢?
Redis 实例会将自己的哈希槽信息通过 Gossip 协议发送给集群中其他的实例,实现了哈希槽分配信息的扩散。这样,集群中的每个实例都有所有哈希槽与实例之间的映射关系信息。在切片数据的时候是将 key 通过 CRC16 计算出一个值再对 16384 取模得到对应的 Slot,这个计算任务可以在客户端上执行发送请求的时候执行。但是,定位到槽以后还需要进一步定位到该 Slot 所在 Redis 实例。当客户端连接任何一个实例,实例就将哈希槽与实例的映射关系响应给客户端,客户端就会将哈希槽与实例映射信息缓存在本地。当客户端请求时,会计算出键所对应的哈希槽,在通过本地缓存的哈希槽实例映射信息定位到数据所在实例上,再将请求发送给对应的实例。
这个时候大家可能会有个疑问:哈希槽与实例之间的映射关系由于新增实例或者负载均衡重新分配导致改变了咋办?
集群中的实例通过 Gossip 协议互相传递消息获取最新的哈希槽分配信息,但是,客户端无法感知。Redis Cluster 提供了重定向机制:客户端将请求发送到实例上,这个实例没有相应的数据,该 Redis 实例会告诉客户端将请求发送到其他的实例上。
Redis 如何告知客户端重定向访问新实例呢?分为两种情况:MOVED 错误、ASK 错误。
MOVED 错误(负载均衡,数据已经迁移到其他实例上):当客户端将一个键值对操作请求发送给某个实例,而这个键所在的槽并非由自己负责的时候,该实例会返回一个 MOVED 错误指引转向正在负责该槽的节点。
(error) MOVED 16330 172.17.18.2:6379
该响应表示客户端请求的键值对所在的哈希槽 16330 迁移到了 172.17.18.2 这个实例上,端口是 6379。这样客户端就与 172.17.18.2:6379 建立连接,并发送 GET 请求。同时,客户端还会更新本地缓存,将该 slot 与 Redis 实例对应关系更新正确。
ASK 错误:如果某个 slot 的数据比较多,部分迁移到新实例,还有一部分没有迁移咋办?
如果请求的 key 在当前节点找到就直接执行命令,否则就需要 ASK 错误响应了,槽部分迁移未完成的情况下,如果需要访问的 key 所在 Slot 正在从从 实例 1 迁移到 实例 2,实例 1 会返回客户端一条 ASK 报错信息:客户端请求的 key 所在的哈希槽正在迁移到实例 2 上,你先给实例 2 发送一个 ASKING 命令,接着发送操作命令。
(error) ASK 16330 172.17.18.2:6379
比如客户端请求定位到 key的槽16330 在实例 172.17.18.1 上,节点1如果找得到就直接执行命令,否则响应 ASK 错误信息,并指引客户端转向正在迁移的目标节点 172.17.18.2:6379
注意:ASK 错误指令并不会更新客户端缓存的哈希槽分配信息。所以客户端再次请求 Slot 16330 的数据,还是会先给 172.17.18.1 实例发送请求,只不过节点会响应 ASK 命令让客户端给新实例发送一次请求。MOVED指令则更新客户端本地缓存,让后续指令都发往新实例。
Cluster集群选举算法
1.集群的配置纪元 +1,是一个自曾计数器,初始值 0 ,每次执行故障转移都会 +1。
2.检测到主节点下线的从节点向集群广播一条
CLUSTERMSG_TYPE_FAILOVER_AUTH_REQUEST消息,要求所有收到这条消息、并且具有投票权的主节点向这个从节点投票。
3.这个主节点尚未投票给其他从节点,那么主节点将向要求投票的从节点返回一条
CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK消息,表示这个主节点支持从节点成为新的主节点。
4.参与选举的从节点都会接收
CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK消息,如果收集到的票 >= (N/2) + 1 支持,那么这个从节点就被选举为新主节点。
5.如果在一个配置纪元里面没有从节点能收集到足够多的支持票,那么集群进入一个新的配置纪元,并再次进行选举,直到选出新的主节点为止。
流程如下图所示:
Cluster集群故障转移
Redis集群的故障转移主要有三个流程:故障检测、选主流程、故障转移,下面分别简单介绍一下。
•故障检查
一个节点认为某个节点失联了并不代表所有的节点都认为它失联了。只有当大多数负责处理 slot 的节点都认定了某个节点下线了,集群才认为该节点需要进行主从切换。Redis 集群节点采用 Gossip协议来广播自己的状态以及自己对整个集群认知的改变。比如一个节点发现某个节点失联了 (PFail),它会将这条信息向整个集群广播,其它节点也就可以收到这个节点的失联信息。
如果一个节点收到了某个节点失联的数量 (PFail Count) 已经达到了集群的大多数,就可以标记该节点为确定下线状态 (Fail),然后向整个集群广播,强迫其它节点也接收该节点已经下线的事实,并立即对该失联节点进行主从切换。
•选主流程
参考上一节的“Cluster集群选举算法”
•故障转移
当一个 Slave 发现自己的主节点进入已下线状态后,从节点将开始对下线的主节点进行故障转移。
1.从下线的 Master 节点的 Slave 节点列表选择一个节点成为新主节点。
2.新主节点会撤销所有对已下线主节点的 slot 指派,并将这些 slots 指派给自己。
3.新的主节点向集群广播一条 PONG 消息,这条 PONG 消息可以让集群中的其他节点立即知道这个节点已经由从节点变成了主节点,并且这个主节点已经接管了原本由已下
Redis集群介绍及测试思路
声明:除非特别标注,否则均为本站原创文章,转载时请以链接形式注明文章出处。如若本站内容侵犯了原著者的合法权益,可联系本站删除。



