CH07-高可用性
主从复制
Redis 主从复制模式可以将主节点的数据同步给从节点,从而保障当主节点不可达的情况下,从节点可以作为后备顶上来,并且可以保障数据尽量不丢失。主从复制可以保障最终一致性
从节点可以扩展主节点的读能力,一旦主节点不能支持大规模并发量的读操作,从节点可以在一定程度上分担主节点的压力。
主从复制面临的问题:
- 当主节点发生故障的时候,需要手动的将一个从节点晋升为主节点,同时通知应用方修改主节点地址并重启应用,同时需要命令其它从节点复制新的主节点,整个过程需要人工干预。
- 主节点的写能力受到单机的限制。
- 主节点的存储能力受到单机的限制。
复制过程
一般当slave
第一次启动连接master
,或者“被认为是第一次连接”,是主从采用全量复制。全量复制的执行流程如下:
slave redis
启动. 会从redis.conf
中读取master ip
和host
。- 定时任务每秒检查是否有新的
mater
需要连接,如果发现就与master
建立socket
连接。 slave
发送ping
指令到mater
。- 如果
mater
配置require pass
,slave
需要发送认证给master
。 Salve
会发送sync
命令到Master
。Master
启动一个后台进程,将Redis
中的数据快照rdb
保存到文件中。- 启动后台进程的同时,
Master
会将保存数据快照期间接收到的写命令缓存起来。 Master
完成写文件操作后,将rdb
发送给Salve
。Salve
将rdb
保存到磁盘上,然后加载rdb
到redis
内存中。- 当
Salve
完成数据快照的恢复后,aster
将这期间收集的写命令发送给Salve
端。 - 后续
Master
收集到的写命令都会通过之前建立的连接. 增量发送给salve
端。
增量复制
当slave
节点与master
全量同步后,master
节点上数据再次发生更新,就会触发增量复制。
当我们在 master
服务器增减数据的时候,就会触发 replicationFeedSalves()
函数,接下来在 Master
服务器上调用的每一个命令都会使用replicationFeedSlaves()
函数来同步到Slave
服务器。当然,在执行此函数之前master
服务器会判断用户执行的命令是否有数据更新,如果有数据更新并且slave
服务器不为空,才会执行此函数,函数主要的工作就是把用户执行的命令发送到所有的 slave
服务器,让slave
服务器执行。
流程如下图:
断点续传
断点续传或者说是断点恢复复制,也就是说 slave 因为某种原因与master
断开连接了一段时间,然后又与master
发生重连。redis2.8
以后对于这种场景进行了优化,开始加入了PSYNC
同步策略。这种策略性能一定是大于全量复制的。
- 从服务器向主服务器发送
PSYNC
命令,携带主服务器的runid
和复制偏移量; - 主服务器验证
runid
和自身runid
是否一致,如不一致,则进行全量复制; - 主服务器验证复制偏移量是否在积压缓冲区内,如不在,则进行全量复制;
- 如都验证通过,则主服务器将保持在积压区内的偏移量后的所有数据发送给从服务器,主从服务器再次回到一致状态。
PSYNC 核心参数
断点续传的几个核心参数,offset
、backlog
、runid
。这三个参数在 PSYNC 中起到了至关重要的作用,下面我们来一一介绍一下。
offet
复制偏移量 ,offset
是用来记录master
和lslave
某个时段的数据版本状态的,slave
每秒会向master
上报offset
,master
保存下来,当触发 PSYNC 时再拿来和master
的offset
数据作对比。说白了,它就是记录数据在某一时刻的快照,用来对比 master 和 slave 数据差异用的。backlog
积压缓冲区- 这个也是一个非常核心的参数,它默认大小为
1mb
,复制积压缓冲区是由Master
维护的一个固定长度的FIFO
队列,它的作用是缓存已经传播出去的命令。当Master
进行命令传播时,不仅将命令发送给所有Slave
,还会将命令写入到复制积压缓冲区里面。 - 全量复制的时候,
master
的数据更新(读写操作,主动过期删除等)会临时存放在backlog
中待全量复制完成后增量发到slave,必须为此保留足够的空间。 - 断点续传时,
backlog
会存下slave
断开连接后,master
变更的数据。当然由于它大小有限制,而且先进先出特性,所以达到缓冲大小后会弹出老数据。这样,就可以把它作为一个衡量执行sync
还是psync
的一个标准(backlog = offset : 部分同步,backlog < offset 执行全量同步)
。一般为了避免,大规模全量复制,我们都会给一个恰当的值,根据公式second*write_size_per_second
来估算:其中second
为从服务器断线后重新连接上主服务器所需的平均时间(以秒计算);而write_size_per_second
则是主服务器平均每秒产生的写命令数据量(协议格式的写命令的长度总和);
- 这个也是一个非常核心的参数,它默认大小为
- master run id,
master
唯一标示,slave
连接master
时会传runid
,master
每次重启runid
都发生变化,当slave
发现master
的runid
变化时都会触发全量复制流程。
优缺点
优点:
- 高可靠性:一方面,采用双机主备架构,能够在主库出现故障时自动进行主备切换,从库提升为主库提供服务,保证服务平稳运行;另一方面,开启数据持久化功能和配置合理的备份策略,能有效的解决数据误操作和数据异常丢失的问题;
- 读写分离策略:从节点可以扩展主库节点的读能力,有效应对大并发量的读操作。
缺点:
- 故障恢复复杂,如果没有
RedisHA
系统(需要开发),当主库节点出现故障时,需要手动将一个从节点晋升为主节点,同时需要通知业务方变更配置,并且需要让其它从库节点去复制新主库节点,整个过程需要人为干预,比较繁琐; - 主库的写能力受到单机的限制,可以考虑分片;
- 主库的存储能力受到单机的限制,可以考虑
Pika
; - 原生复制的弊端在早期的版本中也会比较突出,如:
Redis
复制中断后,Slave
会发起psync
,此时如果同步不成功,则会进行全量同步,主库执行全量备份的同时可能会造成毫秒或秒级的卡顿;又由于COW
机制,导致极端情况下的主库内存溢出,程序异常退出或宕机;主库节点生成备份文件导致服务器磁盘IO
和CPU
(压缩)资源消耗;发送数GB
大小的备份文件导致服务器出口带宽暴增,阻塞请求,建议升级到最新版本。
Redis Sentinel
当主节点出现故障时,Redis Sentinel 能自动完成故障发现和故障转移,并通知应用方,从而实现真正的高可用。RedisSentinel 是一个分布式架构,其中包含若干个 Sentinel 节点和 Redis 数据节点,每个 Sentinel 节点会对数据节点和其余 Sentinel 节点进行监控,当它发现节点不可达时,会对节点做下线标识。如果被标识的是主节点,它还会和其他的 Sentinel 节点进行协商,当大多数 Sentinel 节点都认为主节点不可达时,它们会选举一个 Sentinel 节点来完成自动故障转移的工作,同时会将这个变化实时通知给 Redis 应用方。整个过程是自动的,不需要人工干预,解决了Redis 的高可用问题。
Redis Sentinel 包含了若干个 Sentinel 节点,这样做也带来了两个好处:
- 对节点的故障判断是由多个 Sentinel 节点共同完成,这样可以有效的防止误判。
- Sentinel 节点集合是由若干个 Sentinel 节点组成的,这样即使个别 Sentinel 节点不可用,整个Sentinel节点集合依然是健壮的。
Redis Sentinel 具有以下几个功能:
- 监控:Sentinel 会定期检测 Redis 数据节点、其余 Sentinel 节点是否可到达
- 通知:Sentinel 会将故障转移的结果通知给应用方。
- 主节点故障转移:实现从节点晋升为主节点并维护后续正确的主从关系。
- 配置提供者:在 RedisSentinel 结构中,客户端在初始化的时候连接的是 Sentinel 节点集合,从中获取主节点信息。
RedisSentinel 处理流程:
- Sentinel 集群通过给定的配置文件发现 master,启动时会监控 master。通过向 master 发送 info 信息获得该服务器下面的所有从服务器。
- Sentinel 集群通过命令连接向被监视的主从服务器发送 hello 信息(每秒一次),该信息包括 Sentinel 本身的 IP、端口、id 等内容,以此来向其他 Sentinel 宣告自己的存在。
- Sentinel 集群通过订阅连接接收其他 Sentinel 发送的 hello 信息,以此来发现监视同一个主服务器的其他 Sentinel;集群之间会互相创建命令连接用于通信,因为已经有主从服务器作为发送和接收 hello 信息的中介,Sentinel 之间不会创建订阅连接。
- Sentinel 集群使用 ping 命令来检测实例的状态,如果在指定的时间内(down-after-milliseconds)没有回复或则返回错误的回复,那么该实例被判为下线。
- 当 failover 主备切换被触发后,failover 并不会马上进行,Sentinel 中的大多数 Sentinel 授权后才可以进行 failover,即进行 failover 的 Sentinel 会去获得指定 quorum 个的 Sentinel 的授权,成功后进入 ODOWN 状态。如在 5 个 Sentinel 中配置了 2 个 quorum,等到 2 个 Sentinel 认为 master 死了就执行 failover。
- Sentinel 向选为 master 的 slave 发送 SLAVEOF NO ONE 命令,选择 slave 的条件是 Sentinel 首先会根据 slaves 的优先级来进行排序,优先级越小排名越靠前。如果优先级相同,则查看复制的下标,哪个从 master 接收的复制数据多,哪个就靠前。如果优先级和下标都相同,就选择进程 ID 较小的。
- Sentinel 被授权后,它将会获得宕掉的 master 的一份最新配置版本号 (config-epoch),当 failover 执行结束以后,这个版本号将会被用于最新的配置,通过广播形式通知其它 Sentinel,其它的 Sentinel 则更新对应 master 的配置。
1 到 3 是自动发现机制:
- 以 10 秒一次的频率,向被监视的 master 发送 info 命令,根据回复获取 master 当前信息。
- 以 1 秒一次的频率,向所有 redis 服务器、包含 Sentinel 在内发送 PING 命令,通过回复判断服务器是否在线。与主节点,从节点,其余 Sentinel 都建立起连接,实现了对每个节点的监控。
- 以 2 秒一次的频率,通过向所有被监视的 master,slave 服务器发送当前 Sentinel master 信息的消息。这个定时任务可以完成以下两个工作:
- 发现新的 Sentinel 节点:通过订阅主节点的 Sentinel:hello 了解其他 Sentinel 节点信息。如果是新加入的 Sentinel 节点,将该 Sentinel 节点信息保存起来,并与该 Sentinel 节点创建连接
- Sentinel 节点之间交换主节点状态,作为后面客观下线以及领导者选举的依据
4 是检测机制,5 和 6 是 failover 机制,7 是更新配置机制
Leader 选举
其实在 sentinels 故障转移中,仍然需要一个 Leader 来调度整个过程:master 的选举以及 slave 的重配置和同步。当集群中有多个 sentinel 实例时,如何选举其中一个 sentinel 为 leader 呢?
在配置文件中 can-failover、quorum 参数,以及 is-master-down-by-addr 指令配合来完成整个过程。
- can-failover 用来表明当前 sentinel 是否可以参与 failover 过程,如果为 YES 则表明它将有能力参与 Leader 的选举,否则它将作为 Observer ,observer 参与 leader 选举投票但不能被选举;
- quorum 不仅用来控制 master ODOWN 状态确认,同时还用来选举 leader 时最小「赞同票」数;
- is-master-down-by-addr,在上文中以及提到,它可以用来检测 ip + port 的 master 是否已经处于 SDOWN 状态,不过此指令不仅能够获得 master 是否处于 SDOWN,同时它还额外的返回当前 sentinel 本地「投票选举」的 Leader 信息 (runid);
每个 sentinel 实例都持有其他的 sentinels 信息,在 Leader 选举过程中(当为 leader 的 sentinel 实例失效时,有可能 master server 并没失效,注意分开理解),sentinel 实例将从所有的 sentinels 集合中去除 can-failover = no 和状态为 SDOWN 的 sentinels,在剩余的 sentinels 列表中按照 runid 按照「字典」顺序排序后,取出 runid 最小的 sentinel 实例,并将它「投票选举」为 Leader,并在其他 sentinel 发送的 is-master-down-by-addr 指令时将推选的 runid 追加到响应中。每个 sentinel 实例都会检测 is-master-down-by-addr 的响应结果,如果「投票选举」的 leader 为自己,且状态正常的 sentinels 实例中,赞同者的自己的 sentinel 个数不小于(>=) 50% + 1,且不小与 ,那么此 sentinel 就会认为选举成功且 leader 为自己。
在 sentinel.conf 文件中,我们期望有足够多的 sentinel 实例配置 can-failovers,这样能够确保当 leader 失效时,能够选举某个 sentinel 为 leader,以便进行 failover。如果 leader 无法产生,比如较少的 sentinels 实例有效,那么 failover 过程将续。
failover 过程
在 Leader 触发 failover 之前,首先 wait 数秒(随机 0~5),以便让其他 sentinel 实例准备和调整,如果一切正常,那么 leader 就需要开始将一个 salve 提升为 master,此 slave 必须为状态良好(不能处于 SDOWN/ODOWN 状态)且权重值最低(redis.conf中)的,当 master 身份被确认后,开始 failover:
- +failover-triggered: Leader 开始进行 failover,此后紧跟着 +failover-state-wait-start ,wait 数秒。
- +failover-state-select-slave: Leader 开始查找合适的 slave
- +selected-slave: 已经找到合适的 slave
- +failover-state-sen-slaveof-noone: Leader 向 slave 发送 slaveof no one 指令,此时 slave 已经完成角色转换,此 slave 即为 master
- +failover-state-wait-promotition: 等待其他 sentinel 确认 slave
- +promoted-slave:确认成功
- +failover-state-reconf-slaves: 开始对 slaves 进行 reconfig 操作。
- +slave-reconf-sent: 向指定的 slave 发送 slaveof 指令,告知此 slave 跟随新的 master
- +slave-reconf-inprog: 此 slave 正在执行 slaveof + SYNC 过程,如过 slave 收到 +slave-reconf-sent 之后将会执行 slaveof 操作。
- +slave-reconf-done: 此 slave 同步完成,此后 leader 可以继续下一个 slave 的 reconfig 操作。循环步骤 10
- +failover-end: 故障转移结束
- +switch-master:故障转移成功后,各个 sentinel 实例开始监控新的 master。
总结
Redis-Sentinel 是 Redis 官方推荐的高可用性解决方案,Redis-sentinel 本身也是一个独立运行的进程,它能监控多个 master-slave 集群,发现 master 宕机后能进行自动切换。Sentinel 可以监视任意多个主服务器(复用),以及主服务器属下的从服务器,并在被监视的主服务器下线时,自动执行故障转移操作。
为了防止 sentinel 的单点故障,可以对 sentinel 进行集群化,创建多个 sentinel。
Redis Cluster
Redis
集群是一个分布式(distributed
)、容错(fault-tolerant
)的 Redis
实现, 集群可以使用的功能是普通单机 Redis
所能使用的功能的一个子集(subset
)。
Redis
集群中不存在中心(central
)节点或者代理(proxy
)节点, 集群的其中一个主要设计目标是达到线性可扩展性(linear scalability
)。
Redis
集群提供了一种运行 Redis
的方式,其中数据在多个 Redis
节点间自动分区。Redis
集群还在分区期间提供一定程度的可用性,即在实际情况下能够在某些节点发生故障或无法通信时继续运行。但是,如果发生较大故障(例如,大多数主站不可用时),集群会停止运行。
集群模型
所有的节点通过服务通道直接相连,各个节点之间通过二进制协议优化传输的速度和带宽。
客户端与节点之间通过 ascii 协议进行通信。
客户端与节点直连,不需要中间 Proxy 层。客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可。
尽管这些节点彼此相连,功能相同,但是仍然分为两种节点:master 和 slave。
节点间传递信息
各个节点之间通过 PING-PONG 机制通信,下面是一段关于 PING-PONG 机制的会话”内容”。
节点M:PING,嘿,朋友你好吗?我是 XYZ 哈希槽的 master ,配置信息是 FF89X1JK。
节点N:PONG,我很好朋友,我也是 XYZ 哈希槽的 master ,配置信息是 FF89X1JK。
节点M:我这里有一些关于我最近收到的其他节点的信息 ,A 节点回复了我的 PING 消息,我认为 A 节点是正常的。B 没有回应我的消息,我猜它现在可能出问题了,但是我需要一些 ACK(Acknowledgement) 消息来确认。
节点N:我也想给你分享一些关于其它节点的信息,C 和 D 节点在指定的时间内回应了我, 我认为它们都是正常的,但是 B 也没有回应我,我觉得它现在可能已经挂掉了。
每个节点会向集群中的其他节点发送节点状态信息,如果某个节点挂掉停止了服务,那么会执行投票容错机制,关于这个机制,会在下面讲到。
Hash 槽(slot)
Redis 集群不使用一致的散列,而是一种不同的分片形式,其中每个键在概念上都是我们称之为散列槽的一部分,目的是使数据均匀的存储在诸多节点中。这点类似于 HashMap 中的桶(bucket)。
Redis 集群中有 16384 个散列槽,为了计算给定密钥的散列槽,Redis 对 key 采用 CRC16 算法,以下是负责将键映射到槽的算法:
slot = crc16(key) mod NUMER_SLOTS
例如,你可能有 3 个节点,其中一个集群:
- 节点 A 包含从 0 到 5500 的散列槽。
- 节点 B 包含从 5501 到 11000 的散列槽。
- 节点 C 包含 从 11001 到 16383 的散列槽。
Hash 槽可以轻松地添加和删除集群中的节点。例如,如果我想添加一个新节点 D,我需要将节点 A,B,C 中的一些散列槽移动到 D。同样,如果我想从节点 A 中删除节点 A,可以只移动由 A 服务的散列槽到 B 和 C。当节点 A 为空时,可以将它从群集中彻底删除。
- 对象保存到 Redis 之前先经过 CRC16 哈希到一个指定的 Node 上,例如 Object4 最终 Hash 到了 Node1 上。
- 每个 Node 被平均分配了一个 Slot 段,对应着 0-16384,Slot 不能重复也不能缺失,否则会导致对象重复存储或无法存储。
- Node 之间也互相监听,一旦有 Node 退出或者加入,会按照 Slot 为单位做数据的迁移。例如 Node1 如果掉线了,0-5640 这些 Slot 将会平均分摊到 Node2 和 Node3 上,由于 Node2 和 Node3 本身维护的 Slot 还会在自己身上不会被重新分配,所以迁移过程中不会影响到 5641-16384Slot 段的使用。
想扩展并发读就添加 Slaver,想扩展并发写就添加 Master,想扩容也就是添加 Master,任何一个 Slaver 或者几个 Master 挂了都不会是灾难性的故障。
简单总结下哈希 Slot 的优缺点:
缺点:每个 Node 承担着互相监听、高并发数据写入、高并发数据读出,工作任务繁重
优点:将 Redis 的写操作分摊到了多个节点上,提高写的并发能力,扩容简单。
容错
- 集群中的节点不断的
PING
其他的节点,当一个节点向另一个节点发送PING
命令, 但是目标节点未能在给定的时限内回复, 那么发送命令的节点会将目标节点标记为PFAIL
(possible failure
,可能已失效)。 - 当节点接收到其他节点发来的信息时, 它会记下那些被其他节点标记为失效的节点。 这被称为失效报告(
failure report
)。 - 如果节点已经将某个节点标记为
PFAIL
, 并且根据节点所收到的失效报告显式, 集群中的大部分其他主节点也认为那个节点进入了失效状态, 那么节点会将那个失效节点的状态标记为FAIL
。 - 一旦某个节点被标记为
FAIL
, 关于这个节点已失效的信息就会被广播到整个集群, 所有接收到这条信息的节点都会将失效节点标记为FAIL
。
简单来说, 一个节点要将另一个节点标记为失效, 必须先询问其他节点的意见, 并且得到大部分主节点的同意才行。
如果被标记为 FAIL
的是从节点, 那么当这个节点重新上线时, FAIL
标记就会被移除。 一个从节点是否处于 FAIL
状态, 决定了这个从节点在有需要时能否被提升为主节点。
如果一个主节点被打上 FAIL
标记之后, 经过了节点超时时限的四倍时间, 再加上十秒钟之后, 针对这个主节点的槽的故障转移操作仍未完成, 并且这个主节点已经重新上线的话, 那么移除对这个节点的 FAIL
标记。在不符合上面的条件后,一旦某个主节点进入 FAIL
状态, 如果这个主节点有一个或多个从节点存在, 那么其中一个从节点会被升级为新的主节点, 而其他从节点则会开始对这个新的主节点进行复制。
优缺点
优点:
- 无中心架构;
- 数据按照
slot
存储分布在多个节点,节点间数据共享,可动态调整数据分布; - 可扩展性:可线性扩展到 1000 多个节点,节点可动态添加或删除;
- 高可用性:部分节点不可用时,集群仍可用。通过增加
Slave
做standby
数据副本,能够实现故障自动failover
,节点之间通过gossip
协议交换状态信息,用投票机制完成Slave
到Master
的角色提升; - 降低运维成本,提高系统的扩展性和可用性。
缺点:
Client
实现复杂,驱动要求实现Smart Client
,缓存slots mapping
信息并及时更新,提高了开发难度,客户端的不成熟影响业务的稳定性。目前仅JedisCluster
相对成熟,异常处理部分还不完善,比如常见的“max redirect exception”
。- 节点会因为某些原因发生阻塞(阻塞时间大于
clutser-node-timeout
),被判断下线,这种failover
是没有必要的。 - 数据通过异步复制,不保证数据的强一致性。
- 多个业务使用同一套集群时,无法根据统计区分冷热数据,资源隔离性较差,容易出现相互影响的情况。
Slave
在集群中充当“冷备”,不能缓解读压力,当然可以通过SDK
的合理设计来提高Slave
资源的利用率。Key
批量操作限制,如使用mset
、mget
目前只支持具有相同slot
值的Key
执行批量操作。对于映射为不同slot
值的Key
由于Keys
不支持跨slot
查询,所以执行mset
、mget
、sunion
等操作支持不友好。Key
事务操作支持有限,只支持多key
在同一节点上的事务操作,当多个Key
分布于不同的节点上时无法使用事务功能。Key
作为数据分区的最小粒度,不能将一个很大的键值对象如hash
、list
等映射到不同的节点。- 不支持多数据库空间,单机下的
redis
可以支持到 16 个数据库,集群模式下只能使用 1 个数据库空间,即 db 0。 - 复制结构只支持一层,从节点只能复制主节点,不支持嵌套树状复制结构。
- 避免产生
hot-key
,导致主库节点成为系统的短板。 - 避免产生
big-key
,导致网卡撑爆、慢查询等。 - 重试时间应该大于
cluster-node-time
时间。 Redis Cluster
不建议使用pipeline
和multi-keys
操作,减少max redirect
产生的场景。
Codis
Codis
是一个代理中间件,如下图,Codis
在系统的位置是这样的。
Codis
分为四个部分,分别是Codis Proxy
(codis-proxy
)、Codis Dashboard
(codis-config
)、Codis Redis
(codis-server
)和ZooKeeper/Etcd
.
Codis
就是起着一个中间代理的作用,能够把所有的Redis
实例当成一个来使用,在客户端操作着SDK
的时候和操作Redis
的时候是一样的,没有差别。
因为Codis
是一个无状态的,所以可以增加多个Codis
来提升QPS
,同时也可以起着容灾的作用。
分片原理
在Codis
中,Codis
会把所有的key
分成 1024 个槽,这 1024 个槽对应着的就是Redis
的集群,这个在Codis
中是会在内存中维护着这 1024 个槽与Redis
实例的映射关系。这个槽是可以配置,可以设置成 2048 或者是 4096 个。看你的Redis
的节点数量有多少,偏多的话,可以设置槽多一些。
Codis
中key
的分配算法,先是把key
进行CRC32
后,得到一个 32 位的数字,然后再hash%1024
后得到一个余数,这个值就是这个key
对应着的槽,这槽后面对应着的就是redis
的实例。(可以思考一下,为什么 Codis 很多命令行不支持,例如 KEYS 操作)
CRC32
:CRC
本身是“冗余校验码”的意思,CRC32
则表示会产生一个32bit
(8 位十六进制数)的校验值。由于CRC32
产生校验值时源数据块的每一个bit
(位)都参与了计算,所以数据块中即使只有一位发生了变化,也会得到不同的CRC32
值。
Codis中Key的算法代码如下:
hash = crc32(command.key)
slot_index = hash % 1024
redis = slots[slot_index].redis
redis.do(command)
槽位同步
思考一个问题:如果这个 Codis 节点只在自己的内存里面维护着槽位与实例的关系,那么它的槽位信息怎么在多个实例间同步呢?
Codis 把这个工作交给了 ZooKeeper 来管理,当 Codis 的 Codis Dashbord 改变槽位的信息的时候,其他的 Codis 节点会监听到 ZooKeeper 的槽位变化,会及时同步过来。如图:
节点扩容
思考一个问题:在 Codis 中增加了 Redis 节点后,槽位的信息怎么变化,原来的 key 怎么迁移和分配?如果在扩容的时候,这个时候有新的 key 进来,Codis 的处理策略是怎么样的?
因为Codis
是一个代理中间件,所以这个当需要扩容Redis
实例的时候,可以直接增加redis
节点。在槽位分配的时候,可以手动指定Codis Dashbord
来为新增的节点来分配特定的槽位。
在Codis
中实现了自定义的扫描指令SLOTSSCAN
,可以扫描指定的slot
下的所有的key
,将这些key
迁移到新的Redis
的节点中(话外语:这个是Codis
定制化的其中一个好处)。
首先,在迁移的时候,会在原来的Redis
节点和新的Redis
里都保存着迁移的槽位信息,在迁移的过程中,如果有key
打进将要迁移或者正在迁移的旧槽位的时候,这个时候Codis
的处理机制是,先是将这个key
强制迁移到新的Redis
节点中,然后再告诉Codis
,下次如果有新的key
的打在这个槽位中的话,那么转发到新的节点。代码策略如下:
slot_index = crc32(command.key) % 1024
if slot_index in migrating_slots:
do_migrate_key(command.key) # 强制执行迁移
redis = slots[slot_index].new_redis
else:
redis = slots[slot_index].redis
redis.do(command)
自动均衡策略
面对着上面讲的迁移策略,如果有成千上万个节点新增进来,都需要我们手动去迁移吗?那岂不是得累死啊。当然,Codis
也是考虑到了这一点,所以提供了自动均衡策略。自动均衡策略是这样的,Codis
会在机器空闲的时候,观察Redis
中的实例对应着的slot
数,如果不平衡的话就会自动进行迁移。
Codis 的牺牲
因为Codis
在Redis
的基础上的改造,所以在Codis
上是不支持事务的,同时也会有一些命令行不支持,在官方的文档上有(Codis
不支持的命令)
官方的建议是单个集合的总容量不要超过 1M,否则在迁移的时候会有卡顿感。在Codis
中,增加了proxy
来当中转层,所以在网络开销上,是会比单个的Redis
节点的性能有所下降的,所以这部分会有些的性能消耗。可以增加proxy
的数量来避免掉这块的性能损耗。
MGET 的过程
思考一个问题:如果熟悉 Redis 中的 MGET、MSET 和 MSETNX 命令的话,就会知道这三个命令都是原子性的命令。但是,为什么 Codis 支持 MGET 和 MSET,却不支持 MSETNX 命令呢?
原因如下:
在Codis
中的MGET
命令的原理是这样的,先是在Redis
中的各个实例里获取到符合的key
,然后再汇总到Codis
中,如果是MSETNX
的话,因为key
可能存在在多个Redis
的实例中,如果某个实例的设值成功,而另一个实例的设值不成功,从本质上讲这是不成功的,但是分布在多个实例中的Redis
是没有回滚机制的,所以会产生脏数据,所以 MSETNX 就是不能支持了。
Feedback
Was this page helpful?
Glad to hear it! Please tell us how we can improve.
Sorry to hear that. Please tell us how we can improve.