Redis源码篇(6)——主从复制

news2025/1/22 18:04:46

主从复制

从服务器执行 SLAVEOF IP PORT 命令即可实现对主服务器的复制。
复制又分为完整同步和部分同步(2.8之后)
完整同步:与混合持久化过程类似,先以rdb的方式保存rdb文件然后发送给从服务器。再将期间的命令保存到复制缓存区(每个从节点都有一份),最后以aof形式发给从服务器实现同步。
部分同步:因为存在这么一种情况,从服务器中间只断开了几秒钟的时间,然后又立马重连上主服务器了。这个时候如果无脑完整同步无疑是没有必要的,psync命令只有当需要的时候才会执行完整同步,否则只需将断线期间的命令同步过去就好了。其现原理依靠:
1:主/从服务器各自的复制偏移量(已发送/复制的偏移量)
2:主服务器的复制积压缓存区(一段时间内写命令的保存,只有一份)
3:服务器的运行id(唯一标识)

通过对比主从服务器的复制偏移量找到在主服务器的复制积压缓存区找到已发送但从服务器未同步的命令。当然缓存区的大小是有限制的(默认1M),当超过了这个限制还是只能完整同步。


SLAVEOF命令源码流程如下

replicaofCommand

源码解析第一步当然是找到slaveof的命令执行处理器replicaofCommand
在这里插入图片描述
可以发现这个处理器保存了主服务器的ipport并将server.repl_state设为REPL_STATE_CONNECT(以便定时器触发下一步操作)就返回ok了。
在这里插入图片描述
数据结构:
在这里插入图片描述

connectWithMaster

而后由定时器建立主从套接字连接并创建文件事件,最后同步状态为 REPL_STATE_CONNECTING
在这里插入图片描述

syncWithMaster

接下来的步骤就在syncWithMaster方法了(由上一步创建的ae事件触发)

void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
    char tmpfile[256], *err = NULL;
    int dfd = -1, maxtries = 5;
    int sockerr = 0, psync_result;
    socklen_t errlen = sizeof(sockerr);
    UNUSED(el);
    UNUSED(privdata);
    UNUSED(mask);

    //REPL_STATE_NONE直接返回
    if (server.repl_state == REPL_STATE_NONE) {
        close(fd);
        return;
    }

    if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &sockerr, &errlen) == -1)
        sockerr = errno;
    if (sockerr) {
        serverLog(LL_WARNING,"Error condition on socket for SYNC: %s",
            strerror(sockerr));
        goto error;
    }

    //发送ping命令,检查网络。然后下一步REPL_STATE_RECEIVE_PONG
    if (server.repl_state == REPL_STATE_CONNECTING) {
        serverLog(LL_NOTICE,"Non blocking connect for SYNC fired the event.");
        aeDeleteFileEvent(server.el,fd,AE_WRITABLE);
        server.repl_state = REPL_STATE_RECEIVE_PONG;
        err = sendSynchronousCommand(SYNC_CMD_WRITE,fd,"PING",NULL);
        if (err) goto write_error;
        return;
    }

    //接收ping回复。然后下一步REPL_STATE_SEND_AUTH
    if (server.repl_state == REPL_STATE_RECEIVE_PONG) {
        err = sendSynchronousCommand(SYNC_CMD_READ,fd,NULL);
        if (err[0] != '+' &&
            strncmp(err,"-NOAUTH",7) != 0 &&
            strncmp(err,"-ERR operation not permitted",28) != 0)
        {
            serverLog(LL_WARNING,"Error reply to PING from master: '%s'",err);
            sdsfree(err);
            goto error;
        } else {
            serverLog(LL_NOTICE,
                "Master replied to PING, replication can continue...");
        }
        sdsfree(err);
        server.repl_state = REPL_STATE_SEND_AUTH;
    }

    //发送AUTH命令,检查身份信息。然后下一步REPL_STATE_RECEIVE_AUTH(不需要身份校验直接跳过进入为REPL_STATE_SEND_PORT)
    if (server.repl_state == REPL_STATE_SEND_AUTH) {
        if (server.masterauth) {
            err = sendSynchronousCommand(SYNC_CMD_WRITE,fd,"AUTH",server.masterauth,NULL);
            if (err) goto write_error;
            server.repl_state = REPL_STATE_RECEIVE_AUTH;
            return;
        } else {
            server.repl_state = REPL_STATE_SEND_PORT;
        }
    }

    //接收AUTH回复。然后下一步REPL_STATE_SEND_PORT
    if (server.repl_state == REPL_STATE_RECEIVE_AUTH) {
        err = sendSynchronousCommand(SYNC_CMD_READ,fd,NULL);
        if (err[0] == '-') {
            serverLog(LL_WARNING,"Unable to AUTH to MASTER: %s",err);
            sdsfree(err);
            goto error;
        }
        sdsfree(err);
        server.repl_state = REPL_STATE_SEND_PORT;
    }

    //发送端口号给主节点。然后下一步REPL_STATE_RECEIVE_PORT
    if (server.repl_state == REPL_STATE_SEND_PORT) {
        sds port = sdsfromlonglong(server.slave_announce_port ?
            server.slave_announce_port : server.port);
        err = sendSynchronousCommand(SYNC_CMD_WRITE,fd,"REPLCONF",
                "listening-port",port, NULL);
        sdsfree(port);
        if (err) goto write_error;
        sdsfree(err);
        server.repl_state = REPL_STATE_RECEIVE_PORT;
        return;
    }

    //接收端口号发送的回复。然后下一步REPL_STATE_SEND_IP
    if (server.repl_state == REPL_STATE_RECEIVE_PORT) {
        err = sendSynchronousCommand(SYNC_CMD_READ,fd,NULL);
        /* Ignore the error if any, not all the Redis versions support
         * REPLCONF listening-port. */
        if (err[0] == '-') {
            serverLog(LL_NOTICE,"(Non critical) Master does not understand "
                                "REPLCONF listening-port: %s", err);
        }
        sdsfree(err);
        server.repl_state = REPL_STATE_SEND_IP;
    }

    //发送ip地址给主节点,然后下一步REPL_STATE_SEND_CAPA
    if (server.repl_state == REPL_STATE_SEND_IP &&
        server.slave_announce_ip == NULL)
    {
            server.repl_state = REPL_STATE_SEND_CAPA;
    }
    if (server.repl_state == REPL_STATE_SEND_IP) {
        err = sendSynchronousCommand(SYNC_CMD_WRITE,fd,"REPLCONF",
                "ip-address",server.slave_announce_ip, NULL);
        if (err) goto write_error;
        sdsfree(err);
        server.repl_state = REPL_STATE_RECEIVE_IP;
        return;
    }

    //接收ip发送的回复。然后下一步REPL_STATE_SEND_CAPA
    if (server.repl_state == REPL_STATE_RECEIVE_IP) {
        err = sendSynchronousCommand(SYNC_CMD_READ,fd,NULL);
        if (err[0] == '-') {
            serverLog(LL_NOTICE,"(Non critical) Master does not understand "
                                "REPLCONF ip-address: %s", err);
        }
        sdsfree(err);
        server.repl_state = REPL_STATE_SEND_CAPA;
    }

    //告知主服务器从节点的一些信息,如:以eof结尾和支持psync。然后下一步REPL_STATE_RECEIVE_CAPA
    if (server.repl_state == REPL_STATE_SEND_CAPA) {
        err = sendSynchronousCommand(SYNC_CMD_WRITE,fd,"REPLCONF",
                "capa","eof","capa","psync2",NULL);
        if (err) goto write_error;
        sdsfree(err);
        server.repl_state = REPL_STATE_RECEIVE_CAPA;
        return;
    }

    //接收信息通知的回复。然后下一步REPL_STATE_SEND_PSYNC
    if (server.repl_state == REPL_STATE_RECEIVE_CAPA) {
        err = sendSynchronousCommand(SYNC_CMD_READ,fd,NULL);
        if (err[0] == '-') {
            serverLog(LL_NOTICE,"(Non critical) Master does not understand "
                                  "REPLCONF capa: %s", err);
        }
        sdsfree(err);
        server.repl_state = REPL_STATE_SEND_PSYNC;
    }

    //发送PSYNC命令,并尽可能部分同步
    if (server.repl_state == REPL_STATE_SEND_PSYNC) {
        if (slaveTryPartialResynchronization(fd,0) == PSYNC_WRITE_ERROR) {
            err = sdsnew("Write error sending the PSYNC command.");
            goto write_error;
        }
        server.repl_state = REPL_STATE_RECEIVE_PSYNC;
        return;
    }

    //接收PSYNC回复
    if (server.repl_state != REPL_STATE_RECEIVE_PSYNC) {
        serverLog(LL_WARNING,"syncWithMaster(): state machine error, "
                             "state should be RECEIVE_PSYNC but is %d",
                             server.repl_state);
        goto error;
    }
    psync_result = slaveTryPartialResynchronization(fd,1);
    if (psync_result == PSYNC_WAIT_REPLY) return; /* Try again later... */
    if (psync_result == PSYNC_TRY_LATER) goto error;
    if (psync_result == PSYNC_CONTINUE) {
        serverLog(LL_NOTICE, "MASTER <-> REPLICA sync: Master accepted a Partial Resynchronization.");
        return;
    }

    //回收套接字资源
    disconnectSlaves(); /* Force our slaves to resync with us as well. */
    freeReplicationBacklog(); /* Don't allow our chained slaves to PSYNC. */

    //PSYNC不支持就改用SYNC
    if (psync_result == PSYNC_NOT_SUPPORTED) {
        serverLog(LL_NOTICE,"Retrying with SYNC...");
        if (syncWrite(fd,"SYNC\r\n",6,server.repl_syncio_timeout*1000) == -1) {
            serverLog(LL_WARNING,"I/O error writing to MASTER: %s",
                strerror(errno));
            goto error;
        }
    }

    //到这一步说明是全量同步。因为比较耗时所以创建时间,再由事件处理器readSyncBulkPayload异步接收主节点传过来的rdb文件
    while(maxtries--) {
        snprintf(tmpfile,256,
            "temp-%d.%ld.rdb",(int)server.unixtime,(long int)getpid());
        dfd = open(tmpfile,O_CREAT|O_WRONLY|O_EXCL,0644);
        if (dfd != -1) break;
        sleep(1);
    }
    if (dfd == -1) {
        serverLog(LL_WARNING,"Opening the temp file needed for MASTER <-> REPLICA synchronization: %s",strerror(errno));
        goto error;
    }
    if (aeCreateFileEvent(server.el,fd, AE_READABLE,readSyncBulkPayload,NULL)
            == AE_ERR)
    {
        serverLog(LL_WARNING,
            "Can't create readable event for SYNC: %s (fd=%d)",
            strerror(errno),fd);
        goto error;
    }

    //最后同步状态为 REPL_STATE_TRANSFER
    server.repl_state = REPL_STATE_TRANSFER;
    server.repl_transfer_size = -1;
    server.repl_transfer_read = 0;
    server.repl_transfer_last_fsync_off = 0;
    server.repl_transfer_fd = dfd;
    server.repl_transfer_lastio = server.unixtime;
    server.repl_transfer_tmpfile = zstrdup(tmpfile);
    return;

