Redis主从
主从复制原理
Redis主从复制是指将一个Redis节点(主节点,master)的数据复制到一个或多个Redis节点(从节点,slave)的过程。
初次全量同步
当一个从节点初次连接到主节点时,或者在长时间掉线之后重新连接,主从之间会进行一次全量数据同步。这个过程包括以下几个步骤:
从节点发送 psync 请求:从节点发送一个
psync
命令给主节点,请求获取主节点的 runID 和 offset。初次连接时,从节点不知道主节点的 runID,因此 runID 为?
,同时由于还没有复制任何数据,offset 为-1
。主节点响应:主节点接收到请求后,返回自身的 runID 和从节点的复制进度 offset。由于是初次同步,主节点会返回
FULLRESYNC
响应,指示从节点需要进行全量同步。生成 RDB 文件:主节点执行
bgsave
命令生成 RDB 快照文件,并将其发送给从节点。在此期间,所有客户端发来的写命令会被暂时缓存在replication_buffer
缓冲区中。从节点加载 RDB 文件:从节点接收到 RDB 文件后,会清空自身所有的数据,并加载 RDB 文件中的数据。
传输增量命令:当从节点加载完 RDB 文件后,主节点会将缓存中的增量命令发送给从节点。从节点执行这些命令后,主从数据基本保持一致。
命令传播
初次全量同步完成后,主从节点之间会建立一个长连接,用于实时同步数据。主节点在执行写命令时,会将这些命令写入 replication_buffer
缓冲区,并通过长连接发送给从节点。从节点接收到这些命令后,会立即执行以保持与主节点的数据同步。
增量同步
如果从节点短暂掉线后重新连接,且主从之间的数据差异不大,可以进行增量同步来减少数据同步的时间和资源消耗。增量同步利用了 repl_backlog_buffer
这一环形缓冲区。
环形缓冲区:主节点维护了一个固定的环形缓冲区
repl_backlog_buffer
,用来记录最近传播出去的命令。主节点和从节点各自维护一个 offset 来表示写进度和读进度。同步检查:当从节点重新连接时,会发送一个包含自身 slave_offset 的
psync
请求。主节点会计算 master_offset 和 slave_offset 之间的差值。增量同步判断:
- 如果差值大于
repl_backlog_buffer
的大小,表明主从数据差异较大,需要进行全量同步。 - 如果差值小于
repl_backlog_buffer
的大小,表明数据差异不大,可以进行增量同步。主节点将缓冲区中的差值部分写入replication_buffer
并发送给从节点。
- 如果差值大于
我们可以看出 Redis 的主从复制机制能够有效地提高数据的可用性和冗余度。初次全量同步确保了从节点与主节点的数据一致性,而后续的命令传播和增量同步则大大减少了数据同步所需的时间和资源。此外,通过调整 repl_backlog_buffer
的大小,还可以进一步优化从节点在不稳定网络环境下的表现。
主从配置
配置主从关系通常涉及以下步骤:
- 设置主节点:默认情况下,Redis实例启动时都是主节点。
- 配置从节点:在从节点的配置文件
redis.conf
中,使用slaveof <masterip> <masterport>
命令指定主节点的IP和端口。 - 启动从节点:启动从节点,它会自动连接到主节点并开始复制数据。
故障转移机制可以通过以下方式配置:
- 哨兵模式:部署哨兵节点来监控主从节点的状态。当主节点发生故障时,哨兵会自动选举一个新的主节点,并通知其他从节点重新配置。
- 集群模式:在Redis集群中,主从复制结合了哨兵模式的功能,可以自动处理节点的故障转移。
Redis哨兵
哨兵的作用
Redis哨兵(Sentinel)是一个分布式系统,用于监控Redis实例的状态,并在主节点发生故障时自动进行故障转移。哨兵的主要作用包括:
- 状态监控:哨兵会定期向Redis实例发送心跳包,以监控它们的状态。
- 故障转移:当主节点无法正常工作时,哨兵会自动选举一个从节点作为新的主节点,并重新配置其他从节点。
- 通知:哨兵会通知客户端关于主节点的变更,以便客户端可以及时更新其连接信息。
哨兵的工作原理基于心跳机制和投票协议。哨兵之间通过Redis的发布/订阅机制相互发现和通信。
哨兵的配置
每个哨兵节点在启动时都会读取一个配置文件(通常是sentinel.conf
),这个文件中包含了以下关键配置:
监控的主节点:通过
sentinel monitor
命令指定哨兵要监控的主节点。例如:confsentinel monitor mymaster 127.0.0.1 6379 2
这行配置表示哨兵监控名为
mymaster
的主节点,其地址为127.0.0.1
,端口为6379
,并且需要至少2个哨兵节点同意才能将主节点标记为下线。其他配置:哨兵配置文件中还可以包含其他参数,如:
sentinel down-after-milliseconds
:指定哨兵认为主节点下线所需的时间。sentinel parallel-syncs
:在故障转移期间,指定可以同时对新的主节点进行同步的从节点数量。sentinel failover-timeout
:指定故障转移过程的超时时间。
自动发现
哨兵节点启动后,会自动发现主节点的从节点以及其他哨兵节点。哨兵之间的自动发现机制主要通过以下步骤完成:
1. 初始化连接
当哨兵节点启动时,它会根据配置文件中提供的信息连接到指定的主节点。哨兵会向主节点发送INFO
命令,获取主节点的详细信息,包括从节点的列表和其他哨兵节点的信息。
2. 订阅频道
哨兵节点会订阅主节点的__sentinel__:hello
频道。这个频道是哨兵之间通信的桥梁。每个哨兵节点都会定期在这个频道上发布一条消息,包含自己的IP地址、端口号以及它所监控的主节点的信息。
3. 发现其他哨兵
当哨兵节点订阅了__sentinel__:hello
频道后,它就可以接收到其他哨兵发布的消息。通过这些消息,哨兵节点可以自动发现同一主从复制集群中的其他哨兵节点。这样,哨兵节点就能够形成一个网络,相互之间可以进行通信和协调。
4. 更新配置
哨兵节点会根据从__sentinel__:hello
频道收到的信息更新自己的配置。如果发现了新的哨兵节点或从节点,哨兵会将其添加到自己的配置中。同样,如果某些节点不再发布消息,哨兵可能会将其从配置中移除。
5. 持续监控
哨兵节点会持续监控从__sentinel__:hello
频道收到的消息,以保持对整个集群状态的最新了解。如果主节点的状态发生变化(例如,一个新的从节点被添加),哨兵会相应地更新自己的配置。
故障转移
故障转移是哨兵集群的核心功能之一,它确保了在主节点发生故障时,系统能够自动切换到健康的从节点,从而继续提供服务。
故障检测
故障转移过程始于故障检测。哨兵节点通过以下方式检测故障:
- 定期PING:哨兵节点定期向主节点和从节点发送PING命令。
- 主观下线:如果一个哨兵节点在指定时间内没有从主节点收到有效回复(通过
sentinel down-after-milliseconds
配置),它会将主节点标记为主观下线,并向其他哨兵节点发送sentinel is-master-down-by-addr
命令询问是否也认为该主节点下线。 - 客观下线:当足够数量的哨兵节点(配置文件中指定的
quorum
数量)都认为主节点主观下线时,主节点会被标记为客观下线。
选举领头哨兵
一旦主节点被标记为客观下线,哨兵节点之间会进行选举,选出一个领头哨兵(Leader Sentinel)来执行故障转移。选举过程如下:
- 发起选举:每个哨兵节点都可以尝试成为领头哨兵。
- 选举规则:Redis 基于 Raft 分布式共识算法来实现领头哨兵的选举。通常,哨兵节点会尝试在指定时间内获得其他哨兵节点的多数投票。如果没有哨兵获得多数票,将重新开始选举。
- 选举成功:一旦某个哨兵节点获得多数投票,它将成为领头哨兵。
故障转移执行
领头哨兵负责执行以下故障转移步骤:
- 选择新主节点:领头哨兵会根据以下标准选择一个从节点作为新主节点:
- 最少同步延迟。
- 最新复制偏移量。
- 节点的优先级(如果配置了
slave-priority
)。
- 提升从节点:领头哨兵向被选中的从节点发送
SLAVEOF NO ONE
命令,将其提升为主节点。 - 重新配置从节点:领头哨兵会向其他从节点发送
SLAVEOF
命令,使它们成为新主节点的从节点。 - 通知客户端:领头哨兵会更新配置信息,并通过发布/订阅消息通知其他哨兵节点和客户端新的主节点地址。
- 更新配置:领头哨兵会将新的主节点信息写入所有哨兵节点的配置文件,以持久化配置。
故障转移后的操作
故障转移完成后,以下操作可能会被执行:
- 旧主节点恢复:如果旧的主节点重新上线,它将被配置为从节点,并加入到新的主节点的复制链中。
- 监控恢复:哨兵节点会继续监控新的主节点和从节点,确保集群的稳定运行。
集群脑裂
在Redis集群中,脑裂问题是一个需要特别注意的挑战。当主节点发生故障,如果没有适当的措施,可能会导致数据不一致。
脑裂现象的成因
脑裂现象通常发生在以下情况下:
- 主节点故障:主节点由于某些原因停止服务。
- 客户端连接:客户端未能及时接收到哨兵节点关于新主节点的通知,继续向旧主节点发送写请求。
- 旧主节点复活:旧的主节点重新上线,并继续接受写操作,与新的主节点形成两个独立的服务端点。 这种情况下,集群中的数据可能会出现不一致。
防止脑裂的配置策略
为了防止脑裂,Redis Sentinel提供了两个关键的配置参数:
1. min-replicas-to-write
功能:确保主节点在进行写操作时有足够的从节点支持。
配置:在
sentinel.conf
中设置min-replicas-to-write
的值。示例:
confmin-replicas-to-write 1
作用:这个参数确保主节点必须有至少指定数量的可用从节点才能接收写操作。如果从节点的数量低于这个阈值,主节点将拒绝写操作,这有助于防止在主节点故障后,旧的主节点在没有足够从节点支持的情况下继续接收写操作,从而导致数据不一致。
2. min-replicas-max-lag
功能:限制主节点与从节点之间的最大复制延迟。
配置:在
sentinel.conf
中设置min-replicas-max-lag
的值。示例:
confmin-replicas-max-lag 10
作用:这个参数设置了主节点与其从节点之间数据复制的最大延迟时间。如果主节点与任何一个从节点之间的复制延迟超过了这个阈值,主节点将拒绝写操作。这有助于确保主节点的数据是最新的,并且在主节点故障后,旧的主节点不会接收写操作,避免数据不一致。
这些配置如何协同工作
- 当主节点发生故障,哨兵节点会自动进行故障转移,选择一个新的主节点。
min-replicas-to-write
和min-replicas-max-lag
配置确保了在主节点故障转移期间,旧的主节点不会接受写操作,从而防止了数据不一致。- 客户端通过哨兵节点获得新主节点的信息,并重新连接到新的主节点。
- 提高
min-replicas-to-write
的值会增加部署成本,而降低min-replicas-max-lag
的值可能需要更稳定的网络连接。
Redis分片集群
Redis Cluster是Redis提供的一种分布式数据库解决方案,它允许数据自动分片到多个Redis节点上,从而提供高可用性、扩展性和容错性。
数据分片
Redis Cluster采用哈希槽(hash slot)的概念来进行数据分片。整个Redis Cluster共有16384(
哈希槽(Hash Slot)
在Redis Cluster中,哈希槽是数据分片的基本单位。Redis Cluster预定义了16384个哈希槽,这是一个固定的数字,从0到16383。
键与哈希槽的映射
每个Redis键通过一个哈希函数映射到一个哈希槽。Redis Cluster使用CRC16算法来计算键的哈希值,然后将这个哈希值映射到一个哈希槽上。具体映射方法是:
HASH_SLOT = CRC16(key) mod 16384
其中,CRC16(key)
是键key
的CRC16校验和,mod
是取模运算。
节点与哈希槽的关联
在Redis Cluster中,每个节点负责一部分哈希槽。例如,如果有三个节点,则可能每个节点分别负责以下哈希槽:
- 节点A:0-5460
- 节点B:5461-10922
- 节点C:10923-16383
这些范围可以在集群配置时指定,也可以在集群运行时通过重新分片(resharding)来调整。
数据写入和读取
当客户端向Redis Cluster发送写入或读取请求时,以下步骤会发生:
客户端根据键计算哈希槽。
客户端将请求发送到负责该哈希槽的节点。
如果请求发送到了错误的节点,该节点会返回一个MOVED错误,并告知正确的节点地址。客户端需要根据这个信息更新其内部的路由表,并将请求重定向到正确的节点。
MOVED命令的格式如下:
MOVED <slot> <ip>:<port>
重新分片(Resharding)
Redis Cluster支持在运行时重新分配哈希槽,以实现动态扩容或缩容。重新分片的过程涉及以下步骤:
- 选择源节点和目标节点。
- 选择要移动的哈希槽。
- 将选定的哈希槽中的数据从源节点迁移到目标节点。
- 更新集群中所有节点的配置信息,以反映新的哈希槽分配。
1. 选择源节点和目标节点
在进行重新分片之前,首先需要确定哪些节点是源节点(拥有要移动的哈希槽的节点)和目标节点(将要接收这些哈希槽的节点)。通常,重新分片操作是为了实现以下目的:
- 扩容:向集群中添加新节点,并将现有节点的哈希槽移动到新节点上。
- 缩容:从集群中移除节点,并将该节点的哈希槽分配给其他节点。
- 负载均衡:调整哈希槽的分配,以更均匀地分布数据和请求负载。
2. 选择要移动的哈希槽
确定源节点和目标节点后,接下来需要选择要移动的哈希槽。可以移动单个哈希槽,也可以移动连续范围的多个哈希槽。选择哪些哈希槽进行移动取决于具体的需求,例如:
- 哈希槽中的数据大小
- 节点的当前负载
- 集群的整体平衡
3. 将选定的哈希槽中的数据从源节点迁移到目标节点
这一步是重新分片过程的核心。
- 迁移准备:在源节点上,对要迁移的哈希槽中的所有键进行迭代,记录下这些键的值。
- 迁移数据:将记录下的键值对迁移到目标节点。这个过程可以通过以下两种方式之一进行:
- MIGRATE命令:使用Redis的MIGRATE命令可以将单个键原子地从源节点迁移到目标节点。
- 手动迁移:也可以手动将键值对复制到目标节点,但这种方法不是原子操作,可能会在迁移过程中遇到问题。
- 迁移确认:迁移完成后,需要确认目标节点已经接收了所有键值对,并且数据是一致的。
4. 更新集群中所有节点的配置信息
一旦数据迁移完成,需要更新集群中所有节点的配置信息,以反映新的哈希槽分配。
- 更新槽映射:修改集群配置,将移动的哈希槽从源节点映射到目标节点。
- 传播配置:通过集群的Gossip协议,将新的配置信息传播到集群中的所有节点。每个节点都会更新其内部的槽映射表。
- 配置确认:确保所有节点都已经更新了配置信息,并且可以正确地处理针对新哈希槽的请求。
节点角色
在Redis Cluster中,节点扮演不同的角色以实现高可用性和数据冗余。
主节点(Master)
主节点是Redis Cluster中的核心角色,其主要职责包括:
- 处理读写请求:主节点接收来自客户端的读写请求,并直接处理这些请求。这是Redis Cluster中唯一能够处理写操作的节点类型。
- 存储数据:主节点负责存储数据。每个主节点持有一定数量的哈希槽,这些哈希槽中的数据都存储在该节点上。
- 维护集群状态:主节点参与集群状态的维护,包括故障检测、配置更新等。
从节点(Slave)
从节点是主节点的副本,其主要职责包括:
- 复制数据:从节点会从其对应的主节点复制数据。这是通过Redis的复制功能实现的,从节点会持续同步主节点的数据变更。
- 故障转移:当主节点出现故障时,从节点可以升级成为新的主节点。这个过程称为故障转移(failover)。故障转移通常由Redis Cluster的故障检测和恢复机制自动处理。
- 读操作负载均衡:在配置了读写分离的情况下,从节点可以分担主节点的读请求负载,从而提高集群的整体性能。
以下是关于从节点的更多细节:
- 主从关系:一个主节点可以有多个从节点,而从节点只能有一个主节点。
- 选举机制:在主节点故障时,从节点之间会进行选举,选出新的主节点。选举通常基于复制偏移量(replication offset)和节点ID。
- 手动和自动故障转移:故障转移可以是手动的,也可以是自动的。在自动故障转移模式下,集群能够自动检测到主节点的故障,并触发从节点的选举。
代理节点(Proxy)
代理节点是一个可选的角色,不是Redis Cluster的内置功能,但它可以用于以下目的:
- 请求转发:代理节点接收客户端的请求,然后将请求转发到正确的Redis Cluster节点。这可以简化客户端的逻辑,因为客户端不需要直接处理重定向和哈希槽映射。
- 故障屏蔽:代理节点可以隐藏后端节点的故障,提供更稳定的客户端接口。
- 安全性:代理节点可以作为安全层,实现访问控制、加密通信等功能。 以下是关于代理节点的更多细节:
- 实现方式:代理节点可以是专门的软件,如Twemproxy或Redis Sentinel,也可以是自定义的应用程序逻辑。
- 透明性:对于客户端来说,代理节点通常是透明的。客户端认为它们直接与Redis Cluster交互,而实际上是通过代理节点进行的。
- 性能考虑:使用代理节点可能会引入额外的延迟,因此在性能敏感的应用中需要谨慎考虑。
通信机制
Redis Cluster 使用 Gossip 协议来实现节点之间的通信和协调。
Gossip 协议是一种在分布式系统中非常常见的通信协议,它主要用于节点之间的消息传播和状态同步。
Gossip 协议概述
Gossip 协议是一种去中心化的通信机制,它通过节点之间的随机通信来传播信息。这种机制在大型分布式系统中特别有用,因为它能够减少通信开销并且具有较好的容错能力。
Gossip 消息类型
Redis Cluster 使用以下几种 Gossip 消息类型来进行节点间的通信:
MEET 消息:
- 用途:当一个新节点加入集群时,它会发送 MEET 消息给集群中的其他节点,以便这些节点可以建立连接并开始通信。
- 流程:新节点向已知的任意一个节点发送 MEET 消息,接收节点会更新自己的集群状态,并将新节点的信息转发给其他节点。
- 目的:使所有节点了解新节点的存在,并建立起连接。
PING 消息:
- 用途:PING 消息用于节点间的心跳检测,以确认其他节点是否仍然活跃。
- 流程:每个节点定期向其他节点发送 PING 消息,接收方会回复一个 PONG 消息。
- 目的:监测节点的存活状态,保持集群内节点间的同步。
PONG 消息:
- 用途:作为对 PING 消息的响应。
- 流程:当节点收到 PING 消息时,会回复一个 PONG 消息。
- 目的:确认节点的存活状态,帮助发送方节点维持节点列表的最新状态。
FAIL 消息:
- 用途:报告某个节点已经失效。
- 流程:当一个节点认为另一个节点已经失效时,它会向集群中的其他节点发送 FAIL 消息。
- 目的:通知集群中其他节点某节点的状态,触发故障恢复过程。
INFO 消息:
- 用途:INFO 消息包含了节点的信息,如节点 ID、负责的哈希槽范围、从节点列表等。
- 流程:节点会周期性地广播 INFO 消息,其他节点通过接收 INFO 消息来更新自己的集群状态。
- 目的:确保集群中所有节点都拥有最新的集群状态信息。
通信机制细节
- Gossip 间隔:默认情况下,Redis Cluster 中的节点每秒会向集群中的其他节点发送一次 Gossip 消息(包括 PING 和 INFO)。
- 节点超时:如果一个节点连续一段时间没有接收到另一个节点的 PONG 消息,那么它会认为那个节点已经失效。这个超时时间可以通过
cluster-node-timeout
配置项来调整,默认为 15 秒。 - 故障转移:一旦一个主节点被认为已经失效,它的从节点中会有一个被选举成为新的主节点。这一过程是由集群中的其他节点协作完成的。
限制
Redis Cluster虽然功能强大,但存在一些限制,主要包括多键操作、事务和Lua脚本的限制。
多键操作限制
不支持跨槽多键操作:在Redis Cluster中,所有的数据被分配到16384个哈希槽中,每个节点负责一部分哈希槽。如果多个键不在同一个槽中,则无法执行跨槽的多键操作,例如MGET、MSET等。
解决方法:为了绕过这个限制,可以使用HashTag机制,即在键名上使用花括号将共同的部分括起来,这样这些键会被映射到同一个槽。例如,{user1000}.following和{user1000}.followers就会被分配到同一个槽中。
事务限制
不支持跨槽事务:在Redis Cluster中,如果一个事务中的所有键不在同一个槽中,则该事务无法执行。这是因为事务中的所有命令必须在同一节点上执行,而不同槽的键可能存储在不同的节点上。
部分支持事务:当事务中的所有键都在同一个槽中时,事务可以正常执行,并且遵循事务语意。但如果不满足所有键在一个槽中的条件,事务内的命令仍然可以单独执行,不过它们之间不保证事务语意。
Lua脚本限制
一致性问题:在Redis Cluster中,由于键可能分散在不同的节点上,使用Lua脚本操作多个键时,无法保证操作的原子性和一致性。这可能导致脚本的执行结果不一致或出现错误。
限制加强:如果脚本中的键位于同一个哈希槽中,则脚本可以正常执行。否则,需要重新设计脚本,使其只操作位于同一哈希槽的键,或者使用客户端逻辑来协调多个脚本调用。