编辑推荐: |
本文来自于csdn,文章是从数据库、RDB持久化、AOF持久化、事件、客户端、服务器等方面是介绍的。 |
|
一、复制
slaveof命令让一个从服务器去复制主服务器。
(一)旧版复制功能的实现
redis的复制功能分为同步(sync,将从服务的状态更新至主服务器当前所处的数据库状态)和命令传播(command
propagate,主服务器的数据库状态被修改,导致主从服务器的数据库状态出现不一致时,让主从服务器的数据库状态重新回到一致状态)两个操作。
1、同步
主服务器生成RDB文件,并将在生成RDB文件期间的执行命令存储在缓冲区,发送RDB文件到从服务器,发送缓冲命令到从服务器。
2、命令传播
主服务器将自己的执行命令发送给从服务器。
(二)旧版复制功能的缺陷
复制分为两种:初次复制和断线后重复制。旧版复制在断线后只能够进行执行sync命令,所以效率非常低。因此redis有必要保证在真正有需要时才执行SYNC命令。
(三)新版复制功能的实现
PSYNC命令代替SYNC命令,具有完整重同步和部分重同步两种模式。
完整重同步用于处理初次复制的情况,部分重同步用于处理断线后重复制情况。
(四)部分重同步的实现
部分重同步功能是由一下三个部分构成:主从服务器复制偏移量、主从服务器复制积压缓冲区(固定长度的FIFO队列,默认为1MB,具体可以参考配置文件的repl-backlog-size)、服务器的运行ID(主服务器发送运行ID给连接的从服务器,从服务器断开之后根据之前保存的ID对比现在连接的ID,如果相同则尝试部分重同步,不相同则进行完整重同步)。
(五)PSYNC命令的实现
从服务器发送PSYNC ? -1表示请求完整重同步;PSYNC < runid> <
offset> 命令,主服务器判断返回+FULLRESYNC < runid> <
offset> 回复表示执行完整重同步,返回+CONTINUE表示执行部分重同步,-ERR表示主服务器版本低于2.8,不能识别PSYNC命令。
(六)复制的实现
1、设置主服务器的地址和端口,保存到从服务器状态的masterhost属性和masterport属性里面。SLAVEOF是个异步命令,从服务器设置地址和端口之后马上就返回ok。
2、建立套接字连接:从服务器connect之后将为这个套接字关联一个专门用于处理复制工作的文件事件处理器;主服务器accept之后将为套接字创建相应的客户端状态,并将从服务当做一个连接到主服务器的客户端来对待,这时从服务器将同时具有服务器和客户端两个身份,从服务器可以向主服务器发送命令请求,而主服务器会返回命令回复。
3、发送PING命令
一个检查套接字读写状态是否正常,一个检查主服务器能否正常处理命令请求。
主服务器回复: 返回命令,但从服务器不能再规定的时间内读出命令回复的内容, 说明主从服务器网络连接不佳,断开重新创建;返回错误,断开重新创建,比如说主服务器正在执行一个超时运行的脚本;返回PONG,则网络正常,可以进行下面的步骤。
4、身份验证(如果从服务器设置了masterauth选项,那么进行身份验证,反之则不进行)
身份验证的几种情况:主服务器没有设置requirepass选项,并且从服务器也没有设置masterauth选项,复制工作继续;主服务器设置了requirepass选项,并且从服务器也设置了masterauth选项,密码相同则复制工作继续,密码不同则返回invalid
password错误;主服务器设置而从服务器未设置,则返回NOAUTH错误;从服务器设置而主服务器未设置则返回no
password is set错误。所有的错误都会令从服务器终止目前的复制工作,并从创建套接字开始重新执行,直到通过身份验证或从服务器放弃执行复制为止。
5、发送端口信息
身份验证之后,从服务器将执行命令REPLCONF listening-port < port-number>,向主服务器发送从服务器的监听端口号,主服务器接收到这个命令之后会将端口号记录在从服务器所对应的客户端状态的slave_listening_port属性当中,此属性的唯一作用是用在主服务器执行INFO
replication命令时打印出从服务器的端口号。
6、同步
从服务器发送PSYNC命令,执行同步操作,并将自己的数据库更新至主服务器数据库当前所处的状态。执行同步操作之后,主服务器也会成为从服务器的客户端,这样才能发送缓冲区的写命令或者复制积压缓冲区里面的写命令给从服务器。在同步操作执行之后,主从服务器双方都是对方的客户端,可以相互向对方发送命令请求,或者互相向对方回复命令。
7、命令传播
完成同步之后,主从服务器就会进入命令传播阶段,这时主服务器一直将自己执行的写命令发送给从服务器就可以了。
(七)心跳检测
在命令传播阶段,从服务器会 以每秒一次的频率向主服务器发送命令:REPLCONF ACK <
replication_offset>(从服务器的当前复制偏移量)。
这个命令的作用有三个:
检查主从服务器的网络连接状态(超过1s说明主从服务器之间的连接出现了故障);
辅助实现min_slaves选项(redis的min_slaves_to_write和min_slaves_max_lag两个选项可以防止主服务器在不安全的情况下执行写命令。从服务器数量小于min_slaves_to_write个或者三个服务器的延迟值大于等于min_slaves_max_lag个,主服务器将拒绝执行写命令);
检查命令丢失(通过比较主从服务器的当前复制偏移量就可以知道是否有命令传输丢失,如果丢失就进行重传)。
二、Sentinel
Sentinel是redis高可用性(high availability)解决方案:一个或多个Sentinel实例组成的Sentinel系统可以监视多个主服务器,并在主服务器下线时间超过用户设定的下线时长上限之后,自动将下线主服务器属下的某个从服务器升级为主服务器,并且会继续监视已下线的服务器,并在它重新上线之后将它设置为新的主服务器。
(一)、启动并初始化Sentinel
启动sentinel的命令:redis-sentinel /path/sentinel.conf或者
redis-server /path/sentinel.conf –sentinel(两个命令效果一样)
当一个sentinel启动时,步骤如下:
(1)初始化服务器
(2)将普通redis服务器使用的代码替换成sentinel专用代码
(3)初始化sentinel状态
(4)根据给定的配置文件,初始化sentinel的监视主服务器列表
(5)创建连向主服务器的网络连接
1、初始化服务器
sentinel不适用数据库,因此初始化时不用载入RDB或者AOF文件;sentinel模式下有些功能不适用,有些功能仅在内部使用,有些功能客户端和内部都可以使用。
2、使用sentinel专用代码
3、初始化sentinel状态
初始化一个sentinel.c/sentinelState结构,
4、初始化sentinel状态的masters属性
sentinel状态的masters字典记录了所有被sentinel监视的主服务器的相关信息,
5、创建连向主服务器的网络连接
sentinel会创建两个连向主服务器的一部网络连接:一个是命令连接,一个是订阅连接。
(二)获取主服务器信息
(三)获取从服务器信息
(四)向主服务器和从服务器发送信息
(五)接收来自主服务器和从服务器的频道信息
(六)检测主观下线状态
PING
(七)检查客观下线状态
SENTINEL is-master-down-by-addr
(八)选举领头sentinel
Raft算法
(九)故障转移
SLAVEOF no one
三、集群
redis集群是redis提供的分布式数据库方案,集群通过分片(sharding)来进行数据共享,并提供复制和故障转移功能。包括:节点、槽指派、命令执行、重新分片、转向、故障转移、消息等。
(一)节点
CLUSTER MEET < ip> < port> 节点连接起来,构成一个多节点的集群
1、启动节点
节点继续使用单机模式的服务器组件,只是serverCron函数会调用集群模式特有的clusterCron函数,执行集群的常规操作,例如向集群的其他节点发送Gossip消息,检查节点是否断线,或者检查是否需要对下线的节点进行故障转移操作等。集群用到的数据保存在cluster.h/clusterNode、cluster.h/clusterLink、cluster.h/clusterState结构里面。
2、集群数据结构
clusterNode保存节点状态、clusterLink保存连接节点所需的有关信息(套接字符、输入输出缓冲区等)、clusterState记录当前节点的视角下集群所处的状态(例如集群是上线还是下线、集群包含了多少个节点、集群当前的配置纪元等)
3、CLUSTER MEET命令的实现
(二)槽指派
集群的整个数据库被分为16384个槽(slot),集群每个节点可以处理0个或者16384个槽。CLUSTER
ADDSLOTS slot … 将槽指派给节点
1、记录节点的槽指派信息
clusterNode结构中的slots属性(2048字节数组,共16384位,二进制位为1表示处理该槽)和numslot
属性(处理槽的数量)记录了节点负责处理哪些槽。
2、传播节点的槽指派信息
节点向其他节点发送消息告知自己处理的槽,其他节点收到消息并在clusterState.nodes字典中查找发送消息节点的clusterNode结构中的slots数组进行保存和更新。
3、记录集群所有槽的指派信息
clusterState里面有个slots数组(clusterNode*),记录了集群中所有16384个槽的指派信息。为空表示未指派,不为空存储的是节点的指针地址。Node和State的两个slots数组都是必要的。
4、CLUSTER ADDSLOTS命令的实现
先遍历输入的槽,只要有一个是已经指派就返回错误;如果所有槽都没有指派,那么再次遍历所有槽,将这些槽指派给当前节点。
(三)在集群中执行命令
在所有的槽都指派完毕之后,集群就会进入上线状态,这是客户端就可以向集群中的节点发送数据命令了。客户端向节点发送与数据库键相关的命令时,如果键所在的槽正好就指派给了当前节点,那么节点就直接执行命令;如果键所在的槽并没有指派给当前节点,那么节点返回一个MOVED错误,指引客户端(redirect)至正确节点,并再次发送之前想要执行的命令。
1、计算键属于哪个槽
CLUSTER KEYSLOT key(查看一个给定键属于哪个槽);
2、判断槽是否由当前节点负责处理
检查clusterState.slots数组中的项i,判断键所在的槽是否由自己负责:clusterState.slots[i]==clusterState.myself节点就继续执行客户端发送的命令;不相等节点则会根据clusterState.slots[i]指向的clusterNode结构所记录的节点Ip和端口号向客户端返回MOVED错误,指引客户端转向至正在处理槽i的节点。
3、MOVED错误
MOVED错误的格式为:MOVED < slot> < ip>:< port>
4、节点数据库的实现
节点只能使用0号数据库,而单机redis服务器没有这个限制;节点还会用clusterState结构中的slots_to_keys
跳跃表来保存键和槽之间的关系。
CLUSTER GETKEYSINSLOT < slot> < count>
命令可以返回最多count个属于槽slot的数据库键。
(四)重新分片
redis集群的重新分片操作可以将任意数量已经指派给某个节点的槽改为指派给另一个节点,并且相关的槽所属的键值对也会从源节点转移到目标节点。
重新分片的实现原理
redis的重新分片操作时由redis的集群管理软件redis-trib负责执行的,redis提供了进重新分片所需的所有命令,而redis-trib则通过向源节点和目标节点发送命令来进行重新分片操作。步骤如下:
1、redis-trib对目标节点发送CLUSTER SETLOT < slot > IMPORTING
< source_id>
2、redis-trib对源节点发送CLUSTER SETLOT < slot> MIGRATING
< target_id >
3、redis-trib对源节点发送CLUSTER GETKEYSINSLOT < slot
> < count >
4、redis-trib对源节点发送MIGRATE < key_name> 0 <
timeout>
5、重复3和4,知道槽中的键值对迁移到目标节点
6、redis-trib向任意节点发送CLUSTER SETLOT < slot> NODE
< target_id>,将槽指派给目标节点,并通过消息告知整个集群,最终所有节点都会知道槽slot已经指派给了目标节点。
(五)、ASK错误
在重新分片期间,可能出现客户端访问的键在正在迁移的槽中,如果这个键不在源节点的数据库中,那么向客户端返回ASK错误并转向到目标节点,并再次执行想要执行的命令。
1、CLUSTER SETLOT IMPORTING命令的实现
clusterState结构中的importing_slots_from(clusterNode*)数组记录了当前节点正在从其他节点导入的槽,当redis-trib对目标节点发送CLUSTER
SETLOT < slot > IMPORTING < source_id>时,clusterState结构中的importing_slots_from数组对应的数组元素就被置为源节点的clusterNode结构。
2、CLUSTER SETLOT MIGRATING命令的实现
clusterState结构中的migrating_slots_to(clusterNode*)数组记录了当前节点正在迁移至其他节点的槽,当redis-trib对源节点发送CLUSTER
SETLOT < slot> MIGRATING < target_id >,migrating_slots_to[i]的值就是指向的目标节点。
3、ASK错误
转向目标节点,发送一个ASKING命令,再发送原本要执行的命令。
4、ASKING命令
ASKING命令唯一要做的就是打开发送该命令的客户端的REDIS_ASKING标识,这个命令时一次性的,在执行后面发送的命令之后这个标识将会被移除。
5、ASK错误和MOVED错误的区别
都会导致客户端转向,一个是负责权的转移,一个是迁移槽过程的临时措施。
(六)、复制与故障转移
redis集群中的节点分为主节点和从节点,其中主节点用于处理槽,而从节点则用于复制主节点,并在被复制的主节点下线之后代替下线的主节点继续处理命令请求。
1、设置节点
CLUSTER REPLICATE < node_id> 命令可以让接受命令的节点成为node_id所指定的节点的从节点,并开始对主节点进行复制。接收到该命令的节点首先会在自己的clusterState.nodes字典里面找到node_id对应的节点clusterNode结构,并将自己的clusterState.myself.slaveof指针指向这个结构;节点会修改自己clusterState.myself.flags中的属性,关闭原来的REDIS_NODE_MASTER标识,打开REDIS_NODE_SLAVE标识;调用复制代码,执行对主节点的复制操作。
主节点的clusterNode结构中保存着slaves和numslaves属性,记录了从节点的clusterNode结构指针和从节点的数量。
2、故障检测
集群中的每个节点都会定期地向集群中的其他节点发送PING消息,如果规定时间内没有返回PONG,发送消息的节点就会把接受消息的节点标记为疑似下线PFAIL。clusterNode的flags标识(REDIS_NODE_PFAIL)。节点还会在自己的clusterState.nodes字典里面找到疑似下线节点的clusterNode结构,并将通知当前节点下线节点疑似下线的主节点的下线报告添加到fail_reports链表里面。
如果半数以上的负责处理槽的主节点都将某个主节点报告为疑似下线,那么这个主节点将会被标记为已下线(FAIL),将疑似下线节点标记为下线节点的主节点会向集群广播一条下线主节点FAIL消息,所有收到这条FAIL消息的节点都会立即将该主节点标记为已下线。
3、故障转移
当一个从节点发现自己复制的主节点进入了下线状态的时候,从节点将开始对下线主节点进行故障转移,步骤如下:
1)选举新的主节点
2)新的主节点执行SLAVEOF no one命令,成为新的主节点
3)新的主节点将下线主节点的槽指派给自己
4)新的主节点向集群广播PONG消息,表明自己接管了原来下线节点的槽
5)新的节点开始接收和自己复制处理槽有关的命令请求。
4、选举新的主节点
选举领头sentinel的方法相似,基于Raft算法的领头选举来实现。
(七)消息
消息有发送者(sender)和接收者(receiver)。
节点消息分为5类:MEET、PING、PONG、FAIL、PUBLISH。
消息由消息头(header)和消息正文(data)组成。
1、消息头
cluster.h/clusterMsg结构表示消息头,cluster.h/clusterMsgData联合体指向消息的正文。
2、MEET、PING、PONG的消息实现
redis集群中的各个节点通过Gossip协议来交换各自关于不同节点的状态信息,其中Gossip协议由MEET、PING、PONG三种消息实现,三种消息的正文都是由两个cluster.h/clusterMsgDataGossip结构组成。
3、FAIL消息的实现
Gossip协议传播节点的已下线消息会有一定的延迟,采用FAIL消息能够让集群的所有节点立刻知道某个节点已下线。FAIL消息的正文由cluster.h/clusterMsgDataFail结构表示,只有一个nodename属性,记录已下线节点的名字。
4、PUBLISH消息的实现
客户端向某个集群的节点发送PUBLISH消息时,接受命令的节点还会向集群广播一条PUBLISH消息,PUBLISH消息的正文由cluster.h/clusterMsgDataPublish结构表示:频道号长度、消息长度、频道号和消息组成的字节数组。
没有用广播PUBLISH是因为这种做法不符合redis集群的“各个节点通过发送和接收消息来进行通信”这一规则。 |