Database

redis

Redis

Redis"

Redis

Redis Cluster

redis cluster的实现原理主要就是三点:分片Gossip协议去中心化

数据怎么存?

redis cluster**引入了哈希槽(Hash Slot)**的概念。

它把整个数据集群划分为 16384 个槽。集群里的每个主节点负责维护一部分槽。当我们要存一个 Key 时,Redis 会先对 Key 算一个 CRC16 值,然后对 16384 取模,算出它属于哪个槽,最后把数据存到负责这个槽的节点上。这样做的好处是,扩容缩容非常方便。如果我要加一个新节点,只需要把其他节点身上的一部分槽“过户”给新节点就行了,不需要像传统哈希那样重新计算所有数据的位置。

需要注意的是在redis cluster中每个slot中可以包含很多个key,每个slot只会归属给一个master结点。所以结构是:

slot 0      -> key1, key2, key3, ...
slot 1      -> keyA, keyB, ...
...
slot 16383  -> ...

同时slot也是redis cluster中迁移和负载均衡的最小单位,扩容/缩容都是操作的slot


节点直接如何联系?

集群里的节点是去中心化的,没有所谓的“中心大脑”。它们之间通过 Gossip 协议(八卦协议)互相通信。每个节点都会定期随机选几个邻居,交换彼此的状态信息(比如“我负责哪些槽”、“我是否还活着”)。这样只需要很短的时间,集群的所有节点就能达成一致,知道整个集群的拓扑结构。


客户端如何找数据?

客户端可以连接集群的任意一个主节点。

  • 如果客户端要找的 Key 刚好在这个节点上,那就直接返回
  • 如果不在,这个节点不会充当代理去转发请求,而是会返回一个 MOVED 错误,告诉客户端:“这个 Key 不归我管,你去连 IP:Port 这个节点吧。”然后客户端会重定向到对应Port的结点回去数据

**详细过程:**如果客户端连接的是集群的 Node1,但需要访问存储在 Node3 的键 user:1001,查询过程如下:

查询过程

  1. 计算哈希槽:客户端使用 CRC16 算法计算 user:1001 的哈希值(假设为 12345)。计算哈希槽:12345 % 16384 = 12345。
  2. 查询请求:因为客户端连接的是集群中的 node1,所以客户端发送查询命令 GET user:1001 到 Node1。
  3. Node1 响应:Node1 检测到请求的键 user:1001 属于 Node3,返回一个 MOVED 错误,指示客户端请求的键在另一个节点上。MOVED 错误会中返回目标节点的信息(例如,Node3 的 IP 和端口)
  4. 重新连接:客户端根据返回的目标节点信息,建立与 Node3 的连接。
  5. 再次发送查询请求:客户端向 Node3 发送 GET user:1001
  6. 获取结果:Node3 查询到 user:1001 的值(假设为 {"name": "面试鸭", "age": 18}),并返回结果。

Moved和ASK

MOVEDASKRedis Cluster 里两种“重定向机制”,本质都是:

客户端访问错节点了,服务端告诉你该去哪。

正常来说,像Jedis,Lettuce,Redisson这类Redis客户端在启动时会拉取一份slots的映射关系,执行类似CLUSTER SLOTS , CLUSTER NODES的命令,拿到16384个slot -> redis节点的完整映射表,并缓存起来

查询的时候客户端就会通过CRC16(key) % 16384计算出key对应的slot,然后查缓存,看当前slot映射在哪个节点上。再把请求发给对应的redis节点。

但是Redis Cluster支持动态扩容/缩容,导致slot的映射关系并非一成不变。

当映射关系发生变化时再查询时,就会发生两种情况:

  • 当前slot已经完全不在这个节点上了,节点会返回一个MOVED重定向指令,告诉客户端应该去查哪个节点,即MOVED slot ip:port,由于slot已经不在当前节点了,因此客户端要更新本地缓存,以后直接访问新节点
  • 当前slot正在迁移,部分key已经搬走,部分key还没搬。如果当前查询的key还在这个节点中,直接返回。如果当前key已经迁移了,会返回一个ASK重定向指令ASK slot ip:port,直接去新节点查,不用更新缓存

对于ASK,若节点返回如ASK 1000 192.168.1.2:6379时,客户端并不是直接查,而是先发一个ASKING,再查

发ASKING的目的在于:目标节点默认不接受“非自己负责的 slot 请求”,因为当前slot还只是在迁移,目标节点理论上还没正式接管这个槽。这个ASKING就是在告诉他“我知道你还没完全接管这个 slot,但这次让我读一下。”

