Redis设计与实现之:主从复制

目录:

1.主从复制是什么?有什么用途?
2.主从复制的环境如何搭建?
3.主从复制的功能如何实现?
4.一个完整的主从复制过程?


参考内容:

《Reis设计与实现》第三部分 第十五章 复制
Redis官方文档:https://redis.io/topics/replication


1.主从复制是什么?有什么用途?

概念:

主从复制是指在Redis中通过配置,让一个服务器去复制另一个服务器的数据,使二者存有相同的数据内容,达到"数据库状态一致"的效果。

用途:

(1) 通过主从复制,一个主节点可以有多个从节点,主节点可写可读,从节点可读,把读操作转移到从节点上,可以降低主节点的访问负载;
(2) 如果主节点意外挂掉(例如某些硬件故障导致数据无法恢复),则从节点上的数据备份可以避免数据丢失。

缺点:

不能提供高可用性,因为不支持自动故障转移。
当主节点挂掉,服务器将无法对外提供写服务,而在实际生产环境中服务是一刻都不能停止的,所以一般的生产环境不会单单使用主从模式,而是在其上封装一层的哨兵模式或集群模式(high availability features provided as an additional layer by Redis Cluster or Redis Sentinel)


2.主从复制的环境如何搭建?

两种方式:

方法一: 修改配置文件后启动从机

修改 redis.conf 配置文件:

redis.conf ---> slaveof 127.0.0.1:8001

然后启动服务器:

127.0.0.1:8002>./redis-server redis.conf

使 127.0.0.1:8002 成为 127.00.1:8001 的从机。

方法二: 在运行中的从机上输入 slaveof 命令

127.0.0.1:8002> SLAVEOF 127.0.0.1:8001
// SLAVEOF :

输入 SLAVEOF 命令后,从机上的数据会被覆盖(原有数据清零,数据同步为与主机一致)。

查看主从关系:

在连接服务器的客户端上输入 INFO 命令:

主机:

127.0.0.1:8001> INFO REPLICATION
#Replication
role:master
connected slaves:1
slave0:ip=127.0.0.1,port=8002,state=online, offset=224,lag=1

从机:

127.0.0.1:8002> INFO REPLICATION
#Replication
role:slave
master _host:127.0.0.1
naster_ port:8001
master_link_status:up
master_last_io_seconds_ ago:2
slave_priority:100
slave_read_only:1
connected slaves:0

如何断开主从关系:

在从机上输入命令:

127.0.0.1:8002> SLAVEOF NO ONE

3.主从复制的功能如何实现?

Redis的主从复制功能由 “同步(sync)”“命令传播(command propagate)” 两个操作实现:

(1) 当主从节点首次建立连接,或连接断开后重连时,通过 “同步” 的操作来完成主从复制;

(2) 当主从节点间保持正常连接时,通过 “命令传播” 的操作完成主从复制。

注:

主从复制功能的“同步"和"命令传播"类似于epoll中的ET和LT模式:
	“同步"是一种“边沿触发”: 当主从首次建立连接,或连接中断重连后,采用同步的方式完成主从复制;
	“命令传播"是一种“水平沿触发”: 当主从保持连接状态,主服务器上发生写明令改写数据时,通过命令传播向从服务器传递写明令操作。

3.1 “同步”:

从服务器向主服务器发送 PSYNC 命令执行同步操作:

PSYNC命令的实现:

① 从服务器发送的PSYNC命令有两种格式:

第一种:

PSYNC ? -1

如果从服务器之前没有复制过任何服务器,或者执行了 SLAVEOF no one 命令,则说明此时不可能执行部分重同步,PSYNC 调用的格式如上(请求完整重同步),

第二种:

PSYNC  

如果从服务器之前已经复制过了某个主服务器,那么在开始一次新的复制时的 PSYNC 调用格式如上,
其中, 表示上一次复制的主服务器的“运行ID”, 表示从服务器的当前“复制偏移量”。

② 主服务器的返回值有三种格式:

第一种:

+FULLRESYNC

表示主服务器将与从服务器执行完整重同步;

第二种:

+CONTINUE

表示主服务器将与从服务器执行部分重同步;

第三种:

-ERR

表示主服务器版本低于 Redis 2.8,无法识别 PSYNC 命令,从服务器将发送 FULLRESYNC 命令并执行完整重同步。

完全重同步(full resynchronization)的实现:

① PSYNC:

客户端向从服务器发送 SLAVEOF 命令,从服务器在建立套接字连接、执行 PING 命令、身份验证后,向主服务器发送 PSYNC 命令;

② BGSAVE:

主服务器收到 PSYNC 后,判断需要执行完整重同步,执行 BGSAVE 命令,在后台生成一个 RDB文件,并使用缓冲区记录从现在开始执行的所有写命令;

③ RDB:

