{"msg":"操作成功","code":200,"data":{"createBy":"admin","createTime":"2020-10-13 13:07:55","updateBy":"admin","updateTime":"2020-10-13 13:07:55","remark":null,"id":36,"articleTitle":"Redis（三）之主从复制","articleUrl":"redis_master_to_slave","articleThumbnail":"https://www.asumimoe.com/imgfiles/20220908/828bf19b43f2452d8540393410257d30.png","articleFlag":"1","draftStatus":"1","reprintStatement":"0","articleSummary":"主从复制，是指将一台Redis服务器的数据，复制到其他的Redis服务器。前者称为主节点(master)，后者称为从节点(slave)；数据的复制是单向的，只能由主节点到从节点。","articleContent":"## 一、主从复制概述\n\n主从复制，是指将一台Redis服务器的数据，复制到其他的Redis服务器。前者称为主节点(master)，后者称为从节点(slave)；数据的复制是单向的，只能由主节点到从节点。\n\n默认情况下，每台Redis服务器都是主节点；且一个主节点可以有多个从节点(或没有从节点)，但一个从节点只能有一个主节点。\n\n**主从复制作用：**\n\n1. 数据冗余：主从复制实现了数据的热备份，是持久化之外的一种数据冗余方式。\n2. 故障恢复：当主节点出现问题时，可以由从节点提供服务，实现快速的故障恢复；实际上是一种服务的冗余。\n3. 负载均衡：在主从复制的基础上，配合读写分离，可以由主节点提供写服务，由从节点提供读服务（即写Redis数据时应用连接主节点，读Redis数据时应用连接从节点），分担服务器负载；尤其是在写少读多的场景下，通过多个从节点分担读负载，可以大大提高Redis服务器的并发量。\n4. 高可用基石：除了上述作用以外，主从复制还是哨兵和集群能够实施的基础，因此说主从复制是Redis高可用的基础。\n\n## 二、如何使用主从复制\n\n实现主从复制有三种方式\n\n- 配置文件\n\n  在从服务器配置文件中加入slaveof \\<masterip> \\<masterport>\n\n- 启动命令\n\n  redis-server启动时加入--slaveof \\<masterip> \\<masterport>\n\n- 客户端命令\n\n  通过redis-cli连接到数据库后执行命令slaveof \\<masterip> \\<masterport>\n\n以上三种方式具有同样的效果\n\n### 1.启动两个节点\n\n为方便起见，将配置文件复制一份，将端口修改为6380。\n\n将两个节点分别启动\n\n```bash\nredis-server /usr/local/redis/6379.conf\nredis-server /usr/local/redis/6380.conf\n```\n\n### 2.开启复制\n\n在6380节点上执行slaveof命令\n\n```bash\nredis-cli -p 6380\n127.0.0.1:6380> slaveof 127.0.0.1 6379\nOK\n```\n\n### 3.验证效果\n\n1. 首先在从节点查询一个不存在的key；\n\n   ```bash\n   127.0.0.1:6380> get mykey\n   (nil)\n   ```\n\n2. 在主节点上增加这个key；\n\n   ```bash\n   127.0.0.1:6379> set mykey myvalue\n   OK\n   ```\n\n3. 在从节点再次查看这个key，发现主节点的操作已经同步至从节点；\n\n   ```bash\n   127.0.0.1:6380> get mykey\n   \"myvalue\"\n   ```\n\n4. 在主节点删除这个key；\n\n   ```bash\n   127.0.0.1:6379> del mykey\n   (integer) 1\n   ```\n\n5. 此时在从节点中再次查看这个key，发现主节点的操作已经同步至从节点；\n\n   ```bash\n   127.0.0.1:6380> get mykey\n   (nil)\n   ```\n\n### 4.断开复制\n\n通过slaveof <masterip> <masterport>命令建立主从复制关系以后，可以通过slaveof no one断开。需要注意的是，从节点断开复制后，不会删除已有的数据，只是不再接受主节点新的数据变化。\n\n执行命令后，主节点会打印以下日志：\n\n```shell\n8631:M 05 Oct 2020 16:44:48.325 # Connection with replica 127.0.0.1:6380 lost.\n```\n\n\n\n## 三、主从复制的实现原理\n\n主从复制过程大体可以分为3个阶段：连接建立阶段（即准备阶段）、数据同步阶段、命令传播阶段；下面分别进行介绍。\n\n### 1.连接建立阶段\n\n1. 保存主节点信息\n\n   从节点服务器内部维护了两个字段，即masterhost和masterport字段，用于存储主节点的ip和port信息。\n\n   需要注意的是，**slaveof****是异步命令，从节点完成主节点ip****和port****的保存后，向发送slaveof****命令的客户端直接返回OK****，实际的复制操作在这之后才开始进行。**\n\n   这个过程中，可以看到从节点打印日志如下：\n\n   ```shell\n   8997:M 05 Oct 2020 16:49:20.281 * MASTER MODE enabled (user request from 'id=3 addr=127.0.0.1:15605 fd=9 name= age=58 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=34 qbuf-free=32734 obl=0 oll=0 omem=0 events=r cmd=slaveof')\n   ```\n\n2. 建立socket连接\n\n   从节点每秒1次调用复制定时函数replicationCron()，如果发现了有主节点可以连接，便会根据主节点的ip和port，创建socket连接。如果连接成功，则：\n\n   从节点：为该socket建立一个专门处理复制工作的文件事件处理器，负责后续的复制工作，如接收RDB文件、接收命令传播等。\n\n   主节点：接收到从节点的socket连接后（即accept之后），为该socket创建相应的客户端状态，**并将从节点看做是连接到主节点的一个客户端，后面的步骤会以从节点向主节点发送命令请求的形式来进行。**\n\n   这个过程中，从节点打印日志如下：\n\n   ```shell\n   8997:S 05 Oct 2020 16:50:52.958 * Connecting to MASTER 127.0.0.1:6379\n   8997:S 05 Oct 2020 16:50:52.959 * MASTER <-> REPLICA sync started\n   ```\n\n3. 发送ping命令\n\n   从节点成为主节点的客户端之后，发送ping命令进行首次请求，目的是：检查socket连接是否可用，以及主节点当前是否能够处理请求。\n\n   从节点发送ping命令后，可能出现3种情况：\n\n   - 返回pong：说明socket连接正常，且主节点当前可以处理请求，复制过程继续。\n\n   - 超时：一定时间后从节点仍未收到主节点的回复，说明socket连接不可用，则从节点断开socket连接，并重连。\n\n   - 返回pong以外的结果：如果主节点返回其他结果，如正在处理超时运行的脚本，说明主节点当前无法处理命令，则从节点断开socket连接，并重连。\n\n   在主节点返回pong情况下，从节点打印日志如下：\n\n   ```shell\n   8997:S 05 Oct 2020 16:50:52.959 * Non blocking connect for SYNC fired the event.\n   8997:S 05 Oct 2020 16:50:52.959 * Master replied to PING, replication can continue...\n   ```\n\n4. 身份验证\n\n   如果从节点中设置了masterauth选项，则从节点需要向主节点进行身份验证；没有设置该选项，则不需要验证。从节点进行身份验证是通过向主节点发送auth命令进行的，auth命令的参数即为配置文件中的masterauth的值。\n\n   如果主节点设置密码的状态，与从节点masterauth的状态一致（一致是指都存在，且密码相同，或者都不存在），则身份验证通过，复制过程继续；如果不一致，则从节点断开socket连接，并重连。\n\n5. 发送从节点端口信息\n\n   身份验证之后，从节点会向主节点发送其监听的端口号（前述例子中为6380），主节点将该信息保存到该从节点对应的客户端的slave_listening_port字段中；该端口信息除了在主节点中执行info Replication时显示以外，没有其他作用。\n\n### 2.数据同步阶段\n\n主从节点之间的连接建立后，便可进行数据同步，该阶段可以理解为从节点数据的初始化。具体执行的方式是：从节点向主节点发送psync命令，开始同步。\n\n数据同步阶段是主从复制最核心的阶段，根据主从节点当前的状态的不同，可以分为全量复制和部分复制。需要注意的是，在数据同步阶段之前，从节点是主节点的客户端，主节点不是从节点的客户端；而到了这一阶段及以后，主从节点互为客户端。原因在于：在此之前，主节点只需要响应从节点的请求即可，不需要主动发请求，而在数据同步阶段和后面的命令传播阶段，主节点需要向从节点发送请求（如推送缓冲区中的写命令），才能完成复制。\n\n### 3.命令传播阶段\n\n数据同步阶段完成后，主从节点进入命令传播阶段；在这个阶段中主节点将自己执行的命令发送给从节点，从节点接收命令并执行，从而保证主从节点数据的一致性。\n\n**延迟与不一致**\n\n需要注意的是，命令传播是异步的过程，即主节点发送写命令后并不会等待从节点的回复；因此实际上主从节点之间很难保持实时的一致性，延迟在所难免。数据不一致的程度，与主从节点之间的网络状况、主节点写命令的执行频率、以及主节点中的repl-disable-tcp-nodelay配置等有关。\n\nrepl-disable-tcp-nodelay  no：该配置作用于命令传播阶段，控制主节点是否禁止与从节点的TCP_NODELAY；默认no，即不禁止TCP_NODELAY。当设置为yes时，TCP会对包进行合并从而减少带宽，但是发送的频率会降低，从节点数据延迟增加，一致性变差；具体发送频率与Linux内核的配置有关，默认配置为40ms。当设置为no时，TCP会立马将主节点的数据发送给从节点，带宽增加但延迟变小。\n\n一般来说，只有当应用对Redis数据不一致的容忍度较高，且主从节点之间网络状况不好时，才会设置为yes；多数情况使用默认值no。\n\n## 四、数据同步阶段【全量复制和部分复制】\n\n在Redis2.8及以后，从节点可以发送psync命令请求同步数据，此时根据主从节点当前状态的不同，同步方式可能是全量复制或部分复制。\n\n1. 全量复制：用于初次复制或其他无法进行部分复制的情况，将主节点中的所有数据都发送给从节点，是一个非常重型的操作。\n2. 部分复制：用于网络中断等情况后的复制，只将中断期间主节点执行的写命令发送给从节点，与全量复制相比更加高效。需要注意的是，如果网络中断时间过长，导致主节点没有能够完整地保存中断期间执行的写命令，则无法进行部分复制，仍使用全量复制。\n\n### 1.全量复制\n\nRedis通过psync命令进行全量复制的过程如下：\n\n（1）从节点判断无法进行部分复制，向主节点发送全量复制的请求；或从节点发送部分复制的请求，但主节点判断无法进行部分复制；\n\n（2）主节点收到全量复制的命令后，执行bgsave，在后台生成RDB文件，并使用一个缓冲区（称为复制缓冲区）记录从现在开始执行的所有写命令；\n\n（3）主节点的bgsave执行完成后，将RDB文件发送给从节点，从节点首先清除自己的旧数据，然后载入接收到的RDB文件，将数据库状态更新至主节点执行bgsave时的数据库状态；\n\n（4）主节点将前述复制缓冲区中的所有写命令发送给从节点，从节点执行这些写命令，将数据库状态更新至主节点的最新状态；\n\n（5）如果从节点开启了AOF，则会触发bgwriteaof的执行，从而保证AOF文件更新至主节点的最新状态。\n\n主节点打印日志：\n\n```shell\n3217:M 13 Oct 2020 14:28:24.236 * Replica 127.0.0.1:6380 asks for synchronization\nReplication ID mismatch (Replica asked for '1370f82db11691b10843a63b2c6c20ba05d3fe0e', my replication IDs are '6accc78f6ff4ccb342199b0454da2fe38573e421' and '0000000000000000000000000000000000000000')\n3217:M 13 Oct 2020 14:28:24.236 * Starting BGSAVE for SYNC with target: disk\n3217:M 13 Oct 2020 14:28:24.341 * Background saving started by pid 3358\n3358:C 13 Oct 2020 14:28:24.341 * DB saved on disk\n3358:C 13 Oct 2020 14:28:24.341 * RDB: 4 MB of memory used by copy-on-write\n3217:M 13 Oct 2020 14:28:24.349 * Background saving terminated with success\n3217:M 13 Oct 2020 14:28:24.349 * Synchronization with replica 127.0.0.1:6380 succeeded\n\n```\n\n从节点打印日志：\n\n```shell\n3281:S 13 Oct 2020 14:28:24.349 * Full resync from master: e56a3a3077cbe82d149b4daccf6f68df96c6cd6e:0\n3281:S 13 Oct 2020 14:28:24.349 * MASTER <-> REPLICA sync: receiving 188 bytes from master\n3281:S 13 Oct 2020 14:28:24.349 * MASTER <-> REPLICA sync: Flushing old data\n3281:S 13 Oct 2020 14:28:24.349 * MASTER <-> REPLICA sync: Loading DB in memory\n3281:S 13 Oct 2020 14:28:24.349 * MASTER <-> REPLICA sync: Finished with success\n3281:S 13 Oct 2020 14:28:24.357 * Background append only file rewriting started by pid 3359\n3281:S 13 Oct 2020 14:28:24.384 * AOF rewrite child asks to stop sending diffs.\n3359:C 13 Oct 2020 14:28:24.384 * Parent agreed to stop sending diffs. Finalizing AOF...\n3359:C 13 Oct 2020 14:28:24.384 * Concatenating 0.00 MB of AOF diff received from parent.\n3359:C 13 Oct 2020 14:28:24.384 * SYNC append only file rewrite performed\n3359:C 13 Oct 2020 14:28:24.385 * AOF rewrite: 4 MB of memory used by copy-on-write\n3281:S 13 Oct 2020 14:28:24.436 * Background AOF rewrite terminated with success\n3281:S 13 Oct 2020 14:28:24.436 * Residual parent diff successfully flushed to the rewritten AOF (0.00 MB)\n3281:S 13 Oct 2020 14:28:24.436 * Background AOF rewrite finished successfully\n```\n\n### 2.部分复制\n\n由于全量复制在主节点数据量较大时效率太低，因此Redis2.8开始提供部分复制，用于处理网络中断时的数据同步。\n\n部分复制的实现，依赖于三个重要的概念：\n\n**（1）复制偏移量**\n\n主节点和从节点分别维护一个复制偏移量（offset），代表的是**主节点向从节点传递的字节数**；主节点每次向从节点传播N个字节数据时，主节点的offset增加N；从节点每次收到主节点传来的N个字节数据时，从节点的offset增加N。\n\noffset用于判断主从节点的数据库状态是否一致：如果二者offset相同，则一致；如果offset不同，则不一致，此时可以根据两个offset找出从节点缺少的那部分数据。例如，如果主节点的offset是1000，而从节点的offset是500，那么部分复制就需要将offset为501-1000的数据传递给从节点。而offset为501-1000的数据存储的位置，就是下面要介绍的复制积压缓冲区。\n\n**（2）复制积压缓冲区**\n\n复制积压缓冲区是由主节点维护的、固定长度的、先进先出(FIFO)队列，默认大小1MB；当主节点开始有从节点时创建，其作用是备份主节点最近发送给从节点的数据。注意，无论主节点有一个还是多个从节点，都只需要一个复制积压缓冲区。\n\n在命令传播阶段，主节点除了将写命令发送给从节点，还会发送一份给复制积压缓冲区，作为写命令的备份；除了存储写命令，复制积压缓冲区中还存储了其中的每个字节对应的复制偏移量（offset）。由于复制积压缓冲区定长且是先进先出，所以它保存的是主节点最近执行的写命令；时间较早的写命令会被挤出缓冲区。\n\n由于该缓冲区长度固定且有限，因此可以备份的写命令也有限，当主从节点offset的差距过大超过缓冲区长度时，将无法执行部分复制，只能执行全量复制。反过来说，为了提高网络中断时部分复制执行的概率，可以根据需要增大复制积压缓冲区的大小(通过配置repl-backlog-size)；例如如果网络中断的平均时间是60s，而主节点平均每秒产生的写命令(特定协议格式)所占的字节数为100KB，则复制积压缓冲区的平均需求为6MB，保险起见，可以设置为12MB，来保证绝大多数断线情况都可以使用部分复制。\n\n从节点将offset发送给主节点后，主节点根据offset和缓冲区大小决定能否执行部分复制：\n\n- 如果offset偏移量之后的数据，仍然都在复制积压缓冲区里，则执行部分复制；\n- 如果offset偏移量之后的数据已不在复制积压缓冲区中（数据已被挤出），则执行全量复制。\n\n**（3）服务器运行ID(runid)**\n\n每个Redis节点(无论主从)，在启动时都会自动生成一个随机ID(每次启动都不一样)，由40个随机的十六进制字符组成；runid用来唯一识别一个Redis节点。通过info Server命令，可以查看节点的runid：\n\n```bash\n[root@localhost redis]# ./src/redis-cli -p 6379 info server | grep run_id\nrun_id:7218fc6afe8344f257c889c14c854ee91040c94f\n```\n\n主从节点初次复制时，主节点将自己的runid发送给从节点，从节点将这个runid保存起来；当断线重连时，从节点会将这个runid发送给主节点；主节点根据runid判断能否进行部分复制：\n\n- 如果从节点保存的runid与主节点现在的runid相同，说明主从节点之前同步过，主节点会继续尝试使用部分复制(到底能不能部分复制还要看offset和复制积压缓冲区的情况)；\n- 如果从节点保存的runid与主节点现在的runid不同，说明从节点在断线前同步的Redis节点并不是当前的主节点，只能进行全量复制。\n\n### 3.psync命令的执行\n\n在了解了复制偏移量、复制积压缓冲区、节点运行id之后，本节将介绍psync命令的参数和返回值，从而说明psync命令执行过程中，主从节点是如何确定使用全量复制还是部分复制的。\n\n（1）首先，从节点根据当前状态，决定如何调用psync命令：\n\n- 如果从节点之前未执行过slaveof或最近执行了slaveof no one，则从节点发送命令为psync ? -1，向主节点请求全量复制；\n- 如果从节点之前执行了slaveof，则发送命令为psync <runid> <offset>，其中runid为上次复制的主节点的runid，offset为上次复制截止时从节点保存的复制偏移量。\n\n（2）主节点根据收到的psync命令，及当前服务器状态，决定执行全量复制还是部分复制：\n\n- 如果主节点版本低于Redis2.8，则返回-ERR回复，此时从节点重新发送sync命令执行全量复制；\n- 如果主节点版本够新，且runid与从节点发送的runid相同，且从节点发送的offset之后的数据在复制积压缓冲区中都存在，则回复+CONTINUE，表示将进行部分复制，从节点等待主节点发送其缺少的数据即可；\n- 如果主节点版本够新，但是runid与从节点发送的runid不同，或从节点发送的offset之后的数据已不在复制积压缓冲区中(在队列中被挤出了)，则回复+FULLRESYNC <runid>  <offset>，表示要进行全量复制，其中runid表示主节点当前的runid，offset表示主节点当前的offset，从节点保存这两个值，以备使用。\n\n## 五、命令传播阶段【心跳机制】\n\n在命令传播阶段，除了发送写命令，主从节点还维持着心跳机制：PING和REPLCONF ACK。心跳机制对于主从复制的超时判断、数据安全等有作用。\n\n### 1.主→从：PING\n\n每隔指定的时间，**主节点会向从节点发送**PING命令，这个PING命令的作用，主要是为了让从节点进行超时判断。\n\nPING发送的频率由repl-ping-slave-period参数控制，单位是秒，默认值是10s。\n\n```shell\n# Replicas send PINGs to server in a predefined interval. It's possible to change\n# this interval with the repl_ping_replica_period option. The default value is 10\n# seconds.\n#\n# repl-ping-replica-period 10\n```\n\n### 2.从→主：REPLCONF ACK\n\n在命令传播阶段，**从节点会向主节点发送REPLCONF ACK命令**，频率是每秒1次；命令格式为：REPLCONF ACK {offset}，其中offset指从节点保存的复制偏移量。REPLCONF ACK命令的作用包括：\n\n（1）实时监测主从节点网络状态：该命令会被主节点用于复制超时的判断。此外，在主节点中使用info  Replication，可以看到其从节点的状态中的lag值，代表的是主节点上次收到该REPLCONF  ACK命令的时间间隔，在正常情况下，该值应该是0或1，如下所示：\n\n```bash\n./src/redis-cli -p 6379 info Replication | grep lag\nslave0:ip=127.0.0.1,port=6380,state=online,offset=5390,lag=0\n```\n\n（2）检测命令丢失：从节点发送了自身的offset，主节点会与自己的offset对比，如果从节点数据缺失（如网络丢包），主节点会推送缺失的数据（这里也会利用复制积压缓冲区）。**注意，offset和复制积压缓冲区，不仅可以用于部分复制，也可以用于处理命令丢失等情形；区别在于前者是在断线重连后进行的，而后者是在主从节点没有断线的情况下进行的。**\n\n（3）辅助保证从节点的数量和延迟：Redis主节点中使用min-slaves-to-write和min-slaves-max-lag参数，来保证主节点在不安全的情况下不会执行写命令；所谓不安全，是指从节点数量太少，或延迟过高。例如min-slaves-to-write和min-slaves-max-lag分别是3和10，含义是如果从节点数量小于3个，或所有从节点的延迟值都大于10s，则主节点拒绝执行写命令。而这里从节点延迟值的获取，就是通过主节点接收到REPLCONF ACK命令的时间来判断的，即前面所说的info Replication中的lag值。\n\n## 六、应用中的问题\n\n### 1.读写分离及其中的问题\n\n在主从复制基础上实现的读写分离，可以实现Redis的读负载均衡：由主节点提供写服务，由一个或多个从节点提供读服务（多个从节点既可以提高数据冗余程度，也可以最大化读负载能力）；在读负载较大的应用场景下，可以大大提高Redis服务器的并发量。下面介绍在使用Redis读写分离时，需要注意的问题。\n\n#### （1）延迟与不一致问题\n\n由于主从复制的命令传播是异步的，延迟与数据的不一致不可避免。如果应用对数据不一致的接受程度程度较低，可能的优化措施包括：优化主从节点之间的网络环境（如在同机房部署）；监控主从节点延迟（通过offset）判断，如果从节点延迟过大，通知应用不再通过该从节点读取数据；使用集群同时扩展写负载和读负载等。\n\n在命令传播阶段以外的其他情况下，从节点的数据不一致可能更加严重，例如连接在数据同步阶段，或从节点失去与主节点的连接时等。从节点的slave-serve-stale-data参数便与此有关：它控制这种情况下从节点的表现；如果为yes（默认值），则从节点仍能够响应客户端的命令，如果为no，则从节点只能响应info、slaveof等少数命令。该参数的设置与应用对数据一致性的要求有关；如果对数据一致性要求很高，则应设置为no。\n\n#### （2）数据过期问题\n\n在单机版Redis中，存在两种删除策略：\n\n- 惰性删除：服务器不会主动删除数据，只有当客户端查询某个数据时，服务器判断该数据是否过期，如果过期则删除。\n- 定期删除：服务器执行定时任务删除过期数据，但是考虑到内存和CPU的折中（删除会释放内存，但是频繁的删除操作对CPU不友好），该删除的频率和执行时间都受到了限制。\n\n在主从复制场景下，为了主从节点的数据一致性，从节点不会主动删除数据，而是由主节点控制从节点中过期数据的删除。由于主节点的惰性删除和定期删除策略，都不能保证主节点及时对过期数据执行删除操作，因此，当客户端通过Redis从节点读取数据时，很容易读取到已经过期的数据。\n\nRedis 3.2中，从节点在读取数据时，增加了对数据是否过期的判断：如果该数据已过期，则不返回给客户端；将Redis升级到3.2可以解决数据过期问题。\n\n#### （3）故障切换问题\n\n在没有使用哨兵的读写分离场景下，应用针对读和写分别连接不同的Redis节点；当主节点或从节点出现问题而发生更改时，需要及时修改应用程序读写Redis数据的连接；连接的切换可以手动进行，或者自己写监控程序进行切换，但前者响应慢、容易出错，后者实现复杂，成本都不算低。\n\n### 2.复制超时问题\n\n**超时判断意义**\n\n在复制连接建立过程中及之后，主从节点都有机制判断连接是否超时，其意义在于：\n\n（1）如果主节点判断连接超时，其会释放相应从节点的连接，从而释放各种资源，否则无效的从节点仍会占用主节点的各种资源（输出缓冲区、带宽、连接等）；此外连接超时的判断可以让主节点更准确的知道当前有效从节点的个数，有助于保证数据安全（配合前面讲到的min-slaves-to-write等参数）。\n\n（2）如果从节点判断连接超时，则可以及时重新建立连接，避免与主节点数据长期的不一致。\n\n**判断机制**\n\n主从复制超时判断的核心，在于repl-timeout参数，该参数规定了超时时间的阈值（默认60s），对于主节点和从节点同时有效；主从节点触发超时的条件分别如下：\n\n（1）主节点：每秒1次调用复制定时函数replicationCron()，在其中判断当前时间距离上次收到各个从节点REPLCONF ACK的时间，是否超过了repl-timeout值，如果超过了则释放相应从节点的连接。\n\n（2）从节点：从节点对超时的判断同样是在复制定时函数中判断，基本逻辑是：\n\n- 如果当前处于连接建立阶段，且距离上次收到主节点的信息的时间已超过repl-timeout，则释放与主节点的连接；\n- 如果当前处于数据同步阶段，且收到主节点的RDB文件的时间超时，则停止数据同步，释放连接；\n- 如果当前处于命令传播阶段，且距离上次收到主节点的PING命令或数据的时间已超过repl-timeout值，则释放与主节点的连接。\n\n**需要注意的坑**\n\n（1）数据同步阶段：在主从节点进行全量复制bgsave时，主节点需要首先fork子进程将当前数据保存到RDB文件中，然后再将RDB文件通过网络传输到从节点。如果RDB文件过大，主节点在fork子进程+保存RDB文件时耗时过多，可能会导致从节点长时间收不到数据而触发超时；此时从节点会重连主节点，然后再次全量复制，再次超时，再次重连……这是个悲伤的循环。为了避免这种情况的发生，除了注意Redis单机数据量不要过大，另一方面就是适当增大repl-timeout值，具体的大小可以根据bgsave耗时来调整。\n\n（2）命令传播阶段：如前所述，在该阶段主节点会向从节点发送PING命令，频率由repl-ping-slave-period控制；该参数应明显小于repl-timeout值(后者至少是前者的几倍)。否则，如果两个参数相等或接近，网络抖动导致个别PING命令丢失，此时恰巧主节点也没有向从节点发送数据，则从节点很容易判断超时。\n\n（3）慢查询导致的阻塞：如果主节点或从节点执行了一些慢查询（如keys *或者对大数据的hgetall等），导致服务器阻塞；阻塞期间无法响应复制连接中对方节点的请求，可能导致复制超时。\n\n### 3.复制中断问题\n\n**复制缓冲区溢出**\n\n前面曾提到过，在全量复制阶段，主节点会将执行的写命令放到复制缓冲区中，该缓冲区存放的数据包括了以下几个时间段内主节点执行的写命令：bgsave生成RDB文件、RDB文件由主节点发往从节点、从节点清空老数据并载入RDB文件中的数据。当主节点数据量较大，或者主从节点之间网络延迟较大时，可能导致该缓冲区的大小超过了限制，此时主节点会断开与从节点之间的连接；这种情况可能引起全量复制->复制缓冲区溢出导致连接中断->重连->全量复制->复制缓冲区溢出导致连接中断……的循环。\n\n复制缓冲区的大小由client-output-buffer-limit slave {hard limit} {soft limit}  {soft seconds}配置，默认值为client-output-buffer-limit slave 256MB 64MB  60，其含义是：如果buffer大于256MB，或者连续60s大于64MB，则主节点会断开与该从节点的连接。该参数是可以通过config  set命令动态配置的（即不重启Redis也可以生效）。\n\n日志中有如下关键字则复制缓冲区溢出：\n\n```shell\nClient scheduled to be closed ASAP for overcoming of output buffer limits\nConnection with slave lost\n```\n\n### 4.复制的选择\n\n#### （1）主节点重启\n\n**主节点宕机**\n\n主节点宕机重启后，runid会发生变化，因此不能进行部分复制，只能进行全量复制。\n\n实际上在主节点宕机的情况下，应进行故障转移处理，将其中的一个从节点升级为主节点，其他从节点从新的主节点进行复制；且故障转移应尽量的自动化。\n\n**安全重启**\n\n在一些场景下，可能希望对主节点进行重启，例如主节点内存碎片率过高，或者希望调整一些只能在启动时调整的参数。如果使用普通的手段重启主节点，会使得runid发生变化，可能导致不必要的全量复制。\n\n为了解决这个问题，Redis提供了debug reload的重启方式：**重启后，主节点的runid和offset都不受影响，**避免了全量复制。\n\n但debug reload是一柄双刃剑：它会清空当前内存中的数据，重新从RDB文件中加载，这个过程会导致主节点的阻塞，因此也需要谨慎。\n\n#### （2）从节点重启\n\n从节点宕机重启后，其保存的主节点的runid会丢失，因此即使再次执行slaveof，也无法进行部分复制。\n\n### 5.相关的配置\n\n#### （1）与主从节点都有关的配置\n\n1)  slaveof \\<masterip> \\<masterport>：Redis启动时起作用；作用是建立复制关系，开启了该配置的Redis服务器在启动后成为从节点。该注释默认注释掉，即Redis服务器默认都是主节点。\n\n2)  repl-timeout 60：与各个阶段主从节点连接超时判断有关，见前面的介绍。\n\n#### （2）主节点相关配置\n\n1)  repl-diskless-sync  no：作用于全量复制阶段，控制主节点是否使用diskless复制（无盘复制）。所谓diskless复制，是指在全量复制时，主节点不再先把数据写入RDB文件，而是直接写入slave的socket中，整个过程中不涉及硬盘；diskless复制在磁盘IO很慢而网速很快时更有优势。需要注意的是，截至Redis3.0，diskless复制处于实验阶段，默认是关闭的。\n\n2)  repl-diskless-sync-delay  5：该配置作用于全量复制阶段，当主节点使用diskless复制时，该配置决定主节点向从节点发送之前停顿的时间，单位是秒；只有当diskless复制打开时有效，默认5s。之所以设置停顿时间，是基于以下两个考虑：(1)向slave的socket的传输一旦开始，新连接的slave只能等待当前数据传输结束，才能开始新的数据传输 (2)多个从节点有较大的概率在短时间内建立主从复制。\n\n3)  client-output-buffer-limit slave 256MB 64MB 60：与全量复制阶段主节点的缓冲区大小有关，见前面的介绍。\n\n4)  repl-disable-tcp-nodelay no：与命令传播阶段的延迟有关，见前面的介绍。\n\n5)  masterauth \\<master-password>：与连接建立阶段的身份验证有关，见前面的介绍。\n\n6)  repl-ping-slave-period 10：与命令传播阶段主从节点的超时判断有关，见前面的介绍。\n\n7)  repl-backlog-size 1mb：复制积压缓冲区的大小，见前面的介绍。\n\n8)  repl-backlog-ttl 3600：当主节点没有从节点时，复制积压缓冲区保留的时间，这样当断开的从节点重新连进来时，可以进行部分复制；默认3600s。如果设置为0，则永远不会释放复制积压缓冲区。\n\n9)  min-slaves-to-write 3与min-slaves-max-lag 10：规定了主节点的最小从节点数目，及对应的最大延迟。\n\n#### （3）从节点相关的配置\n\n1)  slave-serve-stale-data yes：与从节点数据陈旧时是否响应客户端命令有关，见前面的介绍。\n\n2)  slave-read-only yes：从节点是否只读；默认是只读的。由于从节点开启写操作容易导致主从节点的数据不一致，因此该配置尽量不要修改。\n\n\n\n转载自：[https://www.cnblogs.com/kismetv/p/9236731.html](https://www.cnblogs.com/kismetv/p/9236731.html)","categoryId":2,"viewCount":1518,"categoryName":"中间件","author":"球接子","authorAvatar":null,"tagIds":[14,1,17],"tagNames":["中间件","Redis","数据库"]}}