3.RedisCluster读写分离
- 默认情况下RedisCluster读写都是通过主节点进行处理,不支持从节点读写。RedisCluster的核心理念是从节点是作为热备以及主节点宕机时从节点进行故障转移,从而实现高可用
- Redis之所以需要读写分离,是为了横向扩展从节点去达到更高的读并发。而在RedisCluster中,主节点本身就可以任意横向扩展,如果想要更高的读写并发,那么对主节点进行横向扩展即可
- 想要在RedisCluster中实现读写分离代价是比较高的,比如可以修改JedisClient源码,或是使用第三方RedisProxy工具
4.RedisCluster通信协议
4.1.Gossip通信协议概述
- HashSlot算法解决的是数据读写问题,那么当集群节点数量发生变化、Slot迁移、主从切换等这些操作的信息需要同步给所有主节点,Gossip协议就是用来维护集群同步状态
- RedisCluster是完全去中心化的,也就是没有一个统一的管理中心去通知所有节点进行同步,每个节点都是一个中心,RedisCluster采取的方案是使用Gossip协议来进行通信,节点之间通讯的目的是为了维护节点之间的元数据信息
- 节点信息:每个节点的ID、IP地址和端口号等信息
- 描述集群拓扑的槽位分配:槽位的范围以及哪些节点负责存储哪些槽位
- 故障转移相关信息:哪个节点是主节点,哪个节点是从节点,如果主节点失效时从节点将如何晋升为主节点等信息
- 其他集群相关的配置信息,如集群名称、是否开启了动态更新等
- Gossip:中文名称是流言协议,在此协议中包含了多种消息类型,即ping、pong、meet、fail等。原理就是节点之间不断的进行通信交换信息,一段时间后所有节点就都有了整个集群的完整信息,最终所有节点的状态都会达成一致。Gossip中除了fail消息是立即全网感知,其他消息都具有一定延迟,所以是最终一致性
- 节点之间通信流程
- 新节点向集群中的任意一个节点发送 meet 命令,并将自己的 IP 和端口号信息附在命令中
- 收到 meet 命令的节点会将新节点的信息记录下来,并向新节点返回 pong 命令,表示已经收到了新节点的请求
- 新节点收到 pong 命令后,将收到 pong 命令的节点加入到自身的节点列表中,并向此节点发送 ping 命令,等待其返回 pong 命令
- 当新节点收到足够数量的 pong 命令后,就会将自己加入到集群中,并向其他节点发送自己的拓扑结构信息。这个拓扑结构信息包括节点自身的信息以及其他节点的信息,以及每个节点的 hash slot 分配信息等
- 集群中的其他节点收到新节点的拓扑结构信息后,会将其添加到自己的节点列表中,并向新节点返回 pong 命令
- 新节点收到其他节点的 pong 命令后,就可以和其他节点进行通信,并开始接收和处理请求了。
- 每个Redis节点会开放两个端口号,一个是自身的运行端口号,另外一个就是运行端口号+10000,此端口号用于节点之间通信
- 节点之间建立TCP通道:每个节点都会与集群中的其他节点建立TCP连接,用于节点之间的通信。使用ping/pong消息保持节点之间的心跳连接
- 节点之间进行握手:每个节点在连接到其他节点后,在固定的时间间隔内,会随机选择的若干个节点发送ping消息,通知对方自己还在运行,并请求对方发送关于自己和其他节点的信息。如果节点长时间没有响应,则被认为已经失效,会向其他节点广播 Fail 消息,以便其他节点更新该节点的状态信息
- 节点响应:接收到ping消息的节点,会向发送ping消息的节点响应PONG命令,进行版本号和槽分配等元数据信息的交换,以确保集群中的所有节点拥有相同的元数据信息。并更新自己的局部视图
- 节点之间进行数据同步:当某个节点的主节点进行数据修改时,会将修改信息发送给所有从节点。从节点收到信息后,会进行数据同步,确保数据的一致性
- 节点之间进行故障转移:每个节点都会定期向其他节点发送PING命令,以确认对方是否存活。如果一个节点在一定时间内没有响应PING命令,其他节点就会将其标记为下线节点,并进行主从切换等操作
4.2.Gossip优缺点
- Gossip优点
- 分布式高效:Gossip协议是一种去中心化的协议,节点之间相互交流信息,每个节点都可以通过传播信息来实现全局一致性,不需要中央控制节点,使得节点加入或退出集群更加高效
- 可伸缩性:Gossip协议可以很好地适应不同规模的系统,当节点数目增加时,节点间通信的成本是对数级别的
- 容错性:Gossip协议具有一定的容错能力,由于每个节点可以通过交互信息来更新状态,因此即使一部分节点失效,其他节点仍然可以更新状态,保持整个集群的一致性
- 自适应性:Gossip协议在传输信息时会根据实时情况进行调整,根据反馈信息和可靠性要求,自动选择合适的节点进行信息交流,从而提高了信息传输的效率和可靠性
- 低延迟:Gossip协议采用分散的信息传播方式,信息可以在整个网络中快速地传播,从而使得系统的响应速度更快
- Gossip缺点
- 延迟问题:由于Gossip协议的传播速度相对较慢,因此可能存在节点状态更新的延迟问题。特别是在网络拓扑结构较为复杂或节点数量较大时,这种延迟问题会更加突出
- 带宽开销:由于Gossip协议的信息需要在节点之间不断传播,因此可能会产生较大的网络带宽开销。特别是在节点数量较大时,这种开销会更加严重
- 数据一致性问题:由于Gossip协议是基于随机的节点通信机制实现的,因此可能会出现数据不一致的情况。特别是在节点状态频繁变化时,这种问题会更加明显
- 安全性问题:Gossip协议需要在节点之间传递敏感信息,因此存在安全性问题。特别是在没有适当的加密和认证机制时,这种问题会更加严重
4.3.Gossip消息分类
- ping:每个节点会按照固定时间频繁的给其他节点发送ping消息,集群节点互相通过ping交换元数据
- ping消息的发送是非常频繁的,并且需要携带元数据,所以会加重网络负担
- 每个节点没秒执行10此ping消息,每次会随机选择5个最久没有通信的其他节点去通信
- 如果发现某个节点通信延迟时间达到了 cluster_node_timeout/2,那么立即发送ping,避免数据交互延迟过长
- cluster_node_timeout值是可以调节的,如果调节的比较大, 发送ping的频率就会降低
- 发送 ping 消息时,会携带自己的信息,并随机选择 1/10 的其他节点,将它们的信息一起发送给目标节点进行交换。这样可以保证每个节点都能够获得全局信息,从而更好地协调整个集群的状态
- 每个 ping 消息至少会包含 3 个其他节点的信息,最多包含总节点数减 2 个其他节点的信息。这是为了防止某些节点信息过于集中,导致部分节点信息丢失或者信息不全。同时,也可以保证每个节点都能够获取足够的信息,从而更好地对整个集群进行管理和控制
- pong:此消息是作为meet和ping的元数据响应消息
- meet:新节点使用此消息通知任意一个集群节点,集群节点会响应pong消息邀请加入到集群
- fail:某个节点判断另外一个节点宕机之后,会广播fail消息,通知其他节点此节点宕机的消息,其他节点接收到消息后标记此节点下线,四种消息中只有fail消息是立即全网感知,其他消息都具有一定的延迟
5.RedisCluster客户端
- 当客户端需要与 Redis Cluster 进行交互时,有两种主要的方式:基于重定向的方式和智能代理的方式
5.1.基于重定向的方式
- 连接RedisCluster集群时,可以使用Redis自带的redis-cli客户端,支持重定向,他有两种方式手动重定向和自动重定向
- 这种模式下,客户端将请求发送到集群中的某个节点,如果这个节点不是数据所在的节点,那么这个节点会将请求重定向到正确的节点上。在这个过程中,客户端和 Redis 集群节点之间使用 Redis 协议进行通信
- 默认情况下,如果节点响应MOVED,那么就意味着节点槽位错误,那么需要手动重新选择。如果是使用redis-cli客户端连接,那么也可以在连接时,指定 -c 属性,那么当响应MOVED时会自动重新连接到正确槽位节点
- 这种模式下,一般而言都最少需要重定向一次,当数据分布发生变化时,可能还需要重定向多次。重定向的网络开销是比较大的,所以一般推荐使用基于智能代理的方式来连接RedisCluster
5.2.基于智能代理的方式
- 智能代理的方式是近些年来才逐渐被 Redis Cluster 推崇的交互方式,其中Jedis的扩展客户端JedisCluster就是一个不错的选择,其他的还有Redisson、Lettuce
- JedisCluster它的设计理念是为了提供更好的性能、可靠性和易用性。JedisCluster在Jedis的基础上增加了自动重定向、连接池等功能,并进行了性能优化和bug修复。同时,它也保留了Jedis的API和用法,使得用户可以很容易地迁移到Smart Jedis上来
- JedisCluster实现原理
- 在初始化时,JedisCluster客户端会获取Redis Cluster中所有主节点的信息,包括节点IP地址、端口号和节点的槽位范围等信息,并将这些信息存储在本地的节点列表中
- JedisCluster客户端在本地创建一个【槽位/节点映射表】,用来缓存集群中槽位与节点之间的映射关系。这个表的初始化过程如下:
- 遍历所有主节点,获取每个节点的槽位范围
- 遍历每个节点的槽位范围,将这些槽位与对应的节点信息存储在【槽位/节点映射表】中
- 当客户端发起请求时,JedisCluster客户端会先通过CRC16算法计算出请求对应的槽位
- JedisCluster客户端会在本地的【槽位/节点映射表】中查找对应的节点,如果能够找到,则直接访问该节点并进行读写操作,避免了根据错误节点返回的MOVE指令进行重定向的开销
- 如果发生了数据迁移导致某个请求返回MOVE指令时,客户端会根据MOVE指令中返回的新节点信息,同步更新本地的【槽位/节点映射表】,以保证后续的请求能够直接通过该表进行节点定位,提升效率
- 为了避免出现网络异常等情况导致本地的【槽位/节点映射表】过期,JedisCluster客户端会定时从Redis Cluster中获取最新的节点信息,并更新本地的节点列表和【槽位/节点映射表】
- 通过这种方式,JedisCluster客户端能够实现高效的请求路由和自动重定向,同时减少了重定向的开销
6.RedisCluster扩容|缩容
- 扩容
- 新节点向集群中的一个已知节点发送 meet 命令请求加入集群
- 已知节点向集群中其他节点发送 meet 命令,将新节点加入集群
- 新节点加入集群后,会通过 ping/pong 报文与其他节点建立心跳连接,同步集群信息,包括集群节点的数量、槽位信息等
- 当新节点成为集群的一员时,集群中的数据迁移将开始,数据迁移过程中对客户端是透明的。具体的数据迁移过程如下
- 集群会从所有原节点中选取一部分槽位的数据迁移到新节点上,具体的迁移槽位和数量取决于集群中的数据分布情况
- 在数据迁移期间,如果有客户端访问迁移的槽位,集群会返回一个 ASK 错误,告知客户端要访问的槽位已经迁移到了新节点上,同时告知客户端新节点的地址,客户端会根据返回的地址重新发起请求
- 当所有数据迁移完成后,集群会向客户端返回MOVED错误,告知客户端对应的槽位已经迁移到了新节点上
- 客户端收到MOVED错误后,会更新本地的【槽位/节点映射表】,以便后续请求可以直接定位到新节点
- 缩容
- 集群会从所有原节点中选取一部分槽位的数据迁移到其他节点上,具体的迁移槽位和数量取决于集群中的数据分布情况
- 当所有数据迁移完成后,集群会将要删除的节点标记为 FAIL 状态,并向其他节点发送 forget 命令,通知其他节点忘记该节点
- 当集群中的某个节点在 cluster-node-timeout 时间内没有收到要删除节点的任何消息时,就会将该节点从集群中删除
- ASK与PONG的区别
- ASK 消息是指当某个节点在进行槽位迁移时,如果有客户端请求的数据位于正在迁移的槽位上,该节点会返回一个 ASK 消息给客户端,告诉客户端该数据已经被迁移到了哪个节点上,并让客户端去请求正确的节点。因此,ASK 消息是在数据请求过程中被主动发送的
- PONG 消息则是在 Redis Cluster 节点间进行心跳检测时使用的。节点间通过相互发送 PING/PONG 消息来保持连接和同步信息,PONG 消息用于回复对方的 PING 消息,以保证节点之间的连接正常。因此,PONG 消息是在节点间进行通信时被动发送的
7.RedisCluster主备切换
- 判断节点宕机
- 如果一个节点认为另一个节点宕机,那么是 subjective pfail,即主观宕机
- 如果超过一半节点都认为另外一个节点宕机了,那就是fail,即客观宕机
- 在 cluster-node-timeout 时间内,一个节点一直没有返回 pong,那么就认为它是 subjective pfail
- 如果一个节点认为某个节点 subjective pfail 了,那么会在 ping 消息中发送给其他节点,如果超过半数的节点都认为该节点宕机了,那么就变成 fail
- 从节点过滤
- 对于宕机的 master 节点,从其所有的 slave 节点中,选择一个切换成 master 节点
- 检查每个 slave 节点与 master 节点断开连接的时机,如果超过了 cluster-node-timeout * cluster-slave-validity-factor,那么这个节点就没有资格切换成 master 节点,直接被过滤
- 从节点选举
- 每个从节点,根据自己对 master 节点复制数据的 offset,来设置一个选举优先级,offset 越大的从节点,选举优先级越靠前,优先进行选举
- 所有的 master 节点开始 slave 选举投票,给要进行选举的从节点进行投票,如果大部分 master 节点 (N/2 + 1) 都投票给了某个从节点,那么选举通过,那个从节点可以切换成 master 节点
- 从节点执行主备切换,从节点切换为主节点