error:
    aeDeleteFileEvent(server.el,fd,AE_READABLE|AE_WRITABLE);
    if (dfd != -1) close(dfd);
    close(fd);
    server.repl_transfer_s = -1;
    server.repl_state = REPL_STATE_CONNECT;
    return;

write_error: /* Handle sendSynchronousCommand(SYNC_CMD_WRITE) errors. */
    serverLog(LL_WARNING,"Sending command to master in replication handshake: %s", err);
    sdsfree(err);
    goto error;
}


PSYNC

syncWithMaster里最重要的步骤还得是从服务器psync命令的发送/回复处理函数以及主服务器的命令执行器,下面就从主/从两个角度着重看一下代码。

从服务器

PSYNC命令的发送及对主服务器不同回复的处理逻辑主要在slaveTryPartialResynchronization函数

int slaveTryPartialResynchronization(int fd, int read_reply) {
    char *psync_replid;
    char psync_offset[32];
    sds reply;

    //发送PSYNC
    if (!read_reply) {
        /* Initially set master_initial_offset to -1 to mark the current
         * master run_id and offset as not valid. Later if we'll be able to do
         * a FULL resync using the PSYNC command we'll set the offset at the
         * right value, so that this information will be propagated to the
         * client structure representing the master into server.master. */
        server.master_initial_offset = -1;

        if (server.cached_master) {
            psync_replid = server.cached_master->replid;
            snprintf(psync_offset,sizeof(psync_offset),"%lld", server.cached_master->reploff+1);
            serverLog(LL_NOTICE,"Trying a partial resynchronization (request %s:%s).", psync_replid, psync_offset);
        } else {
            serverLog(LL_NOTICE,"Partial resynchronization not possible (no cached master)");
            psync_replid = "?";
            memcpy(psync_offset,"-1",3);
        }

        /* Issue the PSYNC command */
        reply = sendSynchronousCommand(SYNC_CMD_WRITE,fd,"PSYNC",psync_replid,psync_offset,NULL);
        if (reply != NULL) {
            serverLog(LL_WARNING,"Unable to send PSYNC to master: %s",reply);
            sdsfree(reply);
            aeDeleteFileEvent(server.el,fd,AE_READABLE);
            return PSYNC_WRITE_ERROR;
        }
        return PSYNC_WAIT_REPLY;
    }

    //接收回复
    reply = sendSynchronousCommand(SYNC_CMD_READ,fd,NULL);
    if (sdslen(reply) == 0) {
        sdsfree(reply);
        return PSYNC_WAIT_REPLY;
    }

    aeDeleteFileEvent(server.el,fd,AE_READABLE);

    //FULLRESYNC-全量同步(逻辑返回,后续readSyncBulkPayload异步执行)
    if (!strncmp(reply,"+FULLRESYNC",11)) {
        char *replid = NULL, *offset = NULL;
        replid = strchr(reply,' ');
        if (replid) {
            replid++;
            offset = strchr(replid,' ');
            if (offset) offset++;
        }
        if (!replid || !offset || (offset-replid-1) != CONFIG_RUN_ID_SIZE) {
            serverLog(LL_WARNING,
                "Master replied with wrong +FULLRESYNC syntax.");
            memset(server.master_replid,0,CONFIG_RUN_ID_SIZE+1);
        } else {
            memcpy(server.master_replid, replid, offset-replid-1);
            server.master_replid[CONFIG_RUN_ID_SIZE] = '\0';
            server.master_initial_offset = strtoll(offset,NULL,10);
            serverLog(LL_NOTICE,"Full resync from master: %s:%lld",
                server.master_replid,
                server.master_initial_offset);
        }
        replicationDiscardCachedMaster();
        sdsfree(reply);
        return PSYNC_FULLRESYNC;
    }

    //+CONTINUE部分同步(同步执行)
    if (!strncmp(reply,"+CONTINUE",9)) {
        serverLog(LL_NOTICE,
            "Successful partial resynchronization with master.");
        char *start = reply+10;
        char *end = reply+9;
        while(end[0] != '\r' && end[0] != '\n' && end[0] != '\0') end++;
        if (end-start == CONFIG_RUN_ID_SIZE) {
            char new[CONFIG_RUN_ID_SIZE+1];
            memcpy(new,start,CONFIG_RUN_ID_SIZE);
            new[CONFIG_RUN_ID_SIZE] = '\0';

            if (strcmp(new,server.cached_master->replid)) {
                serverLog(LL_WARNING,"Master replication ID changed to %s",new);
                memcpy(server.replid2,server.cached_master->replid,
                    sizeof(server.replid2));
                server.second_replid_offset = server.master_repl_offset+1;
                memcpy(server.replid,new,sizeof(server.replid));
                memcpy(server.cached_master->replid,new,sizeof(server.replid));
                disconnectSlaves();
            }
        }

        sdsfree(reply);
        //部分同步主逻辑方法
        replicationResurrectCachedMaster(fd);
        if (server.repl_backlog == NULL) createReplicationBacklog();
        return PSYNC_CONTINUE;
    }


    //其他报错情况,如命令不支持
    if (!strncmp(reply,"-NOMASTERLINK",13) ||
        !strncmp(reply,"-LOADING",8))
    {
        serverLog(LL_NOTICE,
            "Master is currently unable to PSYNC "
            "but should be in the future: %s", reply);
        sdsfree(reply);
        return PSYNC_TRY_LATER;
    }

    if (strncmp(reply,"-ERR",4)) {
        serverLog(LL_WARNING,
            "Unexpected reply to PSYNC from master: %s", reply);
    } else {
        serverLog(LL_NOTICE,
            "Master does not support PSYNC or is in "
            "error state (reply: %s)", reply);
    }
    sdsfree(reply);
    replicationDiscardCachedMaster();
    return PSYNC_NOT_SUPPORTED;
}

