Redis主从是如何维持TCP连接并且检测异常断链?

本文探讨了Redis主从关系的建立、断链检测以及如何重新建立连接。Redis通过slaveof指令建立主从关系,从节点定期发送ping进行心跳检测。主节点则以固定周期发送ping,从节点据此检测主节点状态。断链时,从节点会在超时后尝试重新连接主节点,利用非阻塞IO和事件处理器实现连接重试。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近项目需要,所以研究了一下Redis主从的TCP链接是如何维持的,发现这一块还没有资料说明过,所以就把打算把这个东西记录一下分享出来。

这个问题可以拆成以下几个部分来说,包括:

  • Redis如何建立主从关系
  • Redis如何检测断链
  • Redis如何重新建立主从关系

下面一一说明。

一、Redis如何建立主从关系

redis使用slaveof ip host指令来建立主从关系,主从关系的建立会引发持续的数据同步。值得一提的是,redis的设计认为这个关系的建立是持续性的,所以也设计了重连机制、对master的验证机制、高效的数据重新同步的方法。下面具体描述这个过程及提到的几个概念。

建立主从关系

前备条件:
实例A:127.0.0.1 6379
实例B:127.0.0.1 6380
我们希望实例A实时备份实例B,所以我们对实例A发送指令slaveof 127.0.0.1 6380,这个逻辑如下图所示。

*图1 slaveofCommand指令发送与执行过程

  • redis-cli发送指令slaveof 127.0.0.1 6379
  • redis-server_6379接收slaveof 127.0.0.1 6379,执行slaveofCommand指令,将ip及port写到server结构体里,并修改状态标志位,同时回复OK
  • redis-server_6379执行周期函数serverCron,其中会执行函数replicationCron,这里会检测状态标志位,并调用connectWithMaster,这个函数最后会调用非阻塞的connect,并且安装事件处理函数syncWithMaster,当连接成功后,syncWithMaster被调用,启动与Master的同步协议或者是连接失败处理,如果是失败,则后续会一直尝试重连,所以我们会看到主从建立连接失败的情况下syncWithMaster每秒都会被执行一次。

二、Redis如何检测断链

断链这个问题比较复杂,需要考虑较多的情况,包括正常关闭、半打开、异常关闭,底层的网络编程一般来说都跑不开这几个话题,redis使用c语言作为开发语言,自己开发了一套IO复用机制,把这几个问题处理得很好,这里我不打算再叙述这几个问题,下面直奔主题。

1、主监测从

主从关系建立后,redis从会以1秒为周期,向redis主发送replconf ack offset指令,用以向master表达当前复制偏移量,并且让master得以以此作为依据判断redis从是否仍然存活,即心跳机制。

2、从监测主

主从关系建立后,redis主会以repl-ping-slave-period为周期,向redis从发送PING指令。repl-ping-slave-period可以在配置文件当中更改,不能小于1,因为replicationCron的周期是1,这是redis能处理的最小定时事件了(这是错误的,serverCron的周期才是redis能处理的最小定时事件)。
redis从以此为依据,建立心跳检测机制。redis从每次收到ping指令,会更新server..master->lastinteraction时间,并且之后每次执行replicationCron都会检测距离上次主从交互的时间是否超过了repl-timeout,这个也提供在配置文件当中。如果时间超出了,则redis从认为redis主已经挂掉,则开始清理这个master。
所以我们可以得出结论,redis从基于repl-ping-slave-periodrepl-timeout这两个配置来建立对于master的检测机制,前者最小不能小于replicationCron的周期,后者最小不能小于前者。

正常关闭

即Redis主主动发出Fin包,发起TCP关闭的流程。
redis从会为redis主建立客户端,redis发出Fin包,向redis从发送一个EOF。当redis从收到Fin包,产生可读事件,readQureyFromClient被调用,最终调用read返回0,获得EOF,感知到对端发起的关闭流程。

半打开状态

即对端已经无法通信,但是本地无法感知到,除非主动发起通信流程。
这种情况包括:

1、网络不通

2、主Redis进程崩溃或者主机down

3、主Redis主机网线被拿掉

4、中间网络设备的网络被拿掉

这种情况Redis从需要依赖心跳机制来监测,并且主动发起TCP关闭流程,将[Redis从-->Redis主]方向的TCP连接关闭。
根据我们所描述的从监测主的原理,这个情况都可以被发现。

三、Redis如何重新建立主从关系

Redis从向Redis主重新发起连接

Server结构体定义了masterhost及masterport两个域,用来存储master的ip地址与端口。

`struct redisServer {`

    `...`

    `char* server.masterhost;`

    `int masterport;`

    `...`

`}`

当redis接受了slaveof ip port指令后,就将ip及port写到以上两个域当中。并且执行server.repl_state = REPL_STATE_CONNECT
尔后在replicationCron函数当中根据根据以上操作,执行connect操作。具体如下:

`replicationCron()`

    `-->connectWithMaster()`

        `-->anetTcpNonBlockBestEffortBindConnect()`

        `-->aeCreateFileEvent()`

`replicationSendAck()`

replicationCron()是Redis惟一的定时事件的处理器,每秒被触发一次。所以如果从与主断掉了,从每秒会尝试重新一次。

但是会尝试多久,这个还需要再验证。

每次执行时,会检查是否需要连接master,如果需要就会调用connectWithMaster()
connectWithMaster()使用非阻塞connect()去连接master,并且在connect()返回后马上绑定了syncWithMaster()
这个地方很精妙,也很体现Redis作者功力的地方。原本我以为syncWithMaster只有在连接成功后才会被绑定到描述符上,但事实并不是这样。
connect()因为是非阻塞式的,所以我们没有办法立即得知是否连接成功了,但是当connect()的结果确定后,内核会反映到描述符上,如果成功则会产生可写事件,如果失败就会产生可读、可写事件。这个时候利用getsockopt()就可以判断connect()是否成功了。
这个地方还有一个注意事项,非阻塞式connect()会立即返回-1,但是fd是由sock()创建的,所以这个时候并不是说fd可以用来与远程服务器通信,但是我们的确可以用他来与内核通信,但是这个fd并不会出现在/proc文件系统里。

aeCreateFileEvent()会将anetTcpNonBlockBestEffortBindConnect()产生的fd及可读、可写事件与syncWithMaster()绑定到一起,当fd产生可读可写事件时,syncWithMaster()执行,如果连接失败,那么这个情况就会在syncWithMaster()被处理掉。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值