文末
我将这三次阿里面试的题目全部分专题整理出来,并附带上详细的答案解析,生成了一份PDF文档
- 第一个要分享给大家的就是算法和数据结构
- 第二个就是数据库的高频知识点与性能优化
- 第三个则是并发编程(72个知识点学习)
- 最后一个是各大JAVA架构专题的面试点+解析+我的一些学习的书籍资料
还有更多的Redis、MySQL、JVM、Kafka、微服务、Spring全家桶等学习笔记这里就不一一列举出来
所有错误情况都会令 slave 进入 error 流程:slave 断开当前的连接,之后再进行重试。
5、发送端口信息
在身份验证通过后后, slave 将向 master 发送自己的监听端口号, master 收到后记录在 slave 所对应的客户端状态的 slave_listening_port 属性中。
6、发送IP地址
如果配置了 slave_announce_ip,则 slave 向 master 发送 slave_announce_ip 配置的 IP 地址, master 收到后记录在 slave 所对应的客户端状态的 slave_ip 属性。
该配置是用于解决服务器返回内网 IP 时,其他服务器无法访问的情况。可以通过该配置直接指定公网 IP。
7、发送CAPA
CAPA 全称是 capabilities,这边表示的是同步复制的能力。
slave 会在这一阶段发送 capa 告诉 master 自己具备的(同步)复制能力, master 收到后记录在 slave 所对应的客户端状态的 slave_capa 属性。
CAPA 在最新的 Redis 6.0 版本中有两种值:eof 和 psync2。
eof 表示 slave 支持直接接收从 socket 发送过来的 RDB 数据流,也就是无盘加载(diskless_load)。
psync2 表示 slave 支持 Redis 4.0 引入的部分重同步 v2 版本,这个在下文会详细介绍。
8、数据同步
slave 将向 master 发送 PSYNC 命令, master 收到该命令后判断是进行部分重同步还是完整重同步,然后根据策略进行数据的同步。
1)如果是 slave 第一次执行复制,则向 master 发送 PSYNC ? -1, master 返回 +FULLRESYNC 执行完整重同步
2)如果不是第一次执行复制,则向 master 发送 PSYNC replid offset,其中 replid 是 master 的复制 ID,而 offset 是 slave 当前的复制偏移量。master 根据 replid 和 offset 来判断应该执行哪种同步操作。
如果是完整重同步,则返回 +FULLRESYNC ;如果是部分重同步,则返回 +CONTINUE ,此时 slave 只需等待 master 将自己缺少的数据发送过来就可以。
9、命令传播
当完成了同步之后,就会进人命令传播阶段,这时 master 只要一直将自己执行的写命令发送给 slave ,而 slave 只要一直接收并执行 master 发来的写命令,就可以保证 master 和 slave 一直保持一致了。
在命令传播阶段, slave 默认会以每秒一次的频率,向 master 发送命令:REPLCONF ACK ,其中 reploff 是 slave 当前的复制偏移量。
发送REPLCONF ACK 命令对于主从服务器有三个作用:
1)检测 master 和 slave 的网络连接状态。
2)汇报自己的复制偏移量,检测命令丢失,master 会对比复制偏移量,如果发现 slave 的复制偏移量小于自己,会向 slave 发送未同步的数据。
3)辅助实现 min-slaves 配置,用于防止 master 在不安全的情况下执行写命令。
例如以下配置表示:当延迟时间小于10秒的 slave 数量小于3个,则会拒绝执行写命令。而这边的延迟时间,就是以 slave 最近一次发送 ACK 时间和当前时间作对比。
min-slaves-to-write 3``min-slaves-max-lag 10
以部分重同步为例,主从复制的核心步骤流程图如下:
相关源码在 replication.c,核心方法是:replicationSetMaster、connectWithMaster、syncWithMaster
旧版同步:SYNC
Redis 2.8 之前的数据同步通过 SYNC 命令完成,完整流程如下:
1、slave 向 master 发送 SYNC 命令。
2、master 收到 SYNC 命令后执行 BGSAVE 命令,fork 子进程生成 RDB 文件,同时会使用一个缓冲区记录从现在开始执行的所有写命令。
Redis 在这边使用了 COW(copy-on-write)的特性,这边简单介绍一下。
fork 子进程时,一种比较“愚蠢”的做法是将父进程的整个地址空间拷贝一份给子进程,但这是非常耗时的,因此一般不会这么做。
另一种做法是,fork 之后,父子进程共用父进程已有的地址空间,只有当父子进程要进行写操作时,才将要修改的内容复制一份,再进行写操作,这也是 copy-on-write 名字的由来。
回到本文,这边当主进程 fork 出子进程时,因为 COW 的关系,可以认为在 fork 的这一刻,快照已经生成了,只是还没写到 RDB 文件。
那这边就有一个问题,RDB 文件是 fork 这一刻的数据,从 fork 这一刻到 master 将 RDB 文件发送给 slave 之间,主进程还在继续执行写命令,这期间的写命令 slave 怎么获得?
这就用到上面“同时会使用一个缓冲区记录从现在开始执行的所有写命令”,这个缓冲区会记录 fork 之后的所有写命令。
后面当 master 将 RDB 文件发送给 slave 后,master 会继续将缓冲区中的写命令发送给 slave,也就是下面的第4步,从而保证 slave 的数据是完整的。
3、当 BGSAVE 命令执行完毕,master 会将生成的 RDB 文件发送给 slave。slave 接收 RDB 文件,并载入到内存,将数据库状态更新至 master 执行 BGSAVE 时的数据库状态。
这边发送 RDB 文件的方式有两种:1)socket:master 将 RDB 文件流通过 socket 直接发送到 slave;2)disk:master 将 RDB 文件先持久化到磁盘,再发送到 slave。
默认使用方式为 disk,可以通过以下配置来使用 socket 方式。
repl-diskless-sync yes
同时,相关的参数配置还有 diskless-sync-delay:该参数表示等待一定时长再开始复制,这样可以等待多个 slave 节点重新连接上来。
socket(无磁盘)方式适合磁盘读写速度慢但网络带宽非常高的环境。
另外,这边主进程检查子进程 BGSAVE 是否执行完毕是通过时间事件定时检查的。
4、master 将记录在缓冲区里面的所有写命令发送给 slave,slave 执行这些命令,将数据库状态更新至 master 当前所处的状态。
SYNC 存在的问题:slave 每次断线重连都需要使用完整重同步,效率低下。
新版同步:SYNC
为了解决 slave 每次断线重连都需要使用完整重同步,redis 在 2.8 版本引入了 PSYNC,PSYNC 包含完整重同步和部分重同步。
1、完整重同步:和 SYNC 命令基本一致。
2、部分重同步:slave 只需要接收和同步断线期间丢失的写命令即可,不需要进行完整重同步。
为了实现部分重同步,Redis 引入了复制偏移量、复制积压缓冲区和运行 ID 三个概念。
复制偏移量(offset)
执行主从复制的双方都会分别维护一个复制偏移量,master 每次向 slave 传播 N 个字节,自己的复制偏移量就增加 N;同理 slave 接收 N 个字节,复制偏移量也增加 N。通过对比主从之间的复制偏移量就可以知道主从间的同步状态。
复制积压缓冲区(replication backlog buffer)
复制积压缓冲区是 master 维护的一个固定长度的 FIFO 队列,默认大小为 1MB。
当 master 进行命令传播时,不仅将写命令发给 slave 还会同时写进复制积压缓冲区,因此 master 的复制积压缓冲区会保存一部分最近传播的写命令。
当 slave 重连上 master 时会将自己的复制偏移量通过 PSYNC 命令发给 master,master 检查自己的复制积压缓冲区,如果发现这部分未同步的命令还在自己的复制积压缓冲区中的话就可以利用这些保存的命令进行部分同步,反之如果断线太久这部分命令已经不在复制缓冲区中了,那没办法只能进行全量同步。
运行 ID(runid)
每个 Redis server 都会有自己的运行 ID,由 40 个随机的十六进制字符组成。当 slave 初次复制 master 时,master 会将自己的运行 ID 发给 slave 进行保存,这样 slave 重连时再将这个运行 ID 发送给重连上的 master ,master 会接受这个 ID 并与自身的运行 ID 比较进而判断是否是同一个 master。
引入这三个概念后,数据同步过程如下:
1)slave 通过 PSYNC runid offset 命令,将正在复制的 runid 和 offset 发送给 master。
2)master 判断 runid 和自己的 runid 相同,并且 offset 还在复制积压缓冲区,则进行部分重同步:通过复制积压缓冲区将 slave 缺失的命令发送给 slave,slave 执行命令,将数据库状态更新至 master 所处的状态。
3)否则,如果 master 判断 runid 不相同,或者 offset 已经不在复制积压缓冲区,则执行完整重同步。
PSYNC 的完整流程如下图:
PSYNC 存在的问题
通过上述流程,我们可以看出,PSYNC 执行部分重同步需要满足两个条件:1)master runid 不变;2)复制偏移量在 master 复制积压缓冲区中。一旦不满足这两个条件,则仍然需要进行完整重同步,例如以下场景。
1、slave 重启,缓存的 master runid 和 offset 都会丢失,slave 需进行完整重同步。
2、redis 发生故障切换,故障切换后 master runid 发生了变化,slave 需进行完整重同步。
slave 维护性重启、master 故障切换都是 redis 运维常见场景,因此,PSYNC 的这两个问题出现概率还是非常高的。
相关源码在 replication.c,核心方法是:syncCommand、readSyncBulkPayload、replicationFeedSlaves、backgroundSaveDoneHandler、slaveTryPartialResynchronization 等
PSYNC2
为了解决 PSYNC 在 slave 重启和故障切换导致完整重同步的问题,Redis 在 4.0 版本中对 PSYNC 进行了优化,我们称为 PSYNC2。
PSYNC2 进行了以下2个主要改动:
1、引入两组 replid 和 offset 替换原来的 runid 和 offset
第一组:replid 和 master_repl_offset
对于 master,表示为自己的复制 ID 和复制偏移量;
对于 slave,表示为自己正在同步的 master 的复制 ID 和复制偏移量。
可以认为这一组的两个字段就是对应原来的 runid 和 offset。
第二组:replid2 和 second_repl_offset
对于 master 和 slave,都表示自己的上一个 master 的复制 ID 和复制偏移量;主要用于故障切换时支持部分重同步。
值得注意的是,runid 并不是在引入 replid 之后就不存在了。在 4.0 之前,redis 使用 runid 来作为主从复制的标识,而在 4.0 后引入了 replid 来作为主从复制的标识,但是,runid 在 redis 中的功能不仅仅是作为主从复制的标识,runid 仍然有其他的功能,例如:用于作为 redis 服务器的唯一标识。
2、slave 也会开启复制积压缓冲区
slave 开启复制积压缓冲区,主要是用于故障切换后,当某个 slave 升级为 master,该 slave 仍然可以通过复制积压缓冲区继续支持部分重同步功能。
如果 slave 不开启复制积压缓冲区,当该 slave 升级为 master 后,复制积压缓冲区是空的,就没法支持部分重同步了。
读者福利
分享一份自己整理好的Java面试手册,还有一些面试题pdf
不要停下自己学习的脚步
后,复制积压缓冲区是空的,就没法支持部分重同步了。
读者福利
分享一份自己整理好的Java面试手册,还有一些面试题pdf
不要停下自己学习的脚步
[外链图片转存中…(img-Vjn5tvXo-1715619275513)]
[外链图片转存中…(img-u3MoU0NU-1715619275513)]