针对主服务器部分同步的回复直接由replicationResurrectCachedMaster函数同步处理

void replicationResurrectCachedMaster(int newfd) {
    server.master = server.cached_master;
    server.cached_master = NULL;
    server.master->fd = newfd;
    server.master->flags &= ~(CLIENT_CLOSE_AFTER_REPLY|CLIENT_CLOSE_ASAP);
    server.master->authenticated = 1;
    server.master->lastinteraction = server.unixtime;
    server.repl_state = REPL_STATE_CONNECTED;
    server.repl_down_since = 0;

    /* Re-add to the list of clients. */
    linkClient(server.master);
    if (aeCreateFileEvent(server.el, newfd, AE_READABLE,
                          readQueryFromClient, server.master)) {
        serverLog(LL_WARNING,"Error resurrecting the cached master, impossible to add the readable handler: %s", strerror(errno));
        freeClientAsync(server.master); /* Close ASAP. */
    }

    /* We may also need to install the write handler as well if there is
     * pending data in the write buffers. */
    if (clientHasPendingReplies(server.master)) {
        if (aeCreateFileEvent(server.el, newfd, AE_WRITABLE,
                          sendReplyToClient, server.master)) {
            serverLog(LL_WARNING,"Error resurrecting the cached master, impossible to add the writable handler: %s", strerror(errno));
            freeClientAsync(server.master); /* Close ASAP. */
        }
    }
}

而由于全量同步比较耗时,所有这里(slaveTryPartialResynchronization)只做逻辑返回,由后面的readSyncBulkPayload事件处理器异步处理(异步接收主服务器发来的rdb文件)。

