前言
Redis最为目前主流非关系型数据库,给系统带来了高性能的保障同时也有落地高可用的方案。本文主要讲述持久化机制,主从,哨兵,集群。
Redis持久化
RDB
redis默认情况下将快照存储在名为dump.rdb的二进制文件中。
在redis.config配置中有三条自动触发bgsave指令(可自行配置多个满足一条即可触发)
例:save 900 1 (900秒内至少有1个key发生变化)
除了自动触发还有手动触发,在redis客户端执行命令save或bgsave可以生成新的rdb文件覆盖原先文件。
save和bgsave的区别
| 方式 | save | bgsave |
|---|---|---|
| 读写 | 同步 | 异步 |
| 阻塞 | 是 | 否 |
| 额外内存消耗 | 否 | 是 |
| 启动新进程 | 否 | 是 |
save命令:阻塞当前 Redis,直到 RDB 持久化过程完成为止。
bgsave命令:redis会在后台异步进行快照操作,快照同时还可以响应客户端请求。可以通过lastsave命令获取最后一次成功执行快照的时间。redis 进程执行 fork 操作创建子线程,由子线程完成持久化。
AOF
aof提供了三个选项:
appendfsync always:每次有新命令追加到 AOF 文件时就 fsync 一次
appendfsync everysec:每秒 fsync 一次
appendfsync no:从不 fsync
AOF文件中记录如下:
对比rdb的二进制格式,这种resp协议数据格式注定恢复速度没有二进制快。
在开启AOF功能后,每当redis执行一个命令时这个命令就会已resp数据格式去追加到AOF文件的末尾。
AOF重写
刚刚提到每一条redis命令都会追加到AOF文件末尾,假设一种场景: K为product:1001(产品id) V为1000(库存),在多用户下单后 库存变为950,中间有减去50个库存的操作,AOF在追加这50条命令后,其实是没有意义的因为我们想要只是他的最终值950,中间的过程并不需要关心,AOF也考虑到了这一点于是就有了AOF重写,配置如下:
auto‐aof‐rewrite‐min‐size 64mb //aof文件至少要达到64M才会自动重写
auto‐aof‐rewrite‐percentage 200//aof文件在上一次重写后大小增长了200%则再次触发重写
Redis主从架构
当主从服务器刚建立连接的时候,进行全量同步;全量复制结束后,进行增量复制。
主从全量复制流程
(1)slave服务器连接到master服务器,便开始进行数据同步,发送psync命令。
(2)master服务器收到psync命令之后,开始执行bgsave命令生成RDB快照文件并使用缓存区记录此后执行的所有写命令,如果master收到了多个slave连接,它都会发送一个PSYNC 命令给master请求复制数据。
(3)master服务器bgsave执行完之后,就会向所有Slava服务器发送快照文件,并在发送期间继续在缓冲区内记录被执行的写命令。
(4)slave服务器收到RDB快照文件后,会将接收到的数据写入磁盘,然后清空所有旧数据,在从本地磁盘载入收到的快照到内存中,同时基于旧的数据版本对外提供服务。
(5)master服务器发送完RDB快照文件之后,便开始向slave服务器发送缓冲区中的写命令。
(6)slave服务器完成对快照的载入,开始接收命令请求,并执行来自主服务器缓冲区的写命令。
增量复制流程
在主从复制断开连接后,master持续写入了N条数据,由于salve断开连接,长连接无法正常发送命令。slave重新连接master时,会触发全量复制,但是从2.8版本开始,slave与master能够在网络连接断开重连后,只从中断处继续进行复制,而不必重新同步,这就是所谓的断点续传。
master会在其内存中创建一个复制数据用的缓存队列,缓存最近一段时间的数据,master和它所有的 slave都维护了复制的数据下标offset和master的进程id,因此,当网络连接断开后,slave会请求master 继续进行未完成的复制,从所记录的数据下标开始。如果master进程id变化了,或者从节点数据下标 offset太旧,已经不在master的缓存队列里了,那么将会进行一次全量数据的复制。
Redis哨兵架构
在哨兵架构下客户端建立连接是与sentinel建立,在第一次与sentinel建立连接时,sentinel会找到redis主节点返回给客户端,后续客户端就会直接访问该主节点。当主节点挂掉后有从节点升级为主节点,sentinel会感知到新的主节点信息然后推送给客户端。
哨兵选举
当master挂掉,某个sentinel无法监控该心跳就视为下线状态,该sentinel可以要求其他sentinel共同去选举出leader,每次选举在sentinel中会自增一次选举周期,当有超过一半的sentinel选举某个sentinel则选举成功。由该sentinel从savle中选举出新的master。
Redis集群架构
redis集群是有多个主从节点横向扩容组成的分布式集群,它具有复制,高可用,分片特性。
redis集群原理
redis集群将所有数据划分为16384个槽位,每个主从节点负责一部分槽位。当redis集群的客户端来连接时,它会得到一份集群的槽位配置信息并将其缓存在客户端本地。当客户端要查到某个key时,可以直接定位到目标节点。同时因为槽位信息可能会存在客户端与服务器不一致的清空,还需要纠正机制来实现槽位信息的校验调整。
槽位定位算法
对key使用crc16算法进行hash得到一个整数值,然后在对16384进行取模来得到具体槽位
跳转重定向
当客户端向一个错误的节点发出了指令,该节点会发现指令的 key 所在的槽位并不归自己管理,这时它会向客户端发送一个特殊的跳转指令携带目标操作的节点地址,告诉客户端去连这个节点去获取数据。客户端收到指 令后除了跳转到正确的节点上去操作,还会同步更新纠正本地的槽位映射表缓存,后续所有 key 将使用新的槽位映射表。
集群通信机制
redis集群节点间采取gossip协议进行通信。
redis集群选举原理
(1)某个主从节点下的salve发现自己的master挂掉。
(2)广播FAILOVER_AUTH_REQUEST信息,自增选举周期。
(3)其他节点收到广播信息,其他节点的master判断请求合法性并作出响应,该响应在一次选举周期内只会做出一次ack。
(4)发送广播的salve收集master返回的相应,超过半数master的ack后变成新的master。
(5)该salve广播Pong消息通知其他集群节点自身选举为master。
集群脑裂问题
redis集群的主从节点,master和salve在高可用方案下会分布在不同的机器上,他们之间就会建立起io通信,当出现网络波动清空,master和salve通信中断,salve误认为master挂掉从而发起选举晋升出新的master,当前主从节点对外就会产生两个master。当网络分区恢复,其中一个master就会降级为salve,在网络波动期间写入的数据将丢失。 解决方案在redis配置修改以下参数
- **min-slaves-to-write**:这个配置项设置了**主库能进行数据同步的最少从库数量**;
- **min-slaves-max-lag**:这个配置项设置了**主从库间进行数据复制时,从库给主库发送ACK 消息的最大延迟**(以秒为单位)。
可以把 min-slaves-to-write 和 min-slaves-max-lag 这两个配置项搭配起来使用,分别给它们设置一定的阈值,假设为 N 和 T。这两个配置项组合后的要求是,主库连接的从库中至少有 N 个从库,和主库进行数据复制时的 ACK 消息延迟不能超过 T 秒,否则,主库就不会再接收客户端的请求了。
这个配置也会影响集群的可用性,假如slave挂掉少于1个的场景,这个集群就不能对外提供服务了。
redis集群为什么至少需要三个节点,是否必须是奇数(哨兵同理)
salve在选举过程中需要大于半数的集群master发送ack才能成功,如果集群只有两个master节点,其中一台挂掉,则无法正常进行选举。
不一定是奇数,偶数也可以但是推荐奇数。例如:当前有三个master节点,最多可以挂掉一个master节点我还能进行选举,挂两个master就无法进行正常选举。那么当横向扩容一个节点变成变成四个master节点,也是最多可以挂一个master,挂两个master,也无法达到半数以上选举条件。所以从可用性角度来讲四节点和三节点没有区别。