美文网首页redis
redis 主从超时检测

redis 主从超时检测

作者: 晴天哥_王志 | 来源:发表于2018-06-17 16:20 被阅读25次

 redis的主从超时检测主要从以下三个方面进行判断,分别是主监测从、从监测主、正常关闭。

  • 主监测从:slave定期发送replconf ack offset命令到master来报告自己的存活状况
  • 从监测主:master定期发送ping命令或者\n命令到slave来报告自己的存货状况
  • 正常关闭:eventLoop监测端口关闭的nio事件

周期性发送心跳命令

 定期执行函数serverCron内部会周期性的执行replicationCron方法,该方法内部执行的动作包括重连接主服务器、向主服务器发送 ACK 、判断数据发送失败情况、断开本服务器超时的从服务器,向从服务器发送PING或者\n命令

int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
    clientsCron();

    // 对数据库执行各种操作
    databasesCron();

    // 重连接主服务器、向主服务器发送 ACK 、判断数据发送失败情况、断开本服务器超时的从服务器,等等
    run_with_period(1000) replicationCron();

    // 增加 loop 计数器
    server.cronloops++;

    return 1000/server.hz;
}



 replicationCron内部做的事情就是周期性的发送心跳命令包括:

  • slave发往master的replconf ack offset,用于主检测从的存活性
  • master发往slave的PING命令,用于从检测主的存活性
  • master发往slave的\n命令,用于从检测主的存活性
// 复制 cron 函数,每秒调用一次
void replicationCron(void) {
        
        // 定期向主服务器发送 ACK 命令
        if (server.masterhost && server.master &&
        !(server.master->flags & REDIS_PRE_PSYNC))
        replicationSendAck();
    
    /* 
     * 如果服务器有从服务器,定时向它们发送 PING 。
     *
     * 这样从服务器就可以实现显式的 master 超时判断机制,
     * 即使 TCP 连接未断开也是如此。
     */
    if (!(server.cronloops % (server.repl_ping_slave_period * server.hz))) {
        listIter li;
        listNode *ln;
        robj *ping_argv[1];

        /* First, send PING */
        // 向所有已连接 slave (状态为 ONLINE)发送 PING
        ping_argv[0] = createStringObject("PING",4);
        replicationFeedSlaves(server.slaves, server.slaveseldb, ping_argv, 1);
        decrRefCount(ping_argv[0]);

        /*
         * 向那些正在等待 RDB 文件的从服务器(状态为 BGSAVE_START 或 BGSAVE_END)
         * 发送 "\n"
         *
         * 这个 "\n" 会被从服务器忽略,
         * 它的作用就是用来防止主服务器因为长期不发送信息而被从服务器误判为超时
         */
        listRewind(server.slaves,&li);
        while((ln = listNext(&li))) {
            redisClient *slave = ln->value;

            if (slave->replstate == REDIS_REPL_WAIT_BGSAVE_START ||
                slave->replstate == REDIS_REPL_WAIT_BGSAVE_END) {
                if (write(slave->fd, "\n", 1) == -1) {
                    /* Don't worry, it's just a ping. */
                }
            }
        }
    }
}

主监测从

 master收到slave的replconf命令的时候更新c->repl_ack_time,也就是代表收到slave发送ack命令的时间。

/* REPLCONF <option> <value> <option> <value> ...
 * 由 slave 使用,在 SYNC 之前配置复制进程(process)
 * 目前这个函数的唯一作用就是,让 slave 告诉 master 它正在监听的端口号
 * 然后 master 就可以在 INFO 命令的输出中打印这个号码了。
 * 将来可能会用这个命令来实现增量式复制,取代 full resync 。
 */
void replconfCommand(redisClient *c) {
    for (j = 1; j < c->argc; j+=2) {
      else if (!strcasecmp(c->argv[j]->ptr,"ack")) {
            // 更新最后一次发送 ack 的时间
            c->repl_ack_time = server.unixtime;
           
            return;
        } 
    }
    addReply(c,shared.ok);
}



 master从判断当前时间和上一次ack时间来判断slave的存,(server.unixtime - slave->repl_ack_time) > server.repl_timeout。如果超时就释放和slave的连接。

// 复制 cron 函数,每秒调用一次
void replicationCron(void) {
    // 断开超时从服务器
    if (listLength(server.slaves)) {
        listIter li;
        listNode *ln;

        // 遍历所有从服务器
        listRewind(server.slaves,&li);
        while((ln = listNext(&li))) {
            redisClient *slave = ln->value;
            // 释放超时从服务器
            if ((server.unixtime - slave->repl_ack_time) > server.repl_timeout)
            {
                char ip[REDIS_IP_STR_LEN];
                int port;

                if (anetPeerToString(slave->fd,ip,sizeof(ip),&port) != -1) {
                    redisLog(REDIS_WARNING,
                        "Disconnecting timedout slave: %s:%d",
                        ip, slave->slave_listening_port);
                }
                
                // 释放
                freeClient(slave);
            }
        }
    }

从监测主

 slave每次接收到master发送过来的命令的时候都会更新client的上一次交互时间也就是c->lastinteraction,这里的client c代表就是slave连接master的server.master的redis client对象。


/*
 * 读取客户端的查询缓冲区内容
 */
void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) {
    
    // 读入内容到查询缓存
    nread = read(fd, c->querybuf+qblen, readlen);

    if (nread) {
        // 记录服务器和客户端最后一次互动的时间
        c->lastinteraction = server.unixtime;
    } 

    // 从查询缓存重读取内容,创建参数,并执行命令
    // 函数会执行到缓存中的所有内容都被处理完为止
    processInputBuffer(c);
}



 slave定期检查当前时间和上一次交互时间的差值是否大于最大超时时间:(time(NULL)-server.master->lastinteraction) > server.repl_timeout,如果超时就断开连接。