void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
    char buf[4096];
    ssize_t nread, readlen, nwritten;
    off_t left;
    UNUSED(el);
    UNUSED(privdata);
    UNUSED(mask);

    /* Static vars used to hold the EOF mark, and the last bytes received
     * form the server: when they match, we reached the end of the transfer. */
    static char eofmark[CONFIG_RUN_ID_SIZE];
    static char lastbytes[CONFIG_RUN_ID_SIZE];
    static int usemark = 0;

    /* If repl_transfer_size == -1 we still have to read the bulk length
     * from the master reply. */
    if (server.repl_transfer_size == -1) {
        if (syncReadLine(fd,buf,1024,server.repl_syncio_timeout*1000) == -1) {
            serverLog(LL_WARNING,
                "I/O error reading bulk count from MASTER: %s",
                strerror(errno));
            goto error;
        }

        if (buf[0] == '-') {
            serverLog(LL_WARNING,
                "MASTER aborted replication with an error: %s",
                buf+1);
            goto error;
        } else if (buf[0] == '\0') {
            /* At this stage just a newline works as a PING in order to take
             * the connection live. So we refresh our last interaction
             * timestamp. */
            server.repl_transfer_lastio = server.unixtime;
            return;
        } else if (buf[0] != '$') {
            serverLog(LL_WARNING,"Bad protocol from MASTER, the first byte is not '$' (we received '%s'), are you sure the host and port are right?", buf);
            goto error;
        }

        /* There are two possible forms for the bulk payload. One is the
         * usual $<count> bulk format. The other is used for diskless transfers
         * when the master does not know beforehand the size of the file to
         * transfer. In the latter case, the following format is used:
         *
         * $EOF:<40 bytes delimiter>
         *
         * At the end of the file the announced delimiter is transmitted. The
         * delimiter is long and random enough that the probability of a
         * collision with the actual file content can be ignored. */
        if (strncmp(buf+1,"EOF:",4) == 0 && strlen(buf+5) >= CONFIG_RUN_ID_SIZE) {
            usemark = 1;
            memcpy(eofmark,buf+5,CONFIG_RUN_ID_SIZE);
            memset(lastbytes,0,CONFIG_RUN_ID_SIZE);
            /* Set any repl_transfer_size to avoid entering this code path
             * at the next call. */
            server.repl_transfer_size = 0;
            serverLog(LL_NOTICE,
                "MASTER <-> REPLICA sync: receiving streamed RDB from master");
        } else {
            usemark = 0;
            server.repl_transfer_size = strtol(buf+1,NULL,10);
            serverLog(LL_NOTICE,
                "MASTER <-> REPLICA sync: receiving %lld bytes from master",
                (long long) server.repl_transfer_size);
        }
        return;
    }

    /* Read bulk data */
    if (usemark) {
        readlen = sizeof(buf);
    } else {
        left = server.repl_transfer_size - server.repl_transfer_read;
        readlen = (left < (signed)sizeof(buf)) ? left : (signed)sizeof(buf);
    }

    nread = read(fd,buf,readlen);
    if (nread <= 0) {
        serverLog(LL_WARNING,"I/O error trying to sync with MASTER: %s",
            (nread == -1) ? strerror(errno) : "connection lost");
        cancelReplicationHandshake();
        return;
    }
    server.stat_net_input_bytes += nread;

    /* When a mark is used, we want to detect EOF asap in order to avoid
     * writing the EOF mark into the file... */
    int eof_reached = 0;

    if (usemark) {
        /* Update the last bytes array, and check if it matches our delimiter.*/
        if (nread >= CONFIG_RUN_ID_SIZE) {
            memcpy(lastbytes,buf+nread-CONFIG_RUN_ID_SIZE,CONFIG_RUN_ID_SIZE);
        } else {
            int rem = CONFIG_RUN_ID_SIZE-nread;
            memmove(lastbytes,lastbytes+nread,rem);
            memcpy(lastbytes+rem,buf,nread);
        }
        if (memcmp(lastbytes,eofmark,CONFIG_RUN_ID_SIZE) == 0) eof_reached = 1;
    }

    server.repl_transfer_lastio = server.unixtime;
    if ((nwritten = write(server.repl_transfer_fd,buf,nread)) != nread) {
        serverLog(LL_WARNING,"Write error or short write writing to the DB dump file needed for MASTER <-> REPLICA synchronization: %s", 
            (nwritten == -1) ? strerror(errno) : "short write");
        goto error;
    }
    server.repl_transfer_read += nread;

    /* Delete the last 40 bytes from the file if we reached EOF. */
    if (usemark && eof_reached) {
        if (ftruncate(server.repl_transfer_fd,
            server.repl_transfer_read - CONFIG_RUN_ID_SIZE) == -1)
        {
            serverLog(LL_WARNING,"Error truncating the RDB file received from the master for SYNC: %s", strerror(errno));
            goto error;
        }
    }

    /* Sync data on disk from time to time, otherwise at the end of the transfer
     * we may suffer a big delay as the memory buffers are copied into the
     * actual disk. */
    if (server.repl_transfer_read >=
        server.repl_transfer_last_fsync_off + REPL_MAX_WRITTEN_BEFORE_FSYNC)
    {
        off_t sync_size = server.repl_transfer_read -
                          server.repl_transfer_last_fsync_off;
        rdb_fsync_range(server.repl_transfer_fd,
            server.repl_transfer_last_fsync_off, sync_size);
        server.repl_transfer_last_fsync_off += sync_size;
    }

    /* Check if the transfer is now complete */
    if (!usemark) {
        if (server.repl_transfer_read == server.repl_transfer_size)
            eof_reached = 1;
    }

    if (eof_reached) {
        int aof_is_enabled = server.aof_state != AOF_OFF;

        /* Ensure background save doesn't overwrite synced data */
        if (server.rdb_child_pid != -1) {
            serverLog(LL_NOTICE,
                "Replica is about to load the RDB file received from the "
                "master, but there is a pending RDB child running. "
                "Killing process %ld and removing its temp file to avoid "
                "any race",
                    (long) server.rdb_child_pid);
            kill(server.rdb_child_pid,SIGUSR1);
            rdbRemoveTempFile(server.rdb_child_pid);
        }

        /* Make sure the new file (also used for persistence) is fully synced
         * (not covered by earlier calls to rdb_fsync_range). */
        if (fsync(server.repl_transfer_fd) == -1) {
            serverLog(LL_WARNING,
                "Failed trying to sync the temp DB to disk in "
                "MASTER <-> REPLICA synchronization: %s",
                strerror(errno));
            cancelReplicationHandshake();
            return;
        }

        if (rename(server.repl_transfer_tmpfile,server.rdb_filename) == -1) {
            serverLog(LL_WARNING,"Failed trying to rename the temp DB into dump.rdb in MASTER <-> REPLICA synchronization: %s", strerror(errno));
            cancelReplicationHandshake();
            return;
        }
        serverLog(LL_NOTICE, "MASTER <-> REPLICA sync: Flushing old data");
        /* We need to stop any AOFRW fork before flusing and parsing
         * RDB, otherwise we'll create a copy-on-write disaster. */
        if(aof_is_enabled) stopAppendOnly();
        signalFlushedDb(-1);
        emptyDb(
            -1,
            server.repl_slave_lazy_flush ? EMPTYDB_ASYNC : EMPTYDB_NO_FLAGS,
            replicationEmptyDbCallback);
        /* Before loading the DB into memory we need to delete the readable
         * handler, otherwise it will get called recursively since
         * rdbLoad() will call the event loop to process events from time to
         * time for non blocking loading. */
        aeDeleteFileEvent(server.el,server.repl_transfer_s,AE_READABLE);
        serverLog(LL_NOTICE, "MASTER <-> REPLICA sync: Loading DB in memory");
        rdbSaveInfo rsi = RDB_SAVE_INFO_INIT;
        if (rdbLoad(server.rdb_filename,&rsi) != C_OK) {
            serverLog(LL_WARNING,"Failed trying to load the MASTER synchronization DB from disk");
            cancelReplicationHandshake();
            /* Re-enable the AOF if we disabled it earlier, in order to restore
             * the original configuration. */
            if (aof_is_enabled) restartAOFAfterSYNC();
            return;
        }
        /* Final setup of the connected slave <- master link */
        zfree(server.repl_transfer_tmpfile);
        close(server.repl_transfer_fd);
        replicationCreateMasterClient(server.repl_transfer_s,rsi.repl_stream_db);
        server.repl_state = REPL_STATE_CONNECTED;
        server.repl_down_since = 0;
        /* After a full resynchroniziation we use the replication ID and
         * offset of the master. The secondary ID / offset are cleared since
         * we are starting a new history. */
        memcpy(server.replid,server.master->replid,sizeof(server.replid));
        server.master_repl_offset = server.master->reploff;
        clearReplicationId2();
        /* Let's create the replication backlog if needed. Slaves need to
         * accumulate the backlog regardless of the fact they have sub-slaves
         * or not, in order to behave correctly if they are promoted to
         * masters after a failover. */
        if (server.repl_backlog == NULL) createReplicationBacklog();

        serverLog(LL_NOTICE, "MASTER <-> REPLICA sync: Finished with success");
        /* Restart the AOF subsystem now that we finished the sync. This
         * will trigger an AOF rewrite, and when done will start appending
         * to the new file. */
        if (aof_is_enabled) restartAOFAfterSYNC();
    }
    return;

