概括
redis集群主要是分为三种,redis主从模式、主从+哨兵模式、redisCluster
主从模式
一般来说,很多项目使用主从模式就足够高了,单个master负责写数据,多个slave来负责查询。单机几万QPS,多个实体可以提供每秒10wQPS
replication 的核⼼机制
- Redis 采⽤异步⽅式复制数据到 slave 节点,不过 Redis2.8 开始,slave node 会周期性地确认⾃⼰每次复制 的数据量;
- ⼀个 master node 是可以配置多个 slave node 的;
- slave node 也可以连接其他的 slave node;
- slave node 做复制的时候,不会 block master node 的正常⼯作;
- slave node 在做复制的时候,也不会 block 对⾃⼰的查询操作,它会⽤旧的数据集来提供服务;但是复制完成的时候,需要删除旧数据集,加载新数据集,这个时候就会暂停对外服务了;
- slave node 主要⽤来进⾏横向扩容,做读写分离,扩容的 slave node 可以提⾼读的吞吐量
注意,如果采⽤了主从架构,那么建议必须开启 master node 的持久化,不建议⽤ slave node 作为 master node的数据热备,因为那样的话,如果你关掉 master 的持久化,可能在 master 宕机重启的时候数据是空的,然后可能⼀经过复制, slave node 的数据也丢了。
另外,master 的各种备份⽅案,也需要做。万⼀本地的所有⽂件丢失了,从备份中挑选⼀份 rdb 去恢复 master,这样才能确保启动的时候,是有数据的,即使采⽤了后续讲解的⾼可⽤机制,slave node 可以⾃动接管 masternode,但也可能 sentinel 还没检测到 master failure,master node 就⾃动重启了,还是可能导致上⾯所有的slave node 数据被清空
主从复制
当启动⼀个 slave node 的时候,它会发送⼀个 PSYNC 命令给 master node。
如果这是 slave node 初次连接到 master node,那么会触发⼀次 full resynchronization 全量复制。此时master 会启动⼀个后台线程,开始⽣成⼀份 RDB 快照⽂件,同时还会将从客户端 client 新收到的所有写命令缓存在内存中。 RDB ⽂件⽣成完毕后, master 会将这个 RDB 发送给 slave,slave 会先写⼊本地磁盘,然后再从本地磁盘加载到内存中,接着 master 会将内存中缓存的写命令发送到 slave,slave 也会同步这些数据。slave node 如果跟 master node 有⽹络故障,断开了连接,会⾃动重连,连接之后 master node 仅会复制给 slave 部分缺少的数据
断点续传
从 Redis2.8 开始,就⽀持主从复制的断点续传,如果主从复制过程中,⽹络连接断掉了,那么可以接着上次复制的地⽅,继续复制下去,⽽不是从头开始复制⼀份。
master node 会在内存中维护⼀个 backlog,master 和 slave 都会保存⼀个 replica offset 还有⼀个 master runid,offset 就是保存在 backlog 中的。如果 master 和 slave ⽹络连接断掉了,slave 会让 master 从上次 replicaoffset 开始继续复制,如果没有找到对应的 offset,那么就会执⾏⼀次 resynchronization
如果根据 host+ip 定位 master node,是不靠谱的,如果 master node 重启或者数据出现了变化,那么slave node 应该根据不同的 run id 区分
无磁盘化复制
master 在内存中直接创建 RDB ,然后发送给 slave,不会在⾃⼰本地落地磁盘了。只需要在配置⽂件中开启repl-diskless-sync yes 即可
repl-diskless-sync yes
# 等待 5s 后再开始复制,因为要等更多 slave 重新连接过来
repl-diskless-sync-delay 5
过期key处理
slave 不会过期 key,只会等待 master 过期 key。如果 master 过期了⼀个 key,或者通过 LRU 淘汰了⼀个 key,那么会模拟⼀条 del 命令发送给 slave
主从复制的时候,其实是不会将过期key发送给slave的
复制的完整流程
slave node 启动时,会在⾃⼰本地保存 master node 的信息,包括 master node 的 host 和 ip ,但是复制流程没开始。slave node 内部有个定时任务,每秒检查是否有新的 master node 要连接和复制,如果发现,就跟 master node建⽴ socket ⽹络连接。然后 slave node 发送 ping 命令给 master node。如果 master 设置了 requirepass,那么 slave node 必须发送 masterauth 的⼝令过去进⾏认证。master node 第⼀次执⾏全量复制,将所有数据发给slave node。⽽在后续,master node 持续将写命令,异步复制给 slave node
全量复制
- master 执⾏ bgsave ,在本地⽣成⼀份 rdb 快照⽂件。
- master node 将 rdb 快照⽂件发送给 slave node,如果 rdb 复制时间超过 60 秒(repl-timeout),那么slave node 就会认为复制失败,可以适当调⼤这个参数(对于千兆⽹卡的机器,⼀般每秒传输 100MB,6G ⽂件,很可能超过 60s)
- master node 在⽣成 rdb 时,会将所有新的写命令缓存在内存中,在 slave node 保存了 rdb 之后,再将新的写命令复制给 slave node。
- 如果在复制期间,内存缓冲区持续消耗超过 64MB,或者⼀次性超过 256MB,那么停⽌复制,复制失败。
client-output-buffer-limit slave 256MB 64MB 60
- slave node 接收到 rdb 之后,清空⾃⼰的旧数据,然后重新加载 rdb 到⾃⼰的内存中,同时基于旧的数据版本对外提供服务。
- 如果 slave node 开启了 AOF,那么会⽴即执⾏ BGREWRITEAOF,重写 AOF
增量复制
- 如果全量复制过程中,master-slave ⽹络连接断掉,那么 slave 重新连接 master 时,会触发增量复制。
- master 直接从⾃⼰的 backlog 中获取部分丢失的数据,发送给 slave node,默认 backlog 就是 1MB。
- master 就是根据 slave 发送的 psync 中的 offset 来从 backlog 中获取数据的
heartbeat
主从节点互相都会发送 heartbeat 信息。
master 默认每隔 10 秒发送⼀次 heartbeat,slave node 每隔 1 秒发送⼀个 heartbeat
异步复制
master 每次接收到写命令之后,先在内部写⼊数据,然后异步发送给 slave node。
哨兵机制
初步介绍
sentinel,中⽂名是哨兵。哨兵是 Redis 集群架构中⾮常重要的⼀个组件,主要有以下功能:
- 集群监控:负责监控 Redis master 和 slave 进程是否正常⼯作。
- 消息通知:如果某个 Redis 实例有故障,那么哨兵负责发送消息作为报警通知给管理员。
- 故障转移:如果 master node 挂掉了,会⾃动转移到 slave node 上。
- 配置中⼼:如果故障转移发⽣了,通知 client 客户端新的 master 地址。
哨兵⽤于实现 Redis 集群的⾼可⽤,本身也是分布式的,作为⼀个哨兵集群去运⾏,互相协同⼯作。
故障转移时,判断⼀个 master node 是否宕机了,需要⼤部分的哨兵都同意才⾏,涉及到了分布式选举的问题。
即使部分哨兵节点挂掉了,哨兵集群还是能正常⼯作的,因为如果⼀个作为⾼可⽤机制重要组成部分的故障转移系统本身是单点的,那就很坑爹了
核心知识
- 哨兵⾄少需要 3 个实例,来保证⾃⼰的健壮性。
- 哨兵 + Redis 主从的部署架构,是不保证数据零丢失的,只能保证 Redis 集群的⾼可⽤性。
- 对于哨兵 + Redis 主从这种复杂的部署架构,尽量在测试环境和⽣产环境,都进⾏充⾜的测试和演练。
哨兵集群必须部署 2 个以上节点,如果哨兵集群仅仅部署了 2 个哨兵实例,quorum = 1。
数据丢失
两种情况
主备切换的过程,可能会导致数据丢失:
- 异步复制导致的数据丢失
因为 master->slave 的复制是异步的,所以可能有部分数据还没复制到 slave,master 就宕机了,此时这部分数据就丢失了。 - 脑裂导致的数据丢失
脑裂,也就是说,某个 master 所在机器突然脱离了正常的⽹络,跟其他 slave 机器不能连接,但是实际上 master还运⾏着。此时哨兵可能就会认为 master 宕机了,然后开启选举,将其他 slave 切换成了 master。这个时候,集群⾥就会有两个 master ,也就是所谓的脑裂。
此时虽然某个 slave 被切换成了 master,但是可能 client 还没来得及切换到新的 master,还继续向旧 master 写数据。因此旧 master 再次恢复的时候,会被作为⼀个 slave 挂到新的 master 上去,⾃⼰的数据会清空,重新从新的 master 复制数据。⽽新的 master 并没有后来 client 写⼊的数据,因此,这部分数据也就丢失了
解决方案
min-slaves-to-write 1
min-slaves-max-lag 10
表示,要求⾄少有 1 个 slave,数据复制和同步的延迟不能超过 10 秒。
如果说⼀旦所有的 slave,数据复制和同步的延迟都超过了 10 秒钟,那么这个时候,master 就不会再接收任何请求了
- 减少异步复制数据的丢失
有了 min-slaves-max-lag 这个配置,就可以确保说,⼀旦 slave 复制数据和 ack 延时太⻓,就认为可能master 宕机后损失的数据太多了,那么就拒绝写请求,这样可以把 master 宕机时由于部分数据未同步到 slave 导致的数据丢失降低的可控范围内。 - 减少脑裂的数据丢失
如果⼀个 master 出现了脑裂,跟其他 slave 丢了连接,那么上⾯两个配置可以确保说,如果不能继续给指定数量的 slave 发送数据,⽽且 slave 超过 10 秒没有给⾃⼰ ack 消息,那么就直接拒绝客户端的写请求。因此在脑裂场景下,最多就丢失 10 秒的数据。
sdown 和 odown 转换机制
- sdown 是主观宕机,就⼀个哨兵如果⾃⼰觉得⼀个 master 宕机了,那么就是主观宕机
- odown 是客观宕机,如果 quorum 数量的哨兵都觉得⼀个 master 宕机了,那么就是客观宕机
sdown 达成的条件很简单,如果⼀个哨兵 ping ⼀个 master,超过了 is-master-down-after-milliseconds指定的毫秒数之后,就主观认为 master 宕机了;如果⼀个哨兵在指定时间内,收到了 quorum 数量的其它哨兵也认为那个 master 是 sdown 的,那么就认为是 odown 了
自动发现机制
哨兵互相之间的发现,是通过 Redis 的 pub/sub 系统实现的,每个哨兵都会往 sentinel:hello 这个channel ⾥发送⼀个消息,这时候所有其他哨兵都可以消费到这个消息,并感知到其他的哨兵的存在。
每隔两秒钟,每个哨兵都会往⾃⼰监控的某个 master+slaves 对应的 sentinel:hello channel ⾥发送⼀个消息,内容是⾃⼰的 host、ip 和 runid 还有对这个 master 的监控配置。
每个哨兵也会去监听⾃⼰监控的每个 master+slaves 对应的 sentinel:hello channel,然后去感知到同样在监听这个 master+slaves 的其他哨兵的存在。
每个哨兵还会跟其他哨兵交换对 master 的监控配置,互相进⾏监控配置的同步
slave 配置的⾃动纠错
哨兵会负责⾃动纠正 slave 的⼀些配置,⽐如 slave 如果要成为潜在的 master 候选⼈,哨兵会确保 slave 复制现有 master 的数据;如果 slave 连接到了⼀个错误的 master 上,⽐如故障转移之后,那么哨兵会确保它们连接到正确的 master 上
选举算法
哨兵会负责⾃动纠正 slave 的⼀些配置,⽐如 slave 如果要成为潜在的 master 候选⼈,哨兵会确保 slave 复制现有 master 的数据;如果 slave 连接到了⼀个错误的 master 上,⽐如故障转移之后,那么哨兵会确保它们连接到正确的 master 上
- 跟 master 断开连接的时⻓
- slave 优先级
- 复制 offset
- run id
如果⼀个 slave 跟 master 断开连接的时间已经超过了 down-after-milliseconds 的 10 倍,外加 master 宕机的时⻓,那么 slave 就被认为不适合选举为 master
(down-after-milliseconds * 10) + milliseconds_since_master_is_in_SDOWN_state
接下来会对 slave 进⾏排序:
- 按照 slave 优先级进⾏排序,slave priority 越低,优先级就越⾼。
- 如果 slave priority 相同,那么看 replica offset,哪个 slave 复制了越多的数据,offset 越靠后,优先级就越⾼。
- 如果上⾯两个条件都相同,那么选择⼀个 run id ⽐较⼩的那个 slave
quorum 和 majority
每次⼀个哨兵要做主备切换,⾸先需要 quorum 数量的哨兵认为 odown,然后选举出⼀个哨兵来做切换,这个哨兵还需要得到 majority 哨兵的授权,才能正式执⾏切换。
如果 quorum < majority,⽐如 5 个哨兵,majority 就是 3,quorum 设置为 2,那么就 3 个哨兵授权就可以执⾏切换。
但是如果 quorum >= majority,那么必须 quorum 数量的哨兵都授权,⽐如 5 个哨兵,quorum 是 5,那么必须5 个哨兵都同意授权,才能执⾏切换。
configuration epoch
哨兵会对⼀套 Redis master+slaves 进⾏监控,有相应的监控的配置。执⾏切换的那个哨兵,会从要切换到的新 master(salve->master)那⾥得到⼀个 configuration epoch,这就是⼀个 version 号,每次切换的 version 号都必须是唯⼀的。
如果第⼀个选举出的哨兵切换失败了,那么其他哨兵,会等待 failover-timeout 时间,然后接替继续执⾏切换,此时会重新获取⼀个新的 configuration epoch,作为新的 version 号。
configuration 传播
哨兵完成切换之后,会在⾃⼰本地更新⽣成最新的 master 配置,然后同步给其他的哨兵,就是通过之前说的pub/sub 消息机制。
这⾥之前的 version 号就很重要了,因为各种消息都是通过⼀个 channel 去发布和监听的,所以⼀个哨兵完成⼀次新的切换之后,新的 master 配置是跟着新的 version 号的。其他的哨兵都是根据版本号的⼤⼩来更新⾃⼰的
master 配置的。