主服务器BGSAVE执行完毕后,将生成的RDB文件发送给从服务器,从服务器收到后写入;

④ 缓冲区:

主服务器将缓冲区中记录的写明令发送给从服务器,从服务器执行收到的这些写命令。

部分重同步(partial resynchronization)的实现:

部分重同步功能由三部分构成:
① 主、从服务器的“复制偏移量” (replication offset);
② 主服务器的复制“积压缓冲区”(replication backlog);
③ 服务器的“运行ID”(runID)。

过程:

a. 主从服务器会分别维护一个“复制偏移量”,主服务器在发送N个字节的数据后对偏移量+N,从服务器在收到N个字节的数据后对偏移量+N,因此正常连接的情况下主从双方的复制偏移量相等。
当主从间连接断开重连后,从服务器会发送PSYNC命令,并携带自己的复制偏移量的值;

b. 主服务器维护的“复制积压缓冲区"本质上是一个【固定长度的先进先出队列】,其中保存着主服务器最近执行的 【写命令】,队列满时则会将先进入的数据丢弃;

c. 主服务器收到从服务器的 PSYNC 命令后,判断其中携带的从服务器当前复制偏移量之后的数据(即 offset+1 开始的数据)是否仍然存在于复制积压缓冲区中:
如果是,主从服务返回 “+CONTINUE” 给从服务器,表示将以 “部分重同步” 的模式进行数据同步,
如果不是,则执行完整重同步。

d. 每个服务器都在启动时生成一个由40个十六进制数组成的“运行ID”,初次连接时从服务器会保存主服务器的 运行ID。在从服务器断线重连后,会将之前保存的运行ID发送个主服务器,如果新旧ID相同,主服务器会继续尝试使用部分重同步,否则,主服务器发现此从服务器是新连接的,则执行完整重同步。

附: 配置"复制积压缓冲区”的大小:

Redis复制积压缓冲区的默认大小为 1 MB,需要根据实际情况来设计缓冲区的大小,以便保证 PSYNC 命令的部分重复制功能正常发挥作用。
复制积压缓冲区的大小可根据以下公式来估算:

second * write_size_per_second

其中,second 为从服务器断线重连所需的平均时间,write_size_per_second 为主服务器平均每秒产生的写命令数据量。

3.2 “命令传播”:

在 “同步” 操作执行完毕后,主从服务器达到数据库状态一致,随后当主服务器上发生写操作时,主服务器上的数据被改写,主服务器通过命令传播方式将这条写命令发送到从服务器,从服务器收到后执行这条写明令。

心跳检测:

在命令传播阶段,从服务器默认会以 每秒一次 的频率向主服务器发送命令:

REPLCONF ACK 

其中,replication_offset 是从服务器当前的复制偏移量。

心跳检测有三个作用:

(1) 检测主从服务器的网络连接状态:

主服务器知道正常情况下会 每隔一秒钟收到一次从服务器的心跳包,如果长时间没有收到,则主服务器就可以知道主从间的连接出现了问题。
在主服务器上 INFO replication 可以查看收到各个从服务器上次心跳包的时间:
(正常情况下在0到1秒之间跳变)

127.0.0.1:8001> INFO replication
#Replication
role:master
connected slaves:3
slave0:ip=127.0.0.1,port=8002,state=online,offset=252,lag=1    <===
slave1:ip=127.0.0.1,port=8003, state=online,offset=252,lag=1   <===
slave2:ip=127.0.0.1,port=8004,state=online,offset=252,lag=0    <===
master_replid:9b9ea7eef504c05da56067b53f64 59827283c46
master_replid2:0000000000000000000000000000000000000000

其中的 lag 字段表示的就是上次收到这个slave从服务器上的心跳检测时间,正常值为0或1。

(2) 辅助实现min-slave配置选项:(防止主服务器在不安全情况执行写)

为了防止主服务器在不安全的情况下执行写命令,Redis提供两个配置选项:

min-slaves-to-wite  	3
min-slaves-max-lag 		10

表示如果从服务器的个数小于3,或者有三个最大从服务器的延迟(lag)都大于10秒,则主服务器不执行写命令。

(3) 检测命令丢失:

心跳检测中带有 replication_offset 字段,主服务器可以判断上次的命令传播是否丢失,如丢失,可及时通过复制积压缓冲区中的数据重传。


4. 一个完整的主从复制过程:

假设有 127.0.0.1:8001127.0.0.1:8002 两个Redis服务器,现在让 8002 成为 8001 的从机,整个过程如下:

步骤1: 从服务器:设置主服务器的地址和端口:(redisServer )

在连接从服务器(8002)的客户端上输入 SLAVEOF 命令:

127.0.0.1:8002> SLAVEOF 127.0.0.1:8001
OK

从服务器收到命令后,保存待连接主服务器的 IP地址 和 端口号:

struct redisServer {
	char	*masterhost; 	//主服务器的IP地址
	int 	masterport; 	//主服务器的端口号
};

注意 SLAVEOF 是一个 异步命令,从服务器收到 SLAVEOF 命令后,会直接返回OK 给客户端,表示已经收到了命令,然后才开始设置 masterhostmasterport 属性。
(如果是同步命令,实现过程会怎样? 从服务器收到SLAVEOF命令后,客户端阻塞等待命令返回,从服务器执行完成后返回OK给客户端,客户端继续向下执行。)

步骤2: 从服务器:建立与主服务器的套接字连接:(connect)

从服务器根据在 步骤1 中保存的主服务器IP和端口号后,创建连向主服务器的套接字连接,即从节点调用 connect,主节点调用 accept 接受连接。

如果连接建立成功,从服务器会创建一个专门用于处理主从复制工作的 IO事件处理器(在epoll中添加一个监听fd),后续的复制工作(例如接收RDB文件、接收主服务器传播过来的写命令等)都是依靠这个IO事件处理器来完成。

步骤3: 从服务器:发送PING命令:(PING)

在 步骤2 中主从服务器建立了socket套接字连接,但双方还未使用过该套接字进行通信,因此需要先执行 PING 命令检查双方的读写处理是否正常(步骤2相当于完成了TCP三次握手,主从双方的connect、accept调用正常,步骤3 是用来检测主从双方的read、write 调用是否正常)。

(1)若从服务器发出PING命令后正常收到主服务器的PONG回复,则说明连接状态正常,可执行下面步骤;
(2)若从服务器发出PING命令后收到主服务器回复的错误,或未在规定时间内收到主服务器的回复(TIMEOUT),则从服务器断开重连。

步骤4: 身份验证:(AUTH)

(1) 关于身份验证的配置项:

redis.conf 配置文件中有两个关于主从复制过程中身份验证的配置项:

① 从服务器上设置主服务器的密码:(默认未设置)

#  masterauth 

例如:

masterauth 10086

② 主服务器上设置自身的密码:(默认未设置)

# requirepass foobared

例如:

requirepass 10086

(2) 身份验证的过程:

(1) 如果从服务器设置了 masterauth 选项,则从服务器会发起身份验证,从服务器向主服务器发送 AUTH 命令,命令的参数是 masterauth 选项的值,例如:

AUTH 10086

此时:

如果主服务器设置了 `requirepass` 选项且二者密码匹配,则验证通过,连接成功;
如果密码不匹配,则验证失败,从新发起连接直至成功或从服务器放弃连接.

(2) 如果从服务器未设置 masterauth 选项,则从服务器不会发起身份验证,此时:

如果主服务器也未设置requirepass选项,则连接成功;
如果主服务器设置了requirepass选项,则验证失败,重新发起连接直至成功或从服务器放弃连接。

步骤5: 发送端口信息:(listening-port)

从服务器向主服务器发送自己的监听端口号,主服务器收到后保存在对应从服务器的 redisClient 结构中从服务器调用命令:

REPLCONF listening-port 8002

主服务器保存:

typedef struct redisClient
	int slave_listening_port;  //从服务器的监听端口号
} redisClient;

slave_listening_port 属性的唯一作用是主服务器执行 INFO replication 命令时打印出从服务器的端口号。

步骤6: 同步:

从服务器向主服务器发送 PSYNC 命令,执行同步操作,并将自己的数据库更新至与主服务器一致。

注:

在同步操作之前,只有从服务器是主服务器的客户端;
在同步操作之后,主服务器也会成为从服务器的客户端。

这是因为无论是"部分重同步"还是"完整重同步",都需要主服务器向从服务器发送数据(RDB文件 或 写命令),因此主服务器必须成为从服务器的客户端,才能完成写操作。

步骤7: 命令传播:

在完成同步操作之后,主服务器就会进入命令传播阶段,这时主服务器只需要一直将自己执行的写明令发送给从服务器,从服务器执行传播来的写命令,即可保持主从数据库状态一致。

同时,在命令传播阶段从服务器会按照固定的频率(默认一秒一次)向主服务器发送心跳检测命令。


到目前为止,所接触到的 redisServer 结构中的成员:

struct redisServer{
	redisDb 	*db;
	int 		dbnum; 				//数据库
	
	long long 	dirty;
	time_t 		lastchange;
	struct saveparam *saveparams;	//RDB持久化
	
	sds 		aof_buf;			//AOF持久化
	
	char 		*masterhost;
	int 		masterport;			//主从复制,当此服务器为从机时,保存主机IP及端口号
};

redisClient :

typedef struct redisClient {
	redisDb  *db; 					//记录客户端当前正在使用的数据库
	int 	 slare_listening_port;  //当这个结构体对应的客户端是一个从服务器,用来保存从服务器的监听端口号
} redisClient;

你可能感兴趣的:(#,Redis)