为什么ASK不需要更新缓存?

因为ASK代表当前slot正在迁移到另一个节点中,slot中部分key完成迁移,部分key仍在当前slot中,仍应该交给当前节点管理。如果此时更新了slot,那么会导致还未迁移过来的这部分key直接查不到了。

并且当前slot理论上还不归目标节点管理,你直接查询,节点会拒绝。


集群里的主从是干嘛的?

redis cluster里的主节点负责处理读写请求,每个主节点负责一部分的slot。而从节点这是只复制对应主结点的数据,主要的作用是故障转移和高可用。

当某个master发生故障的时候,Cluster节点通过Gossip协议发现后达到半数以上master投票,该master下的某个slave自动升级为master,继续负责原来的slot

当然slave节点通过客户端的配置,可以让slave处理读请求,减少读多写少场景下master的压力,提升qps。但可能会读到旧数据。


集群下的自愈功能是如何实施的?

  1. 首先在Redis Cluster中所有节点基于Gossip协议,会周期性地相互发送PING,收到后回复PONG(master和master,master和slave)
  2. 如果某个节点在cluster-node-timeout内多次PING未收到对应的PONG,那么当前节点会被认为是主观下线
  3. 当一个master发现有半数以上的master都将同一个master标记未PFAIL的时候,那么该master就会被认定为客观下线
  4. 一旦被标记为FAIL(客观下线),那么他负责的slot就会不可用,他的slave开始准备晋升
  5. slave只有满足一下条件才有可能被选举为新master
    • replication offset 足够新(数据不落后太多)
    • 与原 Master 断开时间未超过阈值
    • 自身状态是 connected
    • 没有被标记 FAIL
  6. 每一个合格的slave都会“自荐”,即向所有 Master 发送 FAILOVER_AUTH_REQUEST
  7. 然后master开始投票,每个master在一次故障转移中只能投一票,会优先投给replication offset 最大的 Slave以及请求最早到达的。当某个节点获得半数以上master的投票则晋升为master
  8. 晋升为master的slave会接管原master的slot

没有出现获得超过半数的 master 节点投票 的slave不能晋升。防止出现“脑裂”。会在一段时间后重试。如果多次重试仍没有,就需要人工介入


如何理解Cluster的去中心化?

Cluster的去中心化指的是:没有统一负责调度/管理的中心节点,也就是:

  • 每个节点地位是平等的
  • 任何节点都知道集群拓扑和 slot 分布
  • 没有 Sentinel、没有独立的调度中心

master和slave的观念,主要是用于master的故障转移以及高可用,防止master宕机后数据直接不可用,slot直接丢失,集群不可恢复


集群下slave节点是如何同步master的数据的?

第一次同步(全量复制)

当一个 Slave 第一次连上 Master,或者复制中断太久时:

流程是:

  1. Slave 发送 PSYNC ? -1
  2. Master 判断无法部分复制
  3. Master fork 子进程
  4. 子进程生成 RDB 快照
  5. RDB 通过网络发送给 Slave
  6. Slave 加载 RDB 到内存
  7. 同时:
    • Master 把新的写命令缓存到 replication buffer
  8. RDB 传完后:
    • Master 把这段时间的写命令流发给 Slave
  9. Slave 重放命令,追平数据

核心:RDB + 命令传播

正常运行(增量复制)

之后进入稳定阶段:

  • Master 每次写命令
  • 直接通过复制连接
  • 实时发给 Slave
  • Slave 顺序执行

这一步 既不是 RDB,也不是 AOF 文件


为什么是16384个slot?

首先redis cluster采用的是Gossip协议,每个节点需要维护一份slot -> node的映射表

如果slot过大,那么每次 gossip 同步要传大量 slot 信息,使得心跳包变大,网络 IO 暴涨

并且slot信息是用的bitmap存的,1 个 bit 表示 1 个 slot。1代表slot属于我,0代表不属于我。

那么16384 个 slot = 16384 个 bit,按照bit -> byte -> kb的换算就是16384 / 8 / 1024得到2kb。那么这个大小在频繁的gossip下就很舒服,不吃带宽。并且redis的作者也表示:Slot 太多会导致 gossip 协议中的消息变大,Slot 太少又会导致迁移粒度过粗,16384 在实践中是一个非常合理的平衡点


Gossip 协议

Gossip 协议是一种非常像“八卦传播”一样的分布式系统通信协议。

它的核心思想就是:像人传闲话一样,随机找几个节点聊聊天,把消息扩散出去,最后整个集群都会知道这个消息。