error:
    cancelReplicationHandshake();
    return;
}

主服务器

主服务器则是对psync命令的执行和回复

/* SYNC and PSYNC command implemenation. */
void syncCommand(client *c) {
    /* ignore SYNC if already slave or in monitor mode */
    if (c->flags & CLIENT_SLAVE) return;

    /* Refuse SYNC requests if we are a slave but the link with our master
     * is not ok... */
    if (server.masterhost && server.repl_state != REPL_STATE_CONNECTED) {
        addReplySds(c,sdsnew("-NOMASTERLINK Can't SYNC while not connected with my master\r\n"));
        return;
    }

    //当有数据要返回给客户端时,无法执行sync/psync
    if (clientHasPendingReplies(c)) {
        addReplyError(c,"SYNC and PSYNC are invalid with pending output");
        return;
    }

    serverLog(LL_NOTICE,"Replica %s asks for synchronization",
        replicationGetSlaveName(c));

    //如果是psync,先尝试部分同步
    if (!strcasecmp(c->argv[0]->ptr,"psync")) {
        if (masterTryPartialResynchronization(c) == C_OK) {
            server.stat_sync_partial_ok++;
            return; /* No full resync needed, return. */
        } else {
            char *master_replid = c->argv[1]->ptr;

            /* Increment stats for failed PSYNCs, but only if the
             * replid is not "?", as this is used by slaves to force a full
             * resync on purpose when they are not albe to partially
             * resync. */
            if (master_replid[0] != '?') server.stat_sync_partial_err++;
        }
    } else {
        /* If a slave uses SYNC, we are dealing with an old implementation
         * of the replication protocol (like redis-cli --slave). Flag the client
         * so that we don't expect to receive REPLCONF ACK feedbacks. */
        c->flags |= CLIENT_PRE_PSYNC;
    }

    //无法部分同步,只能全量同步
    server.stat_sync_full++;

    /* Setup the slave as one waiting for BGSAVE to start. The following code
     * paths will change the state if we handle the slave differently. */
    c->replstate = SLAVE_STATE_WAIT_BGSAVE_START;
    if (server.repl_disable_tcp_nodelay)
        anetDisableTcpNoDelay(NULL, c->fd); /* Non critical if it fails. */
    c->repldbfd = -1;
    c->flags |= CLIENT_SLAVE;
    listAddNodeTail(server.slaves,c);

    //这里创建一个复制积压缓冲区,用于部分同步
    if (listLength(server.slaves) == 1 && server.repl_backlog == NULL) {
        /* When we create the backlog from scratch, we always use a new
         * replication ID and clear the ID2, since there is no valid
         * past history. */
        changeReplicationId();
        clearReplicationId2();
        createReplicationBacklog();
    }

    // case1:当前有bgsave,其实RDB_CHILD_TYPE_DISK类型,尝试共用落盘RDB
    if (server.rdb_child_pid != -1 &&
        server.rdb_child_type == RDB_CHILD_TYPE_DISK)
    {
        /* Ok a background save is in progress. Let's check if it is a good
         * one for replication, i.e. if there is another slave that is
         * registering differences since the server forked to save. */
        client *slave;
        listNode *ln;
        listIter li;

        listRewind(server.slaves,&li);
        while((ln = listNext(&li))) {
            slave = ln->value;
            if (slave->replstate == SLAVE_STATE_WAIT_BGSAVE_END) break;
        }
        /* To attach this slave, we check that it has at least all the
         * capabilities of the slave that triggered the current BGSAVE. */
        if (ln && ((c->slave_capa & slave->slave_capa) == slave->slave_capa)) {
            /* Perfect, the server is already registering differences for
             * another slave. Set the right state, and copy the buffer. */
            copyClientOutputBuffer(c,slave);
            replicationSetupSlaveForFullResync(c,slave->psync_initial_offset);
            serverLog(LL_NOTICE,"Waiting for end of BGSAVE for SYNC");
        } else {
            /* No way, we need to wait for the next BGSAVE in order to
             * register differences. */
            serverLog(LL_NOTICE,"Can't attach the replica to the current BGSAVE. Waiting for next BGSAVE for SYNC");
        }

    // case2:当前有bgsave,但是是RDB_CHILD_TYPE_SOCKET类型,等待下次bgsave
    } else if (server.rdb_child_pid != -1 &&
               server.rdb_child_type == RDB_CHILD_TYPE_SOCKET)
    {
        /* There is an RDB child process but it is writing directly to
         * children sockets. We need to wait for the next BGSAVE
         * in order to synchronize. */
        serverLog(LL_NOTICE,"Current BGSAVE has socket target. Waiting for next BGSAVE for SYNC");

    // case3:当前无bgsave
    } else {
        //无磁盘复制
        if (server.repl_diskless_sync && (c->slave_capa & SLAVE_CAPA_EOF)) {
            /* Diskless replication RDB child is created inside
             * replicationCron() since we want to delay its start a
             * few seconds to wait for more slaves to arrive. */
            if (server.repl_diskless_sync_delay)
                serverLog(LL_NOTICE,"Delay next BGSAVE for diskless SYNC");
        }
        //有磁盘复制 
        else {
            /* Target is disk (or the slave is not capable of supporting
             * diskless replication) and we don't have a BGSAVE in progress,
             * let's start one. */
            if (server.aof_child_pid == -1) {
                startBgsaveForReplication(c->slave_capa);
            } else {
                serverLog(LL_NOTICE,
                    "No BGSAVE in progress, but an AOF rewrite is active. "
                    "BGSAVE for replication delayed");
            }
        }
    }
    return;
}

