Redis之2单点到集群
单机、单节点、单实例有什么问题?
- 单点故障
- 容量有限
- 压力
redis sentinel(哨兵)
可以解决:单点故障、压力(连接或读)
选举
当redis集群的主节点故障时,Sentinel集群将从剩余的从节点中选举一个新的主节点,有以下步骤:
- 故障节点主观下线
- 故障节点客观下线
- Sentinel集群选举Leader
- Sentinel Leader决定新主节点
选举过程
1、主观下线
Sentinel集群的每一个Sentinel节点会定时对redis集群的所有节点发心跳包检测节点是否正常。如果一个节点在down-after-milliseconds时间内没有回复Sentinel节点的心跳包,则该redis节点被该Sentinel节点主观下线。
2、客观下线
当节点被一个Sentinel节点记为主观下线时,并不意味着该节点肯定故障了,还需要Sentinel集群的其他Sentinel节点共同判断为主观下线才行。
该Sentinel节点会询问其他Sentinel节点,如果Sentinel集群中超过quorum数量的Sentinel节点认为该redis节点主观下线,则该redis客观下线。
如果客观下线的redis节点是从节点或者是Sentinel节点,则操作到此为止,没有后续的操作了;如果客观下线的redis节点为主节点,则开始故障转移,从从节点中选举一个节点升级为主节点。
3、Sentinel集群选举Leader
如果需要从redis集群选举一个节点为主节点,首先需要从Sentinel集群中选举一个Sentinel节点作为Leader。
每一个Sentinel节点都可以成为Leader,当一个Sentinel节点确认redis集群的主节点主观下线后,会请求其他Sentinel节点要求将自己选举为Leader。被请求的Sentinel节点如果没有同意过其他Sentinel节点的选举请求,则同意该请求(选举票数+1),否则不同意。
如果一个Sentinel节点获得的选举票数达到Leader最低票数(quorum和Sentinel节点数/2+1的最大值),则该Sentinel节点选举为Leader;否则重新进行选举。
4、Sentinel Leader决定新主节点
当Sentinel集群选举出Sentinel Leader后,由Sentinel Leader从redis从节点中选择一个redis节点作为主节点:
过滤故障的节点
选择优先级slave-priority最大的从节点作为主节点,如不存在则继续
选择复制偏移量(数据写入量的字节,记录写了多少数据。主服务器会把偏移量同步给从服务器,当主从的偏移量一致,则数据是完全同步)最大的从节点作为主节点,如不存在则继续
选择runid(redis每次启动的时候生成随机的runid作为redis的标识)最小的从节点作为主节点
为什么Sentinel集群至少3节点
一个Sentinel节选举成为Leader的最低票数为quorum
和Sentinel节点数/2+1
的最大值,如果Sentinel集群只有2个Sentinel节点,则
1 |
|
即Leader最低票数至少为2,当该Sentinel集群中由一个Sentinel节点故障后,仅剩的一个Sentinel节点是永远无法成为Leader。
也可以由此公式可以推导出,Sentinel集群允许1个Sentinel节点故障则需要3个节点的集群;允许2个节点故障则需要5个节点集群。
集群
如何分片
方案1.客户端按业务逻辑 不同的redis实例
方案2. 客户端使用 算法:hash+取模 modula 不同的redis实例
增加或删除redis实例不方便
方案3. 客户端采用 逻辑: 随机 + 队列 先去看我在哪个实例 不同的redis实例
方案4. 客户端采用 一致性哈希 + 哈希环
集群方案选型
集群方案解决:单点故障、容量有限、压务
代理方式:twemproxy 或者 Predixy
redis官方的集群:redis cluster
twemproxy 或者 Predixy 或者 redis cluster都是集群方案。
集群都支持hash tag
可以把{tag}的key放到一个redis实例。
redis cluster
redis cluster 使用分片方案4,获取是先算出在哪个槽位,再去哪个库取。
复制过程
Redis 的复制功能分为同步( sync )和命令传播( command propagate )两个步骤:
- 同步用于将从服务器的数据库状态更新至主服务器当前所处的数据库状态。
- 命令传播则用于在主服务器的数据库状态被修改,导致主从服务器的数据库状态出现不一致时,让主从服务器的数据库重新回到一致状态。
同步
Redis 使用 psync 命令完成主从数据同步,同步过程分为:全量复制和部分复制。
全量复制:一般用于初次复制场景,它会把主节点全部数据一次性发送给从节点发送给从节点,当数据量较大时,会对主从节点和网络造成很大的开销。
部分复制:用于处理在主从复制中因网络闪断等原因造成的网络丢失场景,当从节点再次连接上主节点后,如果条件允许,主节点会补发丢失数据给从节点。因为补发的数据远远小于全量数据,可以有效避免全量复制的过高开销。
psync 命令运行需要以下组件支持:
- 主从节点各自复制偏移量
- 主节点复制积压缓冲区
- 主节点运行 id
参与复制的从节点都会维护自身复制偏移量。主节点在处理完写命令后,会把命令的字节长度做累加记录,统计在 info replication 中的 masterreploffset 指标中。 从节点在接收到主节点发送的命令后,也会累加记录自身的偏移量,并且会每秒钟上报自身的复制偏移量给主节点。 通过对比主从节点的复制偏移量,可以判断主从节点数据是否一致。
复制积压缓冲区是保存在主节点的一个固定长度的队列,默认大小为 1MB,当主节点有连接的从节点时被创建。主节点响应写命令时,不但会把命令发送给从节点,还会写入复制积压缓冲区中。
复制积压缓冲区大小有限,只能保存最近的复制数据,用于部分复制和复制命令丢失时的数据补救。
每个 Redis 节点启动后都会动态分配一个 40 位的十六进制字符串作为运行 ID。运行 ID 的主要作用是用来唯一标识 Redis 节点,比如说从节点保存主节点的运行 ID 来识别自己正在复制的时哪个主节点。
全量同步
slaveof 命令的执行
从节点发送 psync 命令进行数据同步,由于是第一次进行复制,从节点没有复制偏移量和主节点的运行ID,所以发送的命令时 PSYNC ? -1。
主节点根据 PSYNC ? -1 解析出当前为全量复制,回复 + FULLRESYNC 响应。
从节点接收主节点的响应数据保存运行 ID 和偏移量 offset。
主节点执行 bgsave 保存 RDB 文件到本地,有关 RDB 的知识可以查看《Redis RDB 持久化详解》
主节点发送 RDB 文件给从节点,从节点把接收的 RDB 文件保存在本地并直接作为从节点的数据文件,接收完 RDB 后从节点打印相关日志,可以在日志中查看主节点发送的数据量。
需要注意,对于数据量较大的主节点,比如生成的 RDB 文件超过 6GB 以上时要格外小心。如果传输 RDB 的时间超过 repl-timeout 所配置的值,从节点将发起接收 RDB 文件并清理已经下载的临时文件,导致全量复制失败。
- 对于主节点开始保存 RDB 快照到从节点接收完成期间,主节点仍然响应读命令,因此主节点会把这期间写命令保存在复制客户端缓冲区内,当从节点加载完 RDB 文件后,主节点再把缓冲区内的数据发送给从节点,保证主从之间数据一致性。
如果主节点创建和传输 RDB 的时间过长,可能会出现主节点复制客户端缓冲区溢出。默认配置为 client-output-buffer-limit slave 256MB 64MB 60,如果60s内缓冲区消耗持续大于64MB或者直接超过256MB时,主节点将直接关闭复制客户端连接,造成全量同步失败。
从节点接收完主节点传送来的全部数据后会清空自身旧数据,该步骤对应如下日志。
从节点清空数据后开始加载 RDB 文件,对于加大的 RDB 文件,这一步操作依然比较耗时,可以通过计算日志之间的时间差来判断加载 RDB 的总耗时。
收到 SYNC 命令的主服务器执行 BGSAVE 命令,在后台生成一个 RDB 文件,并使用一个缓冲区记录从现在开始执行的所有写命令。
当主服务器的 BGSAVE 命令执行完毕时,主服务器会将 GBSAVE 命令生成的 RDB 文件发送给从服务器,从服务器接收并载入这个 RDB 文件,将自己的数据库状态更新至主服务器执行 BGSAVE 命令时的数据库状态。
主服务器将记录在缓冲区里边的所有写命令发送给从服务器,从服务器执行这些写命令,将自己的数据库状态更新至主服务器数据库当前所处的状态。
通过分析全量复制的所有流程,读者会发现全量复制是一个非常耗时费力的操作。它时间开销主要包括:
- 主节点 bgsave 时间
- RDB 文件网络传输时间
- 从节点清空数据时间
- 从节点加载 RDB 的时间
- 可能的 AOF 重写时间
全量同步过程中不仅会消耗大量时间,还会进行多次持久化相关操作和网络数据传输,这期间会大量消耗主从节点所在服务器的 CPU、内存和网络资源。所以,除了第一次复制是采用全量同步无法避免,其他场景应该规避全量复制,采取部分同步功能。
部分同步
部分复制主要是 Redis 针对全量复制的过高开销做出的一种优化措施,使用 psync {runId} {offset} 命令实现。当从节点正在复制主节点时,如果出现网络闪断或者命令丢失等异常情况时,从节点会向主节点要求补发丢失的命令数据,如果主节点的复制积压缓冲区存在这部分数据则直接发送给从节点,这样就保证了主从节点复制的一致性。补发的这部分数据一般远远小于全量数据,所以开销很小。
当主从节点之间网络出现中断时,如果超过了 repl-timeout 时间,主节点会认为从节点故障并中断复制连接。
主从连接中断期间主节点依然响应命令,但因复制连接中断命令无法发送给从节点,不过主节点内部存在复制积压缓冲区( repl-backlog-buffer ),依然可以保存最近一段时间的写命令数据,默认最大缓存 1MB。
当主从节点网络恢复后,从节点会再次连上主节点。
当主从连接恢复后,由于从节点之前保存了自身已复制的偏移量和主节点的运行ID。因此会把它们作为 psync 参数发送给主节点,要求进行补发复制操作。
主节点接到 psync 命令后首先核对参数 runId 是否与自身一致,如果一致,说明之前复制的是当前主节点;之后根据参数 offset 在自身复制积压缓冲区查找,如果偏移量之后的数据存在缓冲区中,则对从节点发送 +CONTINUE 响应,表示可以进行部分复制。
主节点根据偏移量把复制积压缓冲区里的数据发送给从节点,保证主从复制进入正常状态。
心跳检测
主从节点在建立复制后,它们之间维护着长连接并彼此发送心跳命令,如下图所示。
主从心跳判断机制如下所示:
主从节点彼此都有心跳检测机制,各自模拟成对方的客户端进行通信,通过 client list 命令查看复制相关客户端信息,主节点的连接状态为 flags=M,从节点连接状态为 flags=S。
主节点默认每隔 10 秒对从节点发送 ping 命令,判断从节点的存活性和连接状态。可以通过参数 repl-ping-slave-period 控制发送频率。
从节点在主线程中每隔 1 秒发送 replconf ack { offset } 命令,给主节点上报自己当前的复制偏移量。
replconf 命令不仅能实时监测主从节点网络状态,还能上报从节点复制偏移量。主节点会根据从节点上传的偏移量检查复制数据是否丢失,如果从节点数据丢失,再从主节点的复制缓存区中拉取丢失的数据发送给该从节点。
异步复制和命令传播
主节点不但负责数据读写,还负责把写命令同步给从节点。写命令的发送过程是异步完成,也就是说主节点自身处理完写命令后直接返回给客户端,并不等待从节点复制完成。
这个异步过程由命令传播来处理,它不仅会将写命令发送给所有从服务器,还会将写命令入队到复制积压缓冲区里边。