想象一下一个村子里有人发现了八卦:

  • 他不会挨家挨户去通知所有人(那样太慢太累)。
  • 他只告诉了3个朋友。
  • 这3个朋友又各自告诉另外3个朋友……
  • 没几轮,整个村子就都知道了。

这就是 Gossip 协议的精髓。

基本原理(三种常见模式)

1)Push 模式(主动推送) 节点 A 知道了新消息,就随机挑几个节点,把消息直接推过去。被推的节点再随机推给其他人。

像病毒一样主动传播。

2)Pull 模式(主动拉取) 节点定期互相打招呼:“喂,你最近有什么新八卦吗?”如果对方有自己没有的消息,就拉过来。

像大家定期聚会交换小道消息。

3)Push+Pull 混合模式(最常用)

既主动推,也定期拉,传播速度最快,也最可靠。

Gossip 协议的优缺点

方面Gossip 协议的表现说明与实际影响
去中心化★★★★★ 完全无中心无单点故障,所有节点对等
容错能力★★★★★ 极强节点批量宕机、网络分区、抖动都不影响最终收敛,网络恢复后自动愈合
可扩展性★★★★★ 几乎线性扩展从 10 台到 10 000 台都不需要改架构,几千节点集群通常 3–10 秒全网同步
实现与维护难度★★★★★ 极其简单核心代码通常几十行,容易理解、调试和修改
负载均衡★★★★★ 天然均衡每个节点发收消息量基本一致,不会出现热点
传播延迟★★★☆☆ 中等(最终一致性)几秒到几十秒收敛,不适合毫秒级强一致性场景
消息冗余/带宽消耗★★☆☆☆ 较高同一条更新会被多次传播,带宽利用率不是最优(现代 10G+ 网络基本无压力)
一致性强度★★☆☆☆ 只能保证最终一致性不提供强一致性保证,不能用于需要顺序性或即时性的关键操作(如扣款、锁)
故障检测速度★★★★☆ 较快通常几秒就能检测到节点失联,比传统中心化心跳更快更准
适用场景匹配度集群成员管理、故障检测、服务发现、配置广播、元数据同步:★★★★★ 金融交易、分布式锁、顺序操作:★☆☆☆☆几乎是“集群八卦与状态同步”这一类场景的首选方案

Redis Sentinel

简介Redis 的哨兵机制(Sentinel) 是一种高可用性解决方案,用于监控 Redis 主从集群,自动完成主从切换,以实现故障自动恢复和通知。

主要功能包括:

  • 监控:哨兵不断监控 Redis 主节点和从节点的运行状态,定期发送 PING 请求检查节点是否正常。
  • 自动故障转移:当主节点发生故障时,哨兵会选举一个从节点提升为新的主节点,并通知客户端更新主节点的地址,从而实现高可用。
  • 通知:哨兵可以向系统管理员或其他服务发送通知,以便快速处理 Redis 实例的状态变化。

哨兵机制的由来

主从架构中,如果采用读写分离的模式,即主节点负责写请求,从节点负责读请求。假设这个时候主节点宕机了,没有新的主节点顶替上来的话,就会出现很长一段时间写请求没响应的情况。

针对这个情况,便出现了哨兵这个机制。它主要进行监控作用,如果主节点挂了,将从节点切换成主节点,从而最大限度地减少停机时间和数据丢失。

  • 哨兵节点(Sentinel): 主要作用是对 Redis 的主从服务节点进行监控,当主节点发生故障的时候,哨兵节点会选择一个合适的从节点升级为主节点,并通知其他从节点和客户端进行更新操作。
  • Redis 节点:主要包括 master 以及 slave 节点,就是 Redis 提供服务的实例。

一般哨兵需要集群部署,至少三台哨兵组成哨兵集群。


主观下线/客观下线

哨兵是如何判断 Redis 中主节点挂了的呢?主要涉及到了两个机制:主观下线以及客观下线

1)主观下线

Sentinel 每隔 1s 会发送 ping 命令给所有的节点。如果 Sentinel 在down-after-milliseconds时间内还未收到对应节点的 pong 回复,就会认为这个节点主观下线SDOWN(Subjectively Down)

2)客观下线

注意,只有主节点才有客观下线,从节点没有

假设目前有个主节点被一个 sentinel 的判断主观下线了,但可能主节点并没问题,只是因为网络抖动导致了一台哨兵的误判。所以此时哨兵需要问问它的队友,来确定这个主节点是不是真的出了问题!

Sentinel会通过 __sentinel__:hello 频道,定期广播:自己的存在和自己看到的Master状态