// 复制 cron 函数,每秒调用一次
void replicationCron(void) {

    // 尝试连接到主服务器,但超时
    if (server.masterhost &&
        (server.repl_state == REDIS_REPL_CONNECTING ||
         server.repl_state == REDIS_REPL_RECEIVE_PONG) &&
        (time(NULL)-server.repl_transfer_lastio) > server.repl_timeout)
    {
        redisLog(REDIS_WARNING,"Timeout connecting to the MASTER...");
        // 取消连接
        undoConnectWithMaster();
    }

    // RDB 文件的传送已超时?
    if (server.masterhost && server.repl_state == REDIS_REPL_TRANSFER &&
        (time(NULL)-server.repl_transfer_lastio) > server.repl_timeout)
    {
        redisLog(REDIS_WARNING,"Timeout receiving bulk data from MASTER... If the problem persists try to set the 'repl-timeout' parameter in redis.conf to a larger value.");
        // 停止传送,并删除临时文件
        replicationAbortSyncTransfer();
    }

    // 从服务器曾经连接上主服务器,但现在超时
    if (server.masterhost && server.repl_state == REDIS_REPL_CONNECTED &&
        (time(NULL)-server.master->lastinteraction) > server.repl_timeout)
    {
        redisLog(REDIS_WARNING,"MASTER timeout: no data nor PING received...");
        // 释放主服务器
        freeClient(server.master);
    }

    // 尝试连接主服务器
    if (server.repl_state == REDIS_REPL_CONNECT) {
        redisLog(REDIS_NOTICE,"Connecting to MASTER %s:%d",
            server.masterhost, server.masterport);
        if (connectWithMaster() == REDIS_OK) {
            redisLog(REDIS_NOTICE,"MASTER <-> SLAVE sync started");
        }
    }
}

正常关闭

 判断socket正常的关闭的途径就是通过socket的read方法来判断:

  • 读取报文数据出错
  • 读取报文长度为0,这里需要解释下:
     1. TCP recv返回0, 说明对方关闭;
     2. 注册EPOLLERR, 收到事件是关闭;
     3. recv/send 返回-1时, 如果错误不是EWOULDBLOCK或者EINTR, 也主动关闭连接。
void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) {
   
    // 读入内容到查询缓存
    nread = read(fd, c->querybuf+qblen, readlen);

    // 读入出错
    if (nread == -1) {
        if (errno == EAGAIN) {
            nread = 0;
        } else {
            redisLog(REDIS_VERBOSE, "Reading from client: %s",strerror(errno));
            freeClient(c);
            return;
        }
    // 遇到 EOF
    } else if (nread == 0) {
        redisLog(REDIS_VERBOSE, "Client closed connection");
        freeClient(c);
        return;
    }
}

相关文章

  • redis 主从超时检测

     redis的主从超时检测主要从以下三个方面进行判断,分别是主监测从、从监测主、正常关闭。 主监测从:slave定...

  • redis集群

    本章要点 Redis主从 Redis哨兵 Redis集群 主从复制原理 1. Redis 主从 比较简单在redi...

  • redis(二)

    Redis用户认证 禁用危险命令 主从复制的过程 主从复制的部署 哨兵 Redis哨兵+主从+密码 Redis哨兵...

  • redis哨兵部署

    Sentinel(哨兵)用于监控redis集群中Master状态,自动对主从进行检测,当master有问题后,将会...

  • redis主从+keepalived实现高可用技术(转)

    redis主从+keepalived实现高可用技术keepalive+redis 主从高可用 Redis是我们当下...

  • redis高可用集群搭建

    主从配置 主redis:master-ip port从redis:slave-ip port 设置主从redis配...

  • Redis|主从复制与哨兵机制

    1. Redis主从复制 1.1 Redis主从复制 Redis主从复制主要有两个角色,主机(master)对外提...

  • redis 主从同步-slave端

    redis 主从命令  redis的主从同步始于命令SLAVEOF host port,通过这个命令能够建立主从关...

  • Redis持久化、主从复制

    Redis持久化、主从复制 [TOC] redis持久化 RDB & AOP redis主从同步策略 一、pers...

  • 【Redis学习笔记】13 Redis 主从复制

    1. Redis 主从复制特性 2. Redis 主从复制流程 3. Redis 主从复制操作 IP地址端口角色1...

网友评论

    本文标题:redis 主从超时检测

    本文链接:https://www.haomeiwen.com/subject/rwiyeftx.html