部分同步回复:masterTryPartialResynchronization
只要数据还在复制积压缓冲区就不用走全量同步,从机等着接收数据更新Offset即可。

int masterTryPartialResynchronization(client *c) {
    long long psync_offset, psync_len;
    char *master_replid = c->argv[1]->ptr;
    char buf[128];
    int buflen;

    /* Parse the replication offset asked by the slave. Go to full sync
     * on parse error: this should never happen but we try to handle
     * it in a robust way compared to aborting. */
    if (getLongLongFromObjectOrReply(c,c->argv[2],&psync_offset,NULL) !=
       C_OK) goto need_full_resync;

    /* Is the replication ID of this master the same advertised by the wannabe
     * slave via PSYNC? If the replication ID changed this master has a
     * different replication history, and there is no way to continue.
     *
     * Note that there are two potentially valid replication IDs: the ID1
     * and the ID2. The ID2 however is only valid up to a specific offset. */
    if (strcasecmp(master_replid, server.replid) &&
        (strcasecmp(master_replid, server.replid2) ||
         psync_offset > server.second_replid_offset))
    {
        /* Run id "?" is used by slaves that want to force a full resync. */
        if (master_replid[0] != '?') {
            if (strcasecmp(master_replid, server.replid) &&
                strcasecmp(master_replid, server.replid2))
            {
                serverLog(LL_NOTICE,"Partial resynchronization not accepted: "
                    "Replication ID mismatch (Replica asked for '%s', my "
                    "replication IDs are '%s' and '%s')",
                    master_replid, server.replid, server.replid2);
            } else {
                serverLog(LL_NOTICE,"Partial resynchronization not accepted: "
                    "Requested offset for second ID was %lld, but I can reply "
                    "up to %lld", psync_offset, server.second_replid_offset);
            }
        } else {
            serverLog(LL_NOTICE,"Full resync requested by replica %s",
                replicationGetSlaveName(c));
        }
        goto need_full_resync;
    }

    //数据不在复制积压缓存区则全量同步
    if (!server.repl_backlog ||
        psync_offset < server.repl_backlog_off ||
        psync_offset > (server.repl_backlog_off + server.repl_backlog_histlen))
    {
        serverLog(LL_NOTICE,
            "Unable to partial resync with replica %s for lack of backlog (Replica request was: %lld).", replicationGetSlaveName(c), psync_offset);
        if (psync_offset > server.master_repl_offset) {
            serverLog(LL_WARNING,
                "Warning: replica %s tried to PSYNC with an offset that is greater than the master replication offset.", replicationGetSlaveName(c));
        }
        goto need_full_resync;
    }

	//到这里表示可以部分同步
    /* If we reached this point, we are able to perform a partial resync:
     * 1) Set client state to make it a slave.
     * 2) Inform the client we can continue with +CONTINUE
     * 3) Send the backlog data (from the offset to the end) to the slave. */
    c->flags |= CLIENT_SLAVE;
    c->replstate = SLAVE_STATE_ONLINE;
    c->repl_ack_time = server.unixtime;
    c->repl_put_online_on_ack = 0;
    listAddNodeTail(server.slaves,c);
    /* We can't use the connection buffers since they are used to accumulate
     * new commands at this stage. But we are sure the socket send buffer is
     * empty so this write will never fail actually. */
    //回复 +CONTINUE
    if (c->slave_capa & SLAVE_CAPA_PSYNC2) {
        buflen = snprintf(buf,sizeof(buf),"+CONTINUE %s\r\n", server.replid);
    } else {
        buflen = snprintf(buf,sizeof(buf),"+CONTINUE\r\n");
    }
    if (write(c->fd,buf,buflen) != buflen) {
        freeClientAsync(c);
        return C_OK;
    }
    //发送复制积压缓存区数据
    psync_len = addReplyReplicationBacklog(c,psync_offset);
    serverLog(LL_NOTICE,
        "Partial resynchronization request from %s accepted. Sending %lld bytes of backlog starting from offset %lld.",
            replicationGetSlaveName(c),
            psync_len, psync_offset);
    /* Note that we don't need to set the selected DB at server.slaveseldb
     * to -1 to force the master to emit SELECT, since the slave already
     * has this state from the previous connection with the master. */

    refreshGoodSlavesCount();
    return C_OK; /* The caller can return, no full resync needed. */

need_full_resync:
    /* We need a full resync for some reason... Note that we can't
     * reply to PSYNC right now if a full SYNC is needed. The reply
     * must include the master offset at the time the RDB file we transfer
     * is generated, so we need to delay the reply to that moment. */
    return C_ERR;
}

注:每次命令的执行,都会将命令写入到复制积压缓存区server.repl_backlog传播给子节点
在这里插入图片描述
(图中命令传播给子节点后面会用到,这里先圈出来)

全量同步回复:startBgsaveForReplication

int startBgsaveForReplication(int mincapa) {
    int retval;
    int socket_target = server.repl_diskless_sync && (mincapa & SLAVE_CAPA_EOF);
    listIter li;
    listNode *ln;

    serverLog(LL_NOTICE,"Starting BGSAVE for SYNC with target: %s",
        socket_target ? "replicas sockets" : "disk");

    //准备rdb文件
    rdbSaveInfo rsi, *rsiptr;
    rsiptr = rdbPopulateSaveInfo(&rsi);
    /* Only do rdbSave* when rsiptr is not NULL,
     * otherwise slave will miss repl-stream-db. */
    if (rsiptr) {
        if (socket_target)
            retval = rdbSaveToSlavesSockets(rsiptr);
        else
            retval = rdbSaveBackground(server.rdb_filename,rsiptr);
    } else {
        serverLog(LL_WARNING,"BGSAVE for replication: replication information not available, can't generate the RDB file right now. Try later.");
        retval = C_ERR;
    }

    //出错情况
    if (retval == C_ERR) {
        serverLog(LL_WARNING,"BGSAVE for replication failed");
        listRewind(server.slaves,&li);
        while((ln = listNext(&li))) {
            client *slave = ln->value;

            if (slave->replstate == SLAVE_STATE_WAIT_BGSAVE_START) {
                slave->replstate = REPL_STATE_NONE;
                slave->flags &= ~CLIENT_SLAVE;
                listDelNode(server.slaves,ln);
                addReplyError(slave,
                    "BGSAVE failed, replication can't continue");
                slave->flags |= CLIENT_CLOSE_AFTER_REPLY;
            }
        }
        return retval;
    }

    //如果是有磁盘复制
    if (!socket_target) {
        listRewind(server.slaves,&li);
        while((ln = listNext(&li))) {
            client *slave = ln->value;
            //找正在等待同步开始的从机回复+FULLRESYNC
            if (slave->replstate == SLAVE_STATE_WAIT_BGSAVE_START) {
                    replicationSetupSlaveForFullResync(slave,
                            getPsyncInitialOffset());
            }
        }
    }

    if (retval == C_OK) replicationScriptCacheFlush();
    return retval;
}