当某个Sentinel发现有 达到 quorum 数量的 Sentinel都认为同一个 Master 是 SDOWN时那么该master则会被标记为:ODOWN(Objectively Down)

认为下线的总投票数大于 quorum(一般为集群总数/2 + 1,假设哨兵集群有 3 台实例,那么 3 / 2 + 1 = 2),则判定该主节点客观下线,此时就需要进行主从切换,而只有哨兵的 leader 才能操作主从切换


Sentinel leader 是如何选举出来的?

Sentinel leader 节点的选举实际上涉及到分布式算法 raft,这里主要简单说一下哨兵集群选择 leader 的方式:

判断主节点主观下线的 sentinel 就是候选者,此时它想成为 leader。如果同时有两个 sentinel 判断主观下线,那么它们都是候选人,一起竞争成为 leader。

候选者们会先投自己一票,然后向其他 sentinel 发送命令让它们给自己投票。每个哨兵手里只有一票,投了一个之后就不能投别人了

最后,如果某个候选者拿到哨兵集群半数及以上的赞成票,就会成为 leader。这里有一个注意的点,为了保证 sentinel 选举的时候尽量避免出现平票的情况,sentinel 的节点个数一般都会是奇数,比如 3,5,7 这样。


Redis 主节点选举

选出哨兵 leader 之后,需要选出 Redis 主从集群中的新 master 节点。

首先需要把一些已经下线的节点全部剔除,然后从正常的从节点中选择主节点,其主要经过以下三个流程:

  1. 根据从节点的优先级进行选择,优先选择优先级的值比较小的节点(优先级的值越小优先级越高,优先级可通过 slave-priority 配置)。
  2. 如果节点的优先级相同,则查看进行主从复制的 offset 的值,即复制的偏移量,偏移量越大则表示其同步的数据越多,优先级越高。
  3. 如果 offset 也相同了,那只能比较 ID 号,选择 ID 号比较小的那个作为主节点(每个实例 ID 不同)。

选好主节点之后,哨兵 leader 会让其他从节点全部成为新 master 节点的 slave 节点。

最后利用 redis 的发布/订阅机制,把新主节点的 IP 和端口信息推送给客户端,此时主从切换就结束了。

旧主节点恢复了怎么办?实际上哨兵会继续监视旧的主节点,如果它上线了,哨兵集群会向它发送 slaveof 命令,让它成为新主节点的从节点。


Redis Cluster和Redis Sentinel的区别

1)Redis Cluster 是 Redis 集群,提供自动分片功能,将数据自动分布在多个节点上,支持自动故障转移。如果一个节点失败,集群会自动重新配置和平衡,不需要外部介入,因为它内置了哨兵逻辑

2)Sentinel 是哨兵,主要用于管理多个 Redis 服务器实例来提高数据的高可用性。当主节点宕机,哨兵会将从节点提升为主节点,它并不提供数据分片功能。

如果需要处理大量数据并进行数据分片,应选择 Redis Cluster,它支持水平扩展,适用于大规模数据、高吞吐量场景

如果只是为了提高 Redis 实例的可用性,并不需要数据分片,应选择主从 + Sentinel,它主要关注故障转移和实例高可用适用于高可用性、读写分离场景。


脑裂

脑裂是指在分布式系统中,由于网络分区或其他问题导致系统中的多个节点(特别是主节点)误以为自己是唯一的主节点。这种情况会导致多个主节点同时提供写入服务,从而引起数据不一致。

分布式系统就像一个团队在干活,如果发生了脑裂,就好比这个团队突然因为某些原因,比如通信出了问题,分成了几个小团体。

每个小团体都以为自己是整个团队,都在按自己的方式工作,各自为政,对同一件事有不同的决策和做法,就像有的说要这么干,有的说要那么干。

这样一来,整个系统就乱套了,数据也可能变得不一致,服务也变得不正常了,这就是分布式系统中的脑裂。

导致脑裂出现原因主要是网络分区

redis里的脑裂主要指的是Sentinel模式下,在网络分区时,Sentinel联系不上master,然后发起选举,选了新的主节点,此时 Redis 就出现了两个主节点。此时客户端写哪都会导致数据不一致

为什么Redis Cluster下不容易发生脑裂呢?

  1. slot只会被一个master合法持有
  2. 少数派 Master 会自动拒绝写请求,当一个 Master发现自己联系不到多数 Master,会进入cluster_state: fail,直接拒绝写。
  3. Cluster 的“自我熔断”机制,所以即使发生网络分区,只有多数派那一侧还能写,少数派 Master 不会对外写,少数派 = 自动降级为只读

post.comments