Redis 中采用了以下两种方式实现高可用:
- 主从复制;
- 采用Sentinel机制监控节点的运行情况,一旦主节点出现问题将由从节点顶上继续提供服务;
主从复制
Redis主从复制是异步同步的,主从复制双方都保存相同的数据,保证最终一致性。
复制过程
- slaveof <master_ip> <master_port>:通过向从服务器发送slaveof命令,可以让该从服务器去复制一个主服务器。从服务器会将master_ip和master_port保存在redisServer结构中。
- 从服务器根据master_ip和master_port创建连向主服务器的连接,连接成功从服务器将创建一个文件事件处理器专门用于处理该socket。
- 主服务器接受从服务器的连接请求后,为该socket创建相应的客户端状态。
- 从服务器向主服务器发送ping命令,检查socket是否正常、主服务器能否正常处理请求。
- 主服务器回复ping命令。
- 从服务器根据配置的masterauth选项决定是否进行身份验证,如果需要进行身份验证,将向主服务器发送一条auth命令,参数为masterauth选择的值。
- 从服务器向主服务器发送监听端口号。
- 主服务器收到从服务器的监听端口号后记录在从服务器对应客户端状态中。
- 从服务器向主服务器发送psync命令,执行同步操作。
- 主服务器进入命令传播阶段,将执行的写命令发送个从服务器,从服务器只要一直接收并执行主服务器发送的写命令。
主从复制步骤
- 同步
- 命令传播
主从同步过程
主从同步可以通过sync/psync命令实现。
sync命令
Redis 2.8之前使用sync命令进行主从复制,sync命令执行的是全量同步。
从服务器通过sync命令请求同步主服务器数据过程:
- 从服务器向主服务器发送sync命令;
- 主服务器收到sync命令,派生一个子进程执行bgsave生成一个RDB文件,并使用一个缓冲区记录从现在开始执行的所有写命令;
- 当bgsave执行完毕,主服务器将生成的RDB文件发送给从服务器;
- 从服务器接收并载入RDB文件;
- 主服务器将缓冲区中的所有写命令发送给从服务器;
- 从服务器执行写命令;
sync命令的缺点
- 每次sync命令主服务器都需要执行bgsave,消耗主服务器大量CPU、内存、磁盘I/O资源;
- 主服务器向从服务器发送RDB文件消耗主从服务器大量的网络资源;
- 从服务器载入RDB期间是阻塞操作,无法处理请求;
psync命令
Redis 2.8之后使用psync命令进行主从复制,psync命令分为完整重同步和部分重同步两种方式。
psync命令解决了sync命令每次都需要全量同步的问题。
完整从同步
完整重同步用于初次复制,执行步骤同sync命令。
部分重同步
部分重同步用于处理中断后重新同步。当从服务器重连主服务器时,主服务器只发送从服务器断开期间的所有写命令(sync命令需要发送整个RDB文件)。
部分重同步实现原理
服务器运行id
当从服务器对主服务器进行初次复制时,主服务器将自己的运行id发送给从服务器,从服务器会将该运行id保存,当从服务器重新连上主服务器时,向主服务器发送保存的运行id,主服务器根据该运行id校验从服务器中断之前连接的主服务器是否是自己,如果是,则执行部分重同步操作,如果不是,则执行完整重同步操作。
主/从服务器复制偏移量
主从复制的双方都会维护一个复制偏移量,通过这2个偏移量可以校验主从同步是否一致。
- 主服务器每次向从服务器发送n个字节数据,就将自己的复制偏移量+n;
- 从服务器每次收到主服务器发送的n个字节数据,就将自己的复制偏移量+n;
当从服务器重新连接上主服务器时,从服务器通过psync命令将自己的复制偏移量发送给主服务器,主服务器查找该偏移量之后的数据是否还存在复制积压缓冲区,如果存在,那么主服务器将对从服务器执行部分重同步操作,如果不存在,则执行完整重同步操作。
主服务器复制积压缓冲区
固定长度的先进先出队列,默认大小为1MB,保存最近发送的命令。当主服务器向从服务器发送命令时,也会将命令发向复制积压缓冲区。
Sentinel机制
Sentinel是Redis的高可用解决方案,本质上是一个运行在特殊模式下的Redis服务器。
由一个或多个Sentinel实例组成的Sentinel系统可以监视任意多个主服务器,以及这些主服务器的从服务器,并在被监视的主服务器下线时,自动将该主服务器的某个从服务器升级为主服务器,由新的主服务器代替已下线的主服务器处理命令请求。
Sentinel作用
- 集群监控:负责监控主服务器和从服务器进程是否正常工作;
- 消息通知:如果某个Redis实例有故障,那么Sentinel负责发送消息作为报警通知给管理员;
- 故障转移:如果主服务器挂掉了,会自动转移到从服务器上;
- 配置中心:如果故障转移发生了,通知客户端新的主服务器地址;
初始化Sentinel
- 初始化一个普通Redis服务器;
- 将普通Redis服务器使用的一部分代码替换成Sentinel专用代码,如使用的端口号、命令表等;
- 初始化Sentinel状态sentinelState结构;
- 根据配置文件初始化Sentinel的监视主服务器列表的sentinelState.masters字典,key为主服务器名称,key为主服务器对应的sentinelRedisInstance结构(sentinelRedisInstance结构会保存该服务器对应的IP地址和端口号),每个sentinelRedisInstance结构代表一个被Sentinel监视的Redis服务器实例(可以是主服务器、从服务器、Sentinel);
- 创建向主服务器的异步网络连接,Sentinel将成为主服务器的客户端,向主服务器发送命令获取相关的信息;
- 命令连接:用于发送/接收命令;
- 订阅连接:用于订阅主服务器的_sentinel_:hello频道;
Sentinel如何监控节点?
- 获取主服务器信息
Sentinel默认会每10秒一次的频率通过命令连接向被监视的主服务器发送info命令获取主服务器当前信息:
- 主服务器自身信息,如运行id等;
- 主服务器下的所有从服务器信息,如IP地址、端口号等, sentinelRedisInstance.slaves使用一个字典结构保存该主服务器下的从服务器,key为ip:port,value为从服务器对应的sentinelRedisInstance结构,Sentinel根据这些信息自动发现从服务器并监视;
- 获取从服务器信息
当Sentinel发现主服务器有新的从服务器时,Sentinel会为这个从服务器创建相应的sentinelRedisInstance结构,还会创建连接到从服务器的命令连接和订阅连接。创建命令连接后,Sentinel默认会每10秒一次的频率通过命令连接向从服务器发送info命令获取信息,如运行id、主服务器IP地址、主服务器端口号等。
Sentinel如何确认节点故障?
主观下线
在默认情况下,Sentinel会以每秒一次的频率向所有与它创建了命令连接的实例发送ping命令,通过ping命令的回复判断对方是否在线。
Sentinel根据配置的down-after-millseconds选项指定Sentinel判断实例进入主观下线需要的时间:如果一个实例在down-after-millseconds时间内连续向Sentinel返回无效回复,那么Sentinel就会将该实例sentinelRedisInstance.flags的SRI_S_DOWN标识打开,标识该实例已进入主观下线状态。
客观下线
当Sentinel将一个主服务器判定为主观下线后,它会向一同监视该主服务器的其他Sentinel发送命令询问,看它们是否也认为该主服务器为下线状态(主观/客观下线),当Sentinel收到超过配置的quorum值的已下线判断,Sentinel会将该主服务器判定为客观下线,将该主服务器sentinelRedisInstance.flags的SRI_O_DOWN标识打开,标识该服务器进入客观下线状态,并对该主服务器执行故障转移操作。
选举领头Sentinel
当一个主服务器被判定为客观下线时,监视这个主服务器的各个Sentinel会协商选举出一个领头Sentinel对主服务器执行故障转移操作。
规则
- 每次进行领头选举,不管是否成功,所有Sentinel的纪元+1;
- 在一个纪元里,所有Sentinel只有一次将某个Sentinel设置为局部领头的机会,一旦设置在该纪元内将不能修改;
- 每个发现主服务器进入客观下线的Sentinel都会要求其他Sentinel将自己设置为局部领头,设置局部领头的规则是先到先得;
步骤
- 源Sentinel向目标Sentinel发送is-master-down-by-addr命令;
- 最先向目标Sentinel发送is-master-down-by-addr命令的源Sentinel会被目标Sentinel设置为局部领头,并向源Sentinel发送命令回复(包含leader_runid局部领头运行id、纪元);
- 源Sentinel收到目标Sentinel的回复,如果目标Sentinel的leader_runid、纪元和自己一致,则表示目标Sentinel将自己设置为了局部领头,计数+1;
- 如果某个Sentinel被半数以上的Sentinel设置成局部领头,那么这个Sentinel将成为领头Sentinel;
Sentinel机制基于Raft算法进行选举领头Sentinel。
故障转移
在选举出领头Sentinel后,领头Sentinel将对已下线的主服务器执行故障转移。
故障转移步骤
- 在已下线主服务器的所有从服务器中选出一个从服务器转为新的主服务器(Sentinel通过向从服务器发送info命令查看服务器的角色);
- 将已下线主服务器的所有从服务器改为同步新的主服务器(向所有从服务器发送slave命令);
- 将已下线主服务器设置为新的主服务器的从服务器;
选择哪个从服务器作为新的主服务器?
- 删除所有处于已下线或断线状态的从服务器,保证剩余从服务器都是正常在线;
- 删除所有最近5秒没有回复过领头Sentinel info命令的从服务器,保证剩余从服务器都是最近成功进行过通信的;
- 删除所有与已下线主服务器连接断开超过[down-after-millseconds*10]毫秒的服务器,保证剩余的从服务器数据都是比较新的;
- 领头Sentinel根据从服务器的优先级,对剩余从服务器进行排序,选择优先级最高的;
- 如果最高优先级有多个从服务器,则选择复制偏移量最大的从服务器,复制偏移量最大的从服务器保存着最新数据;
- 如果存在多个最高优先级、最大复制偏移量的从服务器,则选择运行id最小的从服务器;