到这里,其实全量同步只回复了一个 +FULLRESYNC,那么rdb文件是怎么发送的呢?答案在serverCron定时任务(1242-1275行)。
全量同步rdb文件发送:sendBulkToSlave
在这里插入图片描述
一路跟踪
serverCron->backgroundSaveDoneHandler->backgroundSaveDoneHandlerDisk->updateSlavesWaitingBgsave->sendBulkToSlave
sendBulkToSlave

void sendBulkToSlave(aeEventLoop *el, int fd, void *privdata, int mask) {
    client *slave = privdata;
    UNUSED(el);
    UNUSED(mask);
    char buf[PROTO_IOBUF_LEN]; //每次发送16K
    ssize_t nwritten, buflen;

    //在发送rdb文件之前先发送文件长度
    if (slave->replpreamble) {
        nwritten = write(fd,slave->replpreamble,sdslen(slave->replpreamble));
        if (nwritten == -1) {
            serverLog(LL_VERBOSE,"Write error sending RDB preamble to replica: %s",
                strerror(errno));
            freeClient(slave);
            return;
        }
        server.stat_net_output_bytes += nwritten;
        sdsrange(slave->replpreamble,nwritten,-1);
        if (sdslen(slave->replpreamble) == 0) {
            sdsfree(slave->replpreamble);
            slave->replpreamble = NULL;
            /* fall through sending data. */
        } else {
            return;
        }
    }

    //每次发送16K数据
    lseek(slave->repldbfd,slave->repldboff,SEEK_SET);
    buflen = read(slave->repldbfd,buf,PROTO_IOBUF_LEN);
    if (buflen <= 0) {
        serverLog(LL_WARNING,"Read error sending DB to replica: %s",
            (buflen == 0) ? "premature EOF" : strerror(errno));
        freeClient(slave);
        return;
    }
    if ((nwritten = write(fd,buf,buflen)) == -1) {
        if (errno != EAGAIN) {
            serverLog(LL_WARNING,"Write error sending DB to replica: %s",
                strerror(errno));
            freeClient(slave);
        }
        return;
    }
    slave->repldboff += nwritten;
    server.stat_net_output_bytes += nwritten;
    if (slave->repldboff == slave->repldbsize) {
        close(slave->repldbfd);
        slave->repldbfd = -1;
        aeDeleteFileEvent(server.el,slave->fd,AE_WRITABLE);
        putSlaveOnline(slave);
    }
}

至此全量同步的rdb文件就发送了。


那么问题来了,主服务器从生成rdb文件到从服务器接收并加载rdb文件肯定也是一个比较耗时的操作,在这段时间主服务器新执行的命令是如何同步给从服务器的呢?
其实Redis和客户端通信也好,和从库通信也好,都会给分配一个内存buffer进行数据交互。客户端是一个client,从库也是一个client,所有数据交互都通过这个buffer进行。先把数据写到这个buffer中,然后再把buffer中的数据发到client socket中通过网络发送出去。而在主从复制中这个buffer叫做replication buffer(复制缓存区)
与命令写入复制积压缓存区一样(分析复制积压缓存区时一起圈出来的地方),在call方法执行的时候,(当需要时)也会传播给子节点,即写入复制缓存区。
在这里插入图片描述
一直跟踪addReplyBulk
在这里插入图片描述
可以发现由于此时replicas还未处于SLAVE_STATE_ONLINE状态以及ack不等于0,未加入待处理列表clients_pending_write,这些命令只是先积攒在replicas的缓存区中。
那么何时发送呢?自然是在rdb文件发送完后再发送。回到sendBulkToSlave函数,可以看到在函数的最后会判断rdb文件是否发送完,如果发送完了就会执行putSlaveOnline将从节点逻辑上线,并创建对应套接字的文件事件发送积攒的命令。
在这里插入图片描述
分析到这里其实就不难猜出,无论是全量复制还是部分复制,在复制完成后都会将从节点ONLINE,之后的命令才得以发出去。全量复制刚已经分析过了,现在来验证部分复制完成后是否会将从节点状态置为ONLINE以及将ONLINE_ON_ACK置为0呢?
在这里插入图片描述
果然在masterTryPartialResynchronization函数的最后可以发现
c->replstate = SLAVE_STATE_ONLINE;
c->repl_put_online_on_ack = 0;


至此,slaveof命令流程就已经看完了。最后来一波总结

slaveof流程:
1、从服务器保存主服务器ip和port
3、ping 命令检查网络是否联通
4、AUTH 命令身份检验
5、从服务器发送自身ip和port给主服务器
6、PSYNC 开始同步
7、命令传播

增量/全量的决定逻辑:
在这里插入图片描述
增量同步:
在这里插入图片描述
全量同步:
在这里插入图片描述


主从复制除上面的复制流程外,还有一个心跳检测主要用于检查主从节点之间的连接状况。

心跳检测

从服务器默认每秒发从一次replconf ack命令进行心跳检测。其目的在于
1:检测主从服务连接状态
在这里插入图片描述

2:检测命令的丢失
在这里插入图片描述

3:辅助实现min-slaves
在这里插入图片描述

参考文献:《Redis设计与实现》黄健宏著、redis-5.0.14源码

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/26093.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[论文评析]基于人体姿态识别的立定跳远 动作智能评估系统

基于人体姿态识别的立定跳远 动作智能评估系统论文信息背景方法系统总体设计立定跳远动作智能评估系统标准动作库子系统动作采集子系统人体姿态动作评估子系统人体姿态评估模型立定跳远关键帧匹配姿态评估及对比总结论文信息 题目&#xff1a;基于人体姿态识别的立定跳远 动作…

AMBA:AXI/AHB/APB学习笔记

AMBA、AXI、AHB、APB学习笔记AMBA总线&#xff1a;各模块之间的连接AHB特性&#xff1a;组成部分&#xff1a;AXIAPBAMBA总线&#xff1a;各模块之间的连接 advanced microcontroller bus architecture高级微控制器总线架构 AHB Advanced High-performance Bus 高级高性能总…

微信小程序|搭建一个博客小程序

文章目录一、文章前言二、创建小程序三、功能开发&#xff08;1&#xff09;首页&#xff08;2&#xff09;领域页&#xff08;3&#xff09;博客详情页&#xff08;4&#xff09;个人中心页一、文章前言 此文主要通过小程序搭建一个博客系统&#xff0c;实现博客的一些基础功能…

AD环境下域用户的离线缓存登录

缓存登录主要是为了解决当公司域控制器发生故障联系不上DC或用户拿笔记本电脑回家不拔VPN的情况下&#xff0c;依然能够登录到系统&#xff0c;进行办公。如果用户登录的时候联系不到DC&#xff0c;那么就凭用户登录时输入的用户名和密码去缓存中校验&#xff0c;如果能联系上D…

如果在学习spring的时候没看过这份学习笔记+源码剖析,真的亏大了!

Spring 是一个开源的设计层面框架&#xff0c;它解决的是业务逻辑层和其他各层的松耦合问题&#xff0c;因此它将面向接口的编程思想贯穿整个系统应用。包括在此基础上衍生的 Spring MVC、 Spring Boot 、Spring Cloud 等&#xff0c;在现在企业中的应用越来越广泛。因此对于 S…

YOLOv7移植经验分享

目录 一、背景 二、环境 2.1 服务器环境 2.2 SDK环境 2.3 docker环境 三、移植开发 3.1 模型迁移 3.2 算法迁移 四、部署 一、背景 YOLOv7在 5 FPS 到 160 FPS 范围内的速度和准确度都超过了所有已知的目标检测器&#xff0c;并且在 GPU V100 上 30 FPS 或更高的所有…

python基于OCR深度学习实现商品配料表识别

1、概述 当前人民和国家对食品安全十分重视&#xff0c;但商家为了保证食品长时间储存&#xff0c;味道鲜美&#xff0c;在食品中添加超量或对人有严重危害得食品添加剂&#xff0c;严重危害到人民的安全&#xff0c;我们以方便面为例&#xff0c;一包方便面最多可有25种食品添…

十年开发老手,深度解析企业用人标准为何越来越高?!

涛哥作为一个10多年的开发老手&#xff0c;经历过很多场面试&#xff0c;也面试过很多人&#xff0c;这么多年下来&#xff0c;切身体会到企业的用人标准越来越高&#xff0c;企业对开发工程师的要求也越来越"过分"。所以涛哥今天就借此机会&#xff0c;我们一起来分…

如何制定有效的项目计划,提高团队执行力

项目风险来源有很多&#xff0c;项目日程紧张&#xff0c;导致质量下降风险上升&#xff1b;甲方变更&#xff0c;管理者对变动控制不足&#xff1b;项目太大。 虽然从来不可能完全消除项目风险&#xff0c;但可以将危害减到最小。 一、确认项目计划 项目计划是一个项目启动…

SpringBoot读取properties中配置的List集合

实体类 Data NoArgsConstructor AllArgsConstructor Accessors(chain true) public class Person {private String name;private String age;private String content; } Component//将该类交由Spring管理 ConfigurationProperties(prefix "project") //自定义.pro…

【附源码】计算机毕业设计JAVA演唱会购票系统

【附源码】计算机毕业设计JAVA演唱会购票系统 目运行 环境项配置&#xff1a; Jdk1.8 Tomcat8.5 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; JAVA myba…

听我一句劝好吗?放下那些老掉牙的性能优化笔记吧!又不是没有新的,跟不上时代的学了也没法直接用呀!

性能概述 公司投入人力物力成本开发出的程序&#xff0c;如果出现程序瘫痪、界面停顿、抖动、响应迟缓等问题&#xff0c;会大大降低用户体验&#xff0c;损失大量用户。对于上述问题&#xff0c;都是需要性能调优来解决的问题。 程序性能主要表现在代码的执行速度、软件系统…

数据结构之快速排序(重点)

快速排序 算法所需 一个基准点 左边是比其小的数&#xff0c;右边是比其大的数 先使所指的元素作为基准元素low 用一个piviot存储49 然后进行比遍历操作 就是high向左移动(high–)&#xff0c;到第一个比piviot小的元素进行一个data[low]data[high] 然后进行low&#xff0c;找…

基于最低水平面的三维装箱问题的启发式算法

⭐️ 前言 小编之前写过一篇博文&#xff1a;求解三维装箱问题的启发式深度优先搜索算法(python)&#xff0c;详述了基于空间选择的三维装箱算法。本文考虑了一个事实&#xff1a;在某些情况下&#xff0c;我们在摆放物品时&#xff0c;总是优先选择较低的平面&#xff0c;基于…

LIN通讯

LIN通讯 一、LIN通讯的背景与意义 随着汽车电子的发展&#xff0c;汽车上的电子零件正在逐渐地增加。而电子零件的增加也导致更多的设备&#xff08;传感器、执行器、电子控制器&#xff09;需要加入汽车的局部网络&#xff0c;这些零件的增加还会带来配线的增加&#xff0c;…

java-springboot基于机器学习得心脏病预测系统 的设计与实现-计算机毕业设计

项目介绍 基于机器学习得心脏病预测系统通过对机器学习心脏病数据大数据分析统计系统的建设以实现机器学习心脏病数据分析统计功能。通过对心脏疾病变化市场的充分研究&#xff0c;结合自身技术储备情况&#xff0c;设计并开发了一套基于SpringBoot后台框架、Mybaits数据库映射…

web课程设计网页规划与设计---公司网站(5页 带下拉菜单)

⛵ 源码获取 文末联系 ✈ Web前端开发技术 描述 网页设计题材&#xff0c;DIVCSS 布局制作,HTMLCSS网页设计期末课程大作业 | 公司官网网站 | 企业官网 | 酒店官网 | 等网站的设计与制 | HTML期末大学生网页设计作业&#xff0c;Web大学生网页 HTML&#xff1a;结构 CSS&#…

动态规划算法学习二:最长公共子序列

文章目录前言一、问题描述二、DP实现1、最优子结构性质*****2、状态表示*****3、状态递归方程*****4、计算最优值*****5、代码实现&#xff1a;输出最长公共子序列6、代码实现&#xff1a;输出最优解前言 一、问题描述 列举X的所有子序列&#xff0c;然后检查它是否也是Y的子序…

Java设计模式很难吗,这篇带你熟悉设计模式

3.1 概述 可以发现&#xff0c;设计模式好像都是类似的。越看越感觉都着不多。其实都是类似面向接口编程的一种体现&#xff0c;只不过侧重点不一样或者说要体现的结果不一样。 3.2 使用场景 问题一&#xff1a;应对可能变化的对象实现 方案&#xff1a;间接创建 模式&…

pycharm远程连接服务器

遇到的问题&#xff1a; 在服务器上配环境 流程&#xff1a; 先安装anaconda&#xff08;去其官网下载个脚本文件到服务器上&#xff0c;然后启动脚本即可&#xff09; bash Anaconda3-5.3.1-Linux-x86_64.sh然后创建 python环境 conda create -n pytorch python3.10去pyt…