为《Redis设计与实现》笔记
客户端可以使用SLAVEOF
命令将指定服务器设置为该服务器的主服务器
127.0.0.1:12345> SLAVEOF 127.0.0.1 6379
127.0.0.1:6379
将被设置为127.0.0.1:123456
的主服务器
Redis的复制功能分为同步(sync)和命令传播(command propagate)两个阶段
从服务器对主服务器的同步操作通过向主服务器发送SYNC
命令来完成,步骤如下:
SYNC
命令同步完成后,客户端对主服务器执行了某些操作导致主从服务器的数据库出现不一致,此时主服务器会将自己执行的写命令发送给从服务器,使其恢复成同步状态
旧版复制存在两种情况:
在旧版复制中,断线重连后整个复制操作都需要重新执行,导致效率过低
Redis2.8开始,使用PSYNC
命令代替SYNC
来执行复制时的同步操作
PSYNC
命令具有两种模式:
SYNC
命令的执行一样,也是通过主服务器创建并发送RDB文件并发送缓冲区里的写命令给从服务器来进行同步部分重同步由以下三个部分构成:
主服务器每次向从服务器传播N个字节的数据时,就将自己的复制哦i按一辆值加上N,而从服务器接收到N个字节的数据时,也将自己的复制偏移量加上N。如果主从服务器处于一致状态,那么主从服务器地偏移量是相同的,反之则说明主从服务器为处于一致状态
复制偏移量能够判断主从服务器是否初一致状态,而复制积压缓冲区则是在从服务器断线重连时执行部分重同步时发挥作用
复制积压缓冲区为主服务器维护的固定长度的先进先出队列,其默认大小为1MB。当主服务器将写命令发送给从服务器时,同时将写命令加入到复制积压缓冲区中,并且复制积压缓冲区会为队列中的每个字节记录相应的复制偏移量。
当从服务器重新连上主服务器时,从服务器会通过PSYNC
命令将自己的复制偏移量发送给主服务器,主服务器将根据该偏移量来决定进行那种同步方式:
每个Redis服务器都拥有自己的而运行ID,其在服务器启动时自动生成,由40个随机的十六进制字符组成。当主从服务器进行初次复制时,主服务器将自己的运行ID发送给从服务器,并保存好自己的运行ID;当从服务器断连后重新连接,从服务器将告知主服务器之前保存的运行ID
PSYNC
命令的调用方法有两种:
SLAVEOF no one
命令,那么从服务器将在开始复制时向主服务器发送PSYNC ? -1
命令,主动请求服务器进行完整重同步PSYNC
命令,其中runid
为运行ID,offset
为偏移量主服务器在接收到PSYNC
命令后会有以下三种回答:
+FULLRESYNC
,从服务器将保存主服务器的运行ID,并将offset
作为自己的初始化偏移量+CONTIMUE
回复,这时从服务器只需要等待主服务器发送自己缺失的写命令即可PSYNC
,则发送-ERR
回复,从服务器将发送SYNC
命令执行旧的完整同步操作从服务器执行SLAVEOF
命令时,从服务器保存有主服务器id和端口信息;SLAVEOF
为一个异步命令,在设置完上述两个属性后,向客户端发送OK
后再开始执行复制工作
struct redisServer {
...
// 主服务器地址
char *masterhost;
// 主服务器端口
char *masterport;
...
};
在执行完SLAVEOF
后,从服务器将根据上一步设置的IP和端口,向主服务器建立套接字连接;主服务器接收(accept)从服务器的连接后,为该套接字建立相应的客户端状态,将从服务器看作是连接主服务器的一个客户端来看待
连接建立完成后,从服务器将向主服务器发送PING
命令,其有以下两个作用:
- 检查套接字的读写状态书否正常
- 检查主服务器是否能正常处理命令请求
主服务器接收到PING
命令后会有三种情况
PONG
,表示网络连接正常,可以继续进行下面的步骤从服务器接收到PONG
回复后,如果从服务器设置了masterauth
选项,则进行身份验证,此时从服务器向主服务器发送AUTH
命令,命令的参数为masterauth
的值
从服务器在进行身份验证阶段存在以下几种情况:
requirepass
选项,并且从服务器也没有设置masterauth
选项,则跳过该阶段AUTH
命令发送的密码和主机的requirepass
选项相同,则进行下一步操作,否则返回invalid password
错误requirepass
选项,但从服务器没有设置masterauth
选项,那么主服务器将返回NOAUTH
;相反地,从服务器设置了masterauth
选项而主服务器未设置时返回no password is set
在进行完身份验证后,从服务器将执行REPLCONF listening-port
向主服务器发送从服务器的监听端口号,其记录在主服务器对从服务器的客户端中,起作用时在主服务器执行INFO replication
命令时打印从服务器的端口号
typedef struct redisClient {
...
// 从服务器的监听端口号
int slave_listening_port;
} redisClient;
从服务器将向主服务器发送PSYNC
命令,执行同步操作,将自己的数据更新志主服务器当前所处的状态
在同步操作执行之前,只有从服务器是主服务器的客户端,而在执行同步操作后,主服务器也会成为从服务器的客户端,只有主服务器成为了从服务器的客户端,才能向从服务器发送同步需要的写命令
同步完成后,主从服务器进入命令传播阶段,此时主服务器只要一直将自己执行的写命令发送给从服务器,而从服务器只要一直接收并执行主服务器发送来的写命令,就可以保持主从一致了
在命令椽笔阶段,默认从服务器每秒向主服务器发送命令REPLCONF ACK
,其中replication_offset
为偏移量,其作用如下:
REPLOCONF ACK
命令来检查两者之间的网络连接是否正常,如果主服务器超过以免没有收到从服务器发来的REPLOCONF ACK
命令,那么主服务器就知道与从服务器的连接出现问题min-slaves
选项:Redis的min-slaves-to-write
和min-slaves-max-lag
两个选项可以防止主服务器在不安全的情况下执行写命令,其表示当从服务器数量少于min-slaves-to-write
或者min-slaves-to-write
个从服务器的延迟都不小于min-slaves-max-lag
时主服务器将拒绝执行写命令REPLCONF ACK
发送过来的偏移量和自己记录的偏移量来判断从服务器是否存在命令丢失,如果存在,主服务器将根据复制积压缓冲区中保存的数据重新发送给从服务器Sentinel(哨兵)是Redis的高可用性解决方案,其由一个或多个Sentinel梳理组成的Sentinel系统可以监视任意多个主服务器及其从服务器,并在被监视的主服务器进入下线状态时自动其中的某个从服务器升级为主服务器
以上图为例,当Server1下线时长超过用户设定的下线时长时,Sentinel系统就会对Server1执行故障转移操作:
使用以下命令启动Sentinel:
redis-sentinel /path/to/your/sentinel.conf
或者
redis-server /path/to/your/sentinel.conf --sentinel
当Sentinel启动时,执行以下步骤
sentinel.c/REDIS_SENTINEL_PORT
常量作为服务器端口,其默认值为26479,并将普通Redis服务器的命令表redis.c/redisCommandTable
替换为sentinel.c/sentinelcmds
命令表,所以Sentinel不能执行Redis中如GET
, SET
等命令sentinel.c/sentinelState
结构作为Sentinel状态struct sentinelState {
// 当前纪元,用于故障转移
uint64_t current_epoch;
// 被该Sentinel监视的主服务器列表,键为主服务器名字,值为指向sentinelRedisInstance结构的指针
dict *masters;
// 是否进入了TILT模式
int tilt;
// 目前正在执行的脚本的数量
int running_start_time;
// 最后一次执行时间处理器的事件
mstime_t previous_time;
// 包含了所有需要执行的用户脚本的FIFO队列
list *scripts_queue;
};
sentinel.c/sentinelRedisInstance
结构:typedef struct sentinelRedisInstance {
// 标识值,记录了实例的类型及其状态,主服务器为SRI_MASTER,从服务器为SRI_SLAVE
int flags;
// 实例的名称,默认为 ip:port
char *name;
// 实例的运行ID
char *runid;
// 配置纪元,用于实现故障转移
uint64_t config_epoch;
//实例的地址
sentinelAddr *addr;
// 实例无响应多少毫秒后会被判断为主观下线
mstime_t down_after_period;
// 判断该实例为客观下线所需的支持投票数量
int quorum;
// 在执行故障转移操作时,可以同时对新的主服务器进行同步的从服务器数量
int parallel_syncs;
// 刷新故障迁移状态时的最大时限
mstime_t failover_timeout;
...
} sentinelRedisInstance;
__sentinel__:hello
频道Sentinel建立两个连接是因为在Redis的发布于订阅功能中,被发送的信息都不会保存在Redis服务器中秒如果在信息发送时,想要接收信息的客户端不在线或下线,则信息会丢失,因此需要建立一个专门的连接来接收该频道的信息。此外,除了订阅频道外,Sentinel还需要向主服务器发送命令,所以除了订阅连接外还需要建立命令连接
Sentinel默认十秒一次向主服务器发送INFO
命令,并通过该命令来获取主服务器的当前信息,信息格式如下:
# Server
...
run_id: XXX
...
# Replication
role:master
...
slave0:ip=127.0.0.1,port=11111,state=online,offset=43,lag=0
slave1:ip=127.0.0.1,port=22222,state=online,offset=43,lag=0
slave2:ip=127.0.0.1,port=33333,state=online,offset=43,lag=0
...
# othersections
...
信息内容包括:
Sentinel会根据该信息更新masters
字典中的主服务器信息及sentinelRedisInstance
结构中的slaves
字典中的信息
当Sentinel发现主服务器由新的从服务器时,会对该从服务器建立相应的实例结构和命令连接于订阅连接,同样会每个十秒发送INFO
命令,其回复内容如下:
# Server
...
run__id:XXX
...
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:up
slave_repl_offset:11887
slave_priority:100
# Other sections
...
信息内容包括:
runID
role
master_host
和端口号 master_port
master_link_status
slabe_priority
slave_repl_offset
根据这些信息,Sentinel会对从服务器的实例结构进行更新
默认Sentinel会以两秒一次的间隔给所有监视的主从服务器发送命令:
PUBLISH __sentinel__:hello ",,,,,,,"
信息内容包括:
s_
开头的参数为Sentinel本身的信息
s_ip
:Sentinel的IP地址s_port
:Sentinel的端口号s_runid
:Sentinel的运行IDs_epoch
:Sentinel当前的配置纪元m_
开头的参数为主服务器信息;如果发送的Sentinel正在监视的是主服务器,那么这些参数就是主服务器的信息,如果是从服务器,那么这些参数就是该从服务器对应的主服务器信息
m_ip
:主服务器的IP地址m_port
:主服务器的端口号m_runid
:主服务器的运行IDm_epoch
:主服务器当前的配置纪元当Sentinel和主从服务器建立起连接后,会通过订阅连接发送以下命令
SUBSCRIB __sentinel__:hello
对于监视同一个服务器的多个Sentinel来说,一个Sentinel发送的信息会被其他Sentinel接收到,这些信息用于更新其他entinel对发送信息的Sentinel的认知,也会被用于更新其他Sentinel对被监视服务器的认知
当一个Sentinel从__sentinel__:hello
频道收到一条信息时,Sentinel会对该信息尽心分析,提取出信息中的Sentinel IP地址,Sentinel端口号和运行ID等八个参数,进行以下检查:
默认情况下,Sentinel会以每秒一次的频率向所有创建了命令连接的主从服务器和Sentinel发送PING
命令,通过其返回值来判断其是否在线,如果在down-after-milliseconds
毫秒内连续向Sentinel发送无效回复,那么Sentinel就会判断该实例为主观下线状态,其flag
属性将被设置为SRI_S_DOWN
当一个主服务器被判断为主观下线后,为了确认这个主服务器是否真的下线了,Sentinel会像同样监视这个主服务器的其他Sentinel进行询问,如果收集到了足够多的已下线判断后,Sentinel就会将其判定为客观下线,如果为主服务器则进行故障转移操作
当一个主服务器被判断为客观下线时,监视这个下线主服务器的Sentinel会进行协商,选举出一个领头Sentinel,并由领头Sentinel来执行故障转移操作,其规则如下:
SENTINEL -s-master-down-by-addr
命令,其返回值中的runid
不是*
而是是源Sentinel的运行ID时,表示源Sentinel为目标Sentinel的局部领头SentinelSENTINEL -s-master-down-by-addr
命令后,其回复中的leader_runid
参数和leader_epoch
参数分别为目标Sentinel的局部头领Sentinel的运行ID和配置纪元Leader_runid
参数是否和源Sentinel的运行ID是否一致,如果一致测目标Sentinel将源Sentinel设置为自己的局部领头Sentinel在选举出领头Sentinel后,领头Sentinel会对一下先的主服务器执行故障转移操作:
INFO
命令的从服务器down-after-milliseconds
* 10毫秒的从服务器