- 第三部分:多机数据库的实现
- 第十五章:复制
- 旧版复制功能的实现
- 新版复制功能的实现
- 第十六章:Sentinel
- 第十七章:集群
第三部分:多机数据库的实现
第十五章:复制
用户可是使用SLAVEOF
命令或者设置slaveof
选项来让一个server去复制另一个server,这样被复制的server叫做master,复制的server叫做slave。进行复制中的主从服务器双方的数据库将保存相同的数据,称作为“数据库状态一致”,简称一致。
旧版复制功能的实现
- Redis的复制功能可以分为同步(
sync
)和命令传(command propagate
)两个操作。同步主要用于将从服务器的数据库状态更新至主服务器当前的状态。命令传播是用于在主服务器数据被修改,导致主从服务器数据不一致时,让主从服务器重新恢复一致状态。 - 当客户端向slave发送
SLAVEOF
命令时,slave先会执行同步操作,向master发送SYNC
命令,具体步骤如下:
- salve向master发送
SYNC
命令 - 收到
SYNC
的master开始执行BGSAVE
,在后台生成一个RDB文件,并用一个缓冲区记录现在开始的所有写命令 - 当master的
BGSAVE
执行完毕后,会将RDB文件发送给slave,然后slave会载入这个文件来更新自己的数据库状态,使得与master数据一致。 - master将之前记录在缓冲区的所有写命令发送给slave,slave执行这些命令,来同步。
- 再同步操作完成后,主从服务器的状态就一致了,但是当master向slave发送写命令的期间,master可能正在被修改,导致主从数据再次不一致。为了解决这个问题,master主要对slave执行命令传播操作,即master会将自己执行的写命令发送给slave执行,来回复一致状态。
- 旧版复制功能的缺点:如果在命令传播阶段断线了,那么重新连接就得使用
SYNC
命令,然后开始从头的BGSAVE,然后发送RDB文件,就算之前已经有很多数据已经复制了,但是还得从头来一遍。 -
SYNC
是一个非常耗资源的操作,因为master执行BGSAVE
生成RDB文件就是耗cpu内存等资源,而且把RDB文件发送给slave耗网络资源,其次slave加载RDB文件是阻塞的,期间无法执行命令。
新版复制功能的实现
- 新版使用
PSYNC
来进行同步操作,具有完整重同步和部分重同步两种模式。 - 完整重同步用于处理初次复制,基本和
SYNC
一样,都是让master发送RDB文件,并维持写命令缓冲来进行同步。 - 部分重同步就用于处理命令传播期间断线的问题:当slave断线后重连回master,如果条件允许,master可以将短线期间执行的写命令发送给slave,slave只要接受并执行这些写命令就能更新。
- 部分重同步功能的实现:
- 主从服务器的复制偏移量:master每次向slave传播N个字节的数据时,就会让自己的复制偏移量+N,同理slave接受到传来的B歌字节时就+N。可以通过对比主从服务器的复制偏移量来检测是否处于一致状态。
- 复制积压缓冲区:master维护的一个固定长度的队列,默认大小为1MB。当master进行命令传播时,不仅会将写命令传播给所有slave,还会将写命令保存在复制积压缓冲区中,并且为每个字节记录相应的复制偏移量。当slave断线重连后,master会根据slave的复制偏移量来决定采用哪种同步操作,如果该复制偏移量之后的数据还在缓冲区中,则部分重同步,不然只能完整重同步。
- 服务器运行ID:每个redis服务器,不管是master还是slave,都会有自己的运行ID,当slave进行初次复制时,master会将自己的ID发送过去,slave可以根据这个ID来判断是完整重同步还是部分。
- 完整的
SLAVEOF
命令执行流程:
- slave接收到
SLAVEOF
命令,先判断是否是第一次执行复制。 - 如果是第一次,就向master发送
PSYNC ? -1
,就执行完整重同步,master会返回+FULLRESYNC <ID> <OFFSET>
执行完整重同步。 - 如果不是,就发送
PSYNC <ID> <OFFSET>
,如果master返回的+CONTINUE
,说明offse在缓冲区内,只需要执行部分重同步,如果不是,则说明不在缓冲区内了,还是要执行完整重同步,同2。
- 复制的实现:
- 设置master的ip+port
- 建立套接字连接
- slave
PING
master看是否连通 - 身份验证
- 发送slave的port用于master的监听
-
PSYNC
执行同步 - 命令传播
- 在命令传播极端,slave会心跳默认一秒一次向master发送命令
REPLCONF ACK <OFFSET>
确认连接,并发送复制偏移量。主要3个作用:
- 检查主从服务器之间的网络连接状态。
- 辅助实现
min-slaves
配置选项。 - 检测命令丢失,如果传播过程中丢失了命令,那么ack回来的偏移量肯定和master的偏移量不匹配,可以检测出来。
第十六章:Sentinel
- Sentinel(哨兵)是redis高可用的解决方案:由一个或者多个Sentinel实例组成的Sentinel系统可以监视任意多个主服务器,以及这些主服务器下面的从服务器,并在被监视的主服务器下线时自动将下线主服务器的某个从服务器升级为新的主服务器。
- 当master的下线时长超过设定的上限时,Sentinel系统就会对master做故障转移处理:
- 首先Sentinel系统会挑选master的一个slave,并将slave升级为新的master。
- 之后Sentinel会向原master的所有slave发送新的复制命令,让它们称为新的master的slave,当所有slave开始复制新的master时,故障转移操作结束。
- Sentinel会监视已经下线的原master,并在它重新上线的时候把它设置为slave。
- Sentinel本质上只是一个运行在特殊模式下的redis服务器。
- Sentinel的启动配置等了解即可:Sentinel会读取用户指定的配置文件,为每个要被监视的master创建相应的实例结构,并创建连向master的命令连接和订阅连接。
- Sentinel默认10S一次向被监视的master发送INFO命令,并根据回复信息获取master的当前信息。回复数据中包含master本身的信息,包括id和role,还包括master下所有slave的信息,包括ip,port等。
- 当Sentinel发现master有新的slave出现时,会为这个新的slave创建相应的实例结构,还会创建连接到这个slave的命令连接和订阅连接。创建命令连接后会默认10S一次发送INFO命令,slave收到后回复数据中包含id,role,master的ip和port,连接状态,优先级,复制偏移量等。
- 默认情况下Sentinel会1S一次向所有创建了命令连接的实例发送PING命令,并根据回复来判断是否在线。如果连续50000毫秒(默认)master都向Sentinel发送无效回复,则会被标记主观下线。当将一个master标记为主观下线之后,Sentinel会向其他监视这个master的Sentinel进行询问,等Sentinel从其他Sentinel那里接收到足够多(设置选项)的下线判断后,会标记为客观下线,并执行故障转移。
- 选举领头Sentinel:当master被判断客观下线后,监视它的所有Sentinel会选举出一个领头Sentinel来执行故障转移操作。具体规则:
- 所有在线的Sentinel都有被选举资格。
- 每次进行Sentinel选举后,不管是否成功,所有Sentinel的配置纪元会+1.
- 在一个配置纪元内,所有Sentinel都有一次机会将某个Sentinel设置为局部领队,并且设置完成后不能修改。
- 每个发现了master进入客观下线的Sentinel都会要求其他的将自己设置为局部领队。
- Sentinel设置领队的规则是先来先得,最早发送请求的就投票给他,之后的会被拒绝。
- 如果某个Sentinel被半数以上的选举了,就会成为领队,不然就再来一次。
- 选举完成后,领队Sentinel将对已下线的master进行故障转移操作:
- 在已下线的master的所有slave中挑选一个作为新的master。
- 将其他slave复制新的master。
- 将下线的master设置为新master的slave。
- 选举新master的算法:
- 领队Sentinel会将下线master的所有slave保存在一个列表中,然后删除所有下线或者断线的slave。
- 删除所有5秒内没有回复过Sentinel的INFO命令的slave。
- 删除所有与下线master连接断开超过
down-after-milliseconds * 10
的slave。 - 之后Sentinel根据slave的优先级选取最高的作为master。如果优先级相同,则选复制偏移量最大的。如果复制偏移量都一样,则选id最小的。
第十七章:集群
- redis集群是redis提供的分布式数据库方案,通过
sharding
来进行数据共享,并提供复制和故障转移的功能。 - 一个redis集群通常由多个node组成,将各个node连接起来可以使用
CLUSTER MEET <IP> <PORT>
命令来完成。当一个node使用该命令时,node就会和命令中对应ip和port的node进行握手,如果握手成功,就会将node添加到集群中。 - redis服务器在启动时会根据
cluster-enabled
选项来决定是否开启集群模式。 -
clusterNode
结构保存了一个node的当前状态,包括创建时间,名称,ip和port等。 - redis集群通过sharding来保存数据库中的键值对:集群的整个数据库被分为16384个slot,数据库中的每个key都属于这16384个slot中的一个,集群的每个node可以处理0~16384个slot。当所有的slot都有node在处理时称为上线状态,不然则是下线状态。
- 通过向node发送
CLUSTER ADDSLOTS <SLOT> [slot...]
命令可将多个slot指派给node处理。 -
clusterNode
的slots
属性和numslot
属性记录了node所负责哪些slots。slots
是一个2048字节的数组,由于是二进制,所以长度时2048*8=16384刚好等于slot的数量。 - 一个node除了会将自己负责处理的slot记录在自己的结构体中,还会将slots数组发送给别的node。因此集群中所有node都知道所有的slot是被哪些node处理的。
- 当client对集群发送与数据库键有关的命令时,接收命令的结点会计算出命令要处理的数据库键属于哪一个slot,并检查该slot是否属于自己处理-如果是,则直接执行命令;如果不是,则向client发送
MOVED
错误,并指引客户端redirect到正确的node,并在此发送之前的命令。 - 计算键属于slot的方法:
CRC16(key) & 16384
。计算key的CRC16校验和,然后对16384取余。可以使用CLUSTER KEYSLOT XX
来查询键属于哪个slot。 -
MOVED
的格式为MOVED <SLOT> <IP>:<PORT>
。 - 一个集群客户端通常会与多个node创建套接字连接,其实所谓的node redirect其实就是换一个套接字来发送命令。
- node和单机时的区别是作为集群的node只能使用0号库,而单机的可以使用16个库。
- 重新分片:重新分片可以将任意数量已经指派的slot改指派给别的node,并且相关联的键值对数据也会移动。
- 重新分片可以在线执行,不需要下线处理,并且同时源节点和目标节点都可以继续处理请求。
- 重新分片操作时通过
redis-trib
来执行的,对单个slot进行重新分片的操作步骤如下:
- 对目标结点发送命令,让目标结点准备好从源节点导入该slot的键值对。
- 对源节点发送命令让源节点准备好将属于slot的键值对迁移至目标节点。
- 向源节点发送命令获得属于slot的键值对的键名。
- 根据3获得的键名,向源节点发送迁移命令将该键迁移至目标节点。
- 重复3,4知道slot的所有节点都迁移至目标节点。
- 向任意结点发送命令将slot分派给目标节点,这一指派信息会通过消息发送至整个集群,通知所有节点已经重新分片,更新slots数组。
- 在进行重新分片期间,源节点向目标节点迁移一个slot时可能会出现属于被迁移slot中的一部分键值对保存在源节点中,另一部分已经迁移好的保存在目标节点中,此时客户端发送了一个与数据库键有关的命令,并且处理的该键刚好在被处理的slot中,此时源节点会先在自己的数据库中查找键,如果查到就执行命令,如果没查到,说明已经迁移到目标结点去了,此时会向客户端发送
ASK
错误,指引客户端redirect去新的目标节点并在此发送命令。 -
ASK
和MOVED
的区别:都会导致客户端重定向,MOVED
表示slot的负责全已经转移到另一个node了,只要收到一次MOVED
错误,那么之后所有新的命令都可以发送给重定向后的结点,因为整个slot都已经是别的结点负责了;ASK
错误只是在迁移slot的中间态可能会出现的错误,并且只是针对某些key,所以当出现ASK错误之后,对于该key的命令需要重定向到新的node,但是之后别的key的命令还是可以发送给源节点。 - redis集群中的node分为master和slave,master用于处理slot,slave则是复制了某个master,用于master下线时代替master处理命令。如果集群中某个master下线了,则剩余的master会在它的slave中选举一个新的作为master,在故障转移后,之前下线的结点如果重新上线,则会作为slave。
- 集群中的每个node都会定期向其他node发送
PING
信息,检测对方是否在线。如果接受PING的结点没有在指定时间内返回PONG
,则会被标记为疑似下线。集群中的各个结点都会通过发送消息的方式来交换集群中各个结点的状态信息,比如在线,疑似下线还是已经下线。在集群中如果半数以上负责处理slot的master都将某个master标记为疑似下线,则会被标记为已经下线,并广播这个消息,所有结点都会将其标记为已经下线。 - 当一个slave发现自己正在复制的master进入已下线状态时,会进行故障转移,具体步骤为:
- 复制下线master的所有slave中会有一个被选中。
- 选中的结点会执行命令称为新的主节点。
- 新的主节点会撤销所有对已下线主节点的slot指派,并将这些slot都指派给自己。
- 广播一条
PONG
信息,其他结点可以知道这个结点成为了新的master,并且已经接管了slot。
- 选举方法是基于
Raft
算法的领头选举:
- 集群的配置纪元是一个一个自增计数器,初始值0。
- 当某个node开始故障转移操作 是,配置纪元+1。
- 没有每个配置纪元,每个master都有一个投票机会,第一个向master要求投票的slave将得到票。
- 当slave发现复制的master下线后,会广播一条消息,要求所有收到消息,并有资格投票的master向它投票。
- 如果一个master有投票权,并且没有投给别的,则会返回消息给那个slave表示支持它成为master。
- 每个slave会统计收到多少票
- 如果有N个能投票的master,则当一个slave收到的票超过N/2时就会当选。
- 如果没有出现超过N/2的,则再来一次。