复制功能相关数据结构
服务器全局变量中复制相关的数据结构
Redis在redisServer
这个数据结构之中,存储了一系列用于维护复制功能相关的字段。而服务器全局数据之中的数据字段可以被划分为Master实例与Slave实例所应用的数据。
Master实例关注的数据
在Master实例上所有连接在这个服务器上的Slave实例节点,被存储在一个双端链表之中:
struct redisServer
{
list *slaves;
};
复制
除此之外,Master实例上关于复制机制的相关数据可以被分为三个部分:
struct redisServer
{
char replid[CONFIG_RUN_ID_SIZE+1];
char replid2[CONFIG_RUN_ID_SIZE+1];
long long master_repl_offset;
long long second_replid_offset;
int slaveseldb;
char* repl_backlog;
long long repl_backlog_size;
long long repl_backlog_histlen;
long long repl_backlog_idx;
long long repl_backlog_off;
int repl_ping_slave_period;
time_t repl_backlog_time_limit;
time_t repl_no_slaves_since;
int repl_min_slaves_to_write;
int repl_min_slaves_max_lag;
int repl_good_slaves_count;
int repl_diskless_sync;
int repl_diskless_sync_delay;
};
复制
Master实例自身数据的维护:
redisServer.replid
,用于存储Master实例自身的复制ID,这是一个40位的随机字符串,用于对不同的Redis实例进行区分。redisServer.master_repl_offset
,用于记录前面所提到的Master实例的复制偏移量。每次Master实例将命令写入到积压缓冲区之中的时候,会增加这个master_repl_offset
复制偏移量。redisServer.slaveseldb
,用于记录上一次执行复制命令对应的键空间编号;可以对应理解redisServer.aof_selected_db
这个字段的作用。Master实例上积压缓冲区:
redisServer.repl_backlog
,Master上的积压缓冲区,这个积压缓冲区是使用malloc
分配的一段固定的连续内存。这个积压缓冲区是循环复用的,当写入的数据已经达到积压缓冲区的尾部时,新的数据会从积压缓冲区头部开始写入,并覆盖积压缓冲区之中的旧数据。redisServer.repl_backlog_size
,积压缓冲区的长度。redisServer.repl_backlog_histlen
,这个用于记录积压缓冲区之中实际数据的长度,这个字段数值的长度不会超过repl_backlog_size
规定的上限。redisServer.repl_backlog_idx
,用于标记循环积压缓冲区之中当前的数据偏移,如果有新的数据要被写入积压缓冲区的话,将会从repl_backlog_idx
的下一个字节开始数据写入。redisServer.repl_backlog_off
,积压缓冲区数据偏移量,这个字段用于记录积压缓冲区中有效数据的第一个字节对应于master_repl_offset
的偏移量。Master实例上关于Slave的统计与配置数据:
redisServer.repl_backlog_time_limit
,用于记录积压缓冲区的最大生存时间,如果Master实例长时间没有Slave实例连接,则会将积压缓冲区释放。redisServer.repl_ping_slave_period
,用于配置Master实例与Slave实例发送PING命令的时间间隔。redisServer.repl_no_slaves_since
,如果Master实例上的所有Slave实例都已经断开连接,那么这个字段记录Master上开始没有Slave连接的开始时间。redisServer.repl_min_slaves_to_write
,配置Master实例上所需要的最小Slave实例数量redisServer.repl_min_slaves_max_lag
,redisServer.repl_good_slaves_count
,记录Master实例上,当前处于good状态的Slave实例数量。redisServer.repl_diskless_sync
,配置Master实例上,是否使用启用通过网络的无盘复制方式。redisServer.repl_diskless_sync_delay
,配置Matser实例上启动无盘复制的延时时间,如一个Slave节点请求了一次无盘复制,这是Master将会延时一段时间在开启RDB数据的传输,这样如果在这段延时时间内,另外一个Slave节点也来请求一次无盘的数据同步,Master可以仅开启一次生成RDB文件的处理逻辑。
Slave实例关注的数据
在Slave实例上,Redis则是会一个单独的client
对象来记录其对应的Master实例:
struct redisServer
{
client* master;
client* cached_master;
};
复制
前面已经介绍了,Master实例与Slave实例互为彼此的一个客户端,因此这里redisServer.master
字段作为Master实例在Slave实例的一个客户端记录了Matser与Slave之间的连接信息。同时为了实现前面提到的主从之间的增量数据同步,需要在连接断开时,对redisServer.master
的信息进行缓存,而这个缓存的Master客户端对象将会被存储在redisServer.cached_master
字段之中。
struct redisServer
{
char *masterauth;
char *masterhost;
int masterport;
int repl_timeout;
int repl_syncio_timeout;
int repl_state;
off_t repl_tarnsfer_size;
off_t repl_transfer_read;
off_t repl_transfer_last_fsync_off;
int repl_transfer_s;
int repl_transfer_fd;
char *repl_transfer_tmpfile;
time_t repl_transfer_lastio;
int repl_serve_stale_data;
int repl_slave_ro;
int repl_slave_ignore_maxmemory;
time_t repl_down_since;
int repl_disavle_tcp_nodelay;
int slave_priority;
int slave_announce_port;
char *slave_announce_ip;
};
复制
另外还有一些数据字段用于维护Slave实例端的复制机制:
redisServer.masterhost
,在Slave实例一侧,记录Master主机所在的地址,通常在代码里通过这个字段是否为空来判断当前的Redis是否是主从模式之中的一个Slave实例。redisServer.masterport
,在Slave一侧,记录Master对应的端口。redisServer.repl_syncio_timeout
,在Slave实例一侧,在建立连接时,向Master实例发送内部命令是采用阻塞整个进程的同步IO方式进行传输的,repl_syncio_timeout
这个字段用于设定每次同步IO的超时时间。redisServer.repl_state
,用于记录在主从模式之中,当前Slave实例所处于的状态:REPL_STATE_NONE
,表示当前服务器没有处于或者发起准备建立Slave的状态。REPL_STATE_CONNECT
,通过配置或者REPLICAOF命令设置了Master地址信息之后,Slave实例会被置为这个状态,这个状态表示Slave将要与Master实例建立连接。REPL_STATE_CONNECTING
,当Slave已经设置了地址信息之后,会通过anetTcpNonBlockBestEffortBindConnect
函数接口建立与Master实例的连接,当连接建立并注册到事件循环之后,Slave节点将会被设置成PEPL_STATE_CONNECTING
这个状态。REPL_STATE_RECEIVE_PONG
,由于连接是以非阻塞的方式建立的,因此第一次从事件循环之中返回时,表明连接已经被建立,此时Slave会向Master同步发送一条PING命令,并将状态设置为REPL_STATE_RECEIVE_PONG
,以表明自己处于等待接受Master返回的状态。REPL_STATE_SEND_AUTH
,这个状态表示Slave实例将要将Master发送认证命令AUTH。REPL_STATE_RECEIVE_AUTH
,这个状态表示Slave实例已近发送送完AUTH命令,正在等待Master的返回。REPL_STATE_SEND_PORT
,表示Slave实例将要向Master实例通过REPLCONF发送自己的端口数据。REPL_STATE_RECEIVE_PORT
,这个状态表示Slave已经通过REPLCONF命令向Master告知了自己的端口数据,正在等待Master的返回。REPL_STATE_SEND_IP
,表示Slave实例将要向Master实例通过REPLCONF发送自己的端口数据。REPL_STATE_RECEIVE_IP
,这个状态表示Slave已经通过REPLCONF命令向Master告知了自己的IP数据,正在等待Master的返回。REPL_STATE_SEND_CAPA
,表示Slave实例将要向Master实例通过REPLCONF发送自己的CAPA信息。REPL_STATE_RECEIVE_CAPA
,这个状态表示Slave已经通过REPLCONF命令向Master告知了自己的CAPA信息。正在等待Master的返回。REPL_STATE_SEND_PSYNC
,表示Slave实例将要向Master实例发送PSYNC命令以开启数据同步过程。REPL_STATE_RECEIVE_PSYNC
,Slave发送完PSYNC命令后,等待Master实例的返回。REPL_STATE_TRANSFER
,数据同步逻辑开始执行,Slave实例处于该状态,并开始从连接上获取Master发送到的同步数据。REPL_STATE_CONNECTED
,数据同步逻辑结束,Slave将切换至该状态,后续将接受Master实例转发来的命令数据,用以维护数据的一致性。redisServer.repl_transfer_size
,对于使用无盘的数据同步,该字段将会被设置为0;而对于有盘的数据同步,该字段为整个待传输的RDB文件的大小。redisServer.repl_transfer_read
,用于记录Slave实例从网络连接上已经接收到的数据的数量。redisServer.repl_transfer_last_fsync_off
,由于Slave实例收到的同步数据需要先写入磁盘,因此这个字段用于记录上次被写入到磁盘中的数据的偏移。redisServer.repl_transfer_s
,用于记录Slave与Master之间连接的套接字文件描述符。redisServer.repl_transfer_fd
,用于记录Slave实例接收同步数据的临时RDB文件的文件描述符。redisServer.repl_transfer_tmpfile
,记录临时RDB文件的文件名。redisServer.repl_transfer_lastio
,记录Slave实例上一次接收同步数据的时间戳。
客户端对象中复制相关的数据结构
因为在Redis的复制机制之中,Master实例与Slave实例彼此将对方看做一个特殊的客户端,因此在client
这个表示客户端的数据结构之中也存储了一系列用于维护复制功能相关的数据字段。
struct client
{
int replstate;
int repl_put_online_on_ack;
int repldbfd;
off_t repldboff;
off_t repldbsize;
sds replpreamble;
long long read_reploff;
long long reploff;
long long repl_ack_off;
long long repl_ack_time;
long long psync_initial_offset;
char replid[CONFIG_RUN_ID_SIZE + 1];
int slave_listening_port;
char slave_ip[NET_IP_STR_LEN];
int slave_capa;
long long woff;
};
复制
client.replstate
,这个字段用于从Master的视角来看Slave对应客户端的状态:SLAVE_STATE_WAIT_BGSAVE_START
,对应的Slave已经请求数据同步,正在等待Master开启RDB的生成逻辑。SLAVE_STATE_WAIT_BGSAVE_END
,对应Slave同步请求的RDB文件生成流程已经开始执行,正在等待RDB文件生成结束。SLAVE_STATE_SEND_BULK
,Master正在想这个client
对应Slave实例发送RDB同步数据。SLAVE_STATE_ONLINE
,Master已经向这个client
对应的Slave实例完成发送RDB文件,开始进入命令转发的流程。client.repldbfd
,Master用于同步的RDB文件的文件描述符。client.repldboff
,Master已经传输的RDB文件的大小。client.repldbsize
,Master传输的RDB文件的总大小。client.replpreamble
,用于记录Master传输的RDB文件的一些前缀信息。对于有盘RDB传输,前缀信息中存储了RDB文件的长度;对于无盘RDB传输,前缀信息中存储了用于标记传输结束的<EOF>
分隔字符串。client.read_reploff
,在Slave一侧记录从Master中读取到的命令数据的偏移量client.reploff
,在Slave一侧记录对应Master所发送的数据中已经被Slave处理的命令数据的偏移量。client.psync_initial_offset
,在Master实例一侧用于记录对应Slave的初始偏移。client.replid
,在Slave实例上记录Master实例的复制ID。client.slave_listening_port
,在Master实例上记录的Slave通过REPLCONF命令传来的端口数据。client.slave_ip
,在Master实例上记录的Slave通过REPLCONF命令传来的IP数据。client.slave_capa
,在Master实例上记录的Slave通过REPLCONF命令传来的这个Slave实例的capa数据。client.repl_ack_off
,对于一个表示Slave实例的client
对象,这个字段用于存储这个Slave当前确认处理的复制偏移量。client.repl_ack_time
,对于一个Slave实例的客户端对象,这个字段记录这个Slave上次确认的时间。client.woff
,这个字段用于存储上一个写命令的全局复制偏移量。