Redis设计与实现(三)–主从和集群
Redis主从复制
在Redis中,用户可以通过执行slaveof命令或者设置slaveof选项,让一个服务器去复制一个主服务器的数据。slaveof命令只是设置主从关系,并不触发复制动作。
slaveof host(主服务ip) port(主服务器端口)
Redis使用psync命令实现从服务器复制主服务器数据的功能。psync有完整重同步和部分重同步两种模式:
1.完整重同步用于处理初次复制情况,完整重同步通过让主服务器创建并发送完整的RDB文件,以及向从服务器发送保存在缓冲区里面的写命令来进行同步。
2.部分重同步用于出里断线后的重辅助情况:当从服务器在断线后重连主服务器是,如果条件容许,主服务器可以将从服务器连接断开期间执行的写命令发送给从服务器,从服务器执行这些写命令就可以保证和主服务器的数据一致。
部分重同步的实现:
1.主服务器的复制偏移量和从服务器的复制偏移量。
2.主服务器的复制积压缓冲区。
3.服务器运行id。(从服务器初次复制时会保留主服务器传递过来的主服务器id)
同步策略的选择:从服务器发送psync命令,会携带自己的复制偏移量。主服务器接受到命令之后会先检查从服务器的偏移量,如果需要复制的数据都在缓存区,就采用部分重同步。否则,采用完整重同步。
slaveof命令执行步骤:
1.设置主服务器的地址和端口,保存在本地。
struct redisServer{
//主服务器地址
char *masterhost;
//主服务器端口号
int masterport;
//…
}
2.建立套接字连接:通过步骤1获取的主服务器的地址和端口建立套接字连接。
从服务器创建的套接字能成功连接(connect)到主服务器,那么从服务器将为这个套接字关联一个专门用于处理复制工作的文件事件处理器,这个处理器将负责执行后续的复制工作,比如接收RDB文件,以及接收主服务器传播来的写命令,诸如此类。而主服务器在接受(accept)从服务器的套接字连接之后,将为该套接字创建相应的客户端状态,并将从服务器看作是一个连接到主服务器的客户端来对待,这时从服务器将同时具有服务器(server)和客户端(client)两个身份:从服务器可以向主服务器发送命令请求,而主服务器则会向从服务器返回命令回复。
3.发送ping命令
从服务器成为主服务器的客户端之后,向主服务器发送配ping命令。通过发送ping命令可以检查套接字的读写状态是否正常,主服务器能否正常处理命令请求。
4.身份认证
如果主服务器设置了requirepass参数且从服务器设置了masterauth参数,从服务器会想主服务器发送auth passwd进行身份认证。
5.发送端口信息
从服务器想主服务发送自己的端口信息,主服务器收到命令后会将端口保存在客户端信息中:
struct redisClient{
//客户端监听端口号
int slave_listening_port;
}
6.同步
7.命令传播
Sentinel集群
Sentinel(哨岗、哨兵)是Redis的高可用性(highavailability)解决方案:由一个或多个Sentinel实例(instance)组成的Sentinel系统(system)可以监视任意多个主服务器,以及这些主服务器属下的所有从服务器,并在被监视的主服务器进入下线状态时,自动将下线主服务器属下的某个从服务器升级为新的主服务器,然后由新的主服务器代替已下线的主服务器继续处理命令请求。
双环:主服务器 单环:从服务器
服务器下线检查:主观下线检查,客观下线检查
主观下线:sentinel系统会以每秒一次的平率想所有与它创建的命令连接实例(主服务器,从服务器,其他sentinel在内)发送ping命令,通过ping命令判断是否在线。如果在down-after-milliseconds时间段内,连续向sentinel返回无效回复,那么sentinel会标记该实例已经下线。打开实例的sri_s_down标识,表示该实例已经主观下线。
服务器客观下线:当Sentinel将一个主服务器判断为主观下线之后,为了确认这个主服务器是否真的下线了,它会向同样监视这一主服务器的其他Sentinel进行询问,看它们是否也认为主服务器已经进入了下线状态(可以是主观下线或者客观下线)。当Sentinel从其他Sentinel那里接收到足够数量的已下线判断之后,Sentinel就会将从服务器判定为客观下线,并对主服务器执行故障转移操作。
选举领头sentinel:当一个主服务器被判断为客观下线时,监视这个下线主服务器的各个Sentinel会进行协商,选举出一个领头Sentinel,并由领头Sentinel对下线主服务器执行故障转移操作。只有得到的票数多余sentinel是个数的一半,该sentinel会被选为领头sentinel。
故障转移:1)在已下线主服务器属下的所有从服务器里面,挑选出一个从服务器,并将其转换为主服务器。
2)让已下线主服务器属下的所有从服务器改为复制新的主服务器。
3)将已下线主服务器设置为新的主服务器的从服务器,当这个旧的主服务器重新上线时,它就会成为新的主服务器的从服务器。
从服务器选为主服务器优先级:1.删除已下线或断线的从服务器。2.删除列表中醉经5s没有回复过领头sentinel的info命令的从服务器。3.选择偏移量最大的从服务器。
注:选举策略参考Raft算法
cluster 集群
Redis集群是Redis提供的分布式数据库解决方案,集群通过分片来进行数据共享,并提供复制和故障转移功能。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VE0p73aZ-1589194354448)(C:\Users\Administrator\Desktop\10.jpg)]
1.节点
一个Redis集群通常有多个节点组成,刚开始时,没给我节点都相互独立,处于自己的集群中,需要执行cluster meet 将一个节点加入到工作集群中。集群的数据结构如下:
truct clusterNode{
//创建节点的时间
mstime_t ctime;
//节点的名字,由40个十六进制字符组成例如68eef66df23420a5862208ef5b1a7005b806f2ff
char name[REDIS_CLUSTER_NAMELEN];
//节点标识
//使用各种不同的标识值记录节点的角色(比如主节点或者从节点),
//以及节点目前所处的状态(比如在线或者下线)。
int flags;
//节点当前的配置纪元,用于实现故障转移
uint64_t configEpoch;
//节点的IP地址
char ip[REDIS_IP_STR_LEN];
//节点的端口号
int port;
//保存连接节点所需的有关信息
clusterLink *link;
//…
};
typedef struct clusterLink{
//连接的创建时间
mstime_t ctime;
//TCP套接字描述符
int fd;
//输出缓冲区,保存着等待发送给其他节点的消息(message)。
sds sndbuf;
//输入缓冲区,保存着从其他节点接收到的消息。
sds rcvbuf;
//与这个连接相关联的节点,如果没有的话就为NULL
struct clusterNode* node;
}clusterLink;
每个节点都保存这一个clusterState结构,clusterState结构如下:
typedef struct clusterState{
//指向当前节点的指针
clusterNode myself;
//集群当前的配置纪元,用于实现故障转移
uint64_t currentEpoch;
//集群当前的状态:是在线还是下线
int state;
//集群中至少处理着一个槽的节点的数量
int size;
//集群节点名单(包括myself节点)
//字典的键为节点的名字,字典的值为节点对应的clusterNode结构
dict nodes;
//…
.}clusterState;
如果集群中添加了三个节点(7000,7001,7002),则该结构如下图所示:
2.分槽
Redis集群通过分片的方式来保存数据库中的键:集群中的整个数据被分为16384个槽,数据中键都属于16384个槽中的一个。因此,Redis最多也只能构建16384个节点。当数据库中的所有槽被分配了,集群才能处于运行状态。节点分配槽后,会将分配信息传播给其他节点。
当客户端想节点发送与数据库键有关的命令时,接受客户端命令的节点会先计算出数据库键属于那个槽,并检查槽是否属于自己,若键正好指派给了当前节点,那么节点直接执行这个命令。如果键所在的槽并没有分配给这个节点,那么节点会向客户端返回一个moved错误,指引客户端专项至正确的节点,并发送之前想要执行的命令。
接受命令的节点会先计算出数据库键属于那个槽,并检查槽是否属于自己,若键正好指派给了当前节点,那么节点直接执行这个命令。如果键所在的槽并没有分配给这个节点,那么节点会向客户端返回一个moved错误,指引客户端专项至正确的节点,并发送之前想要执行的命令。