深入剖析 redis 主从复制.docx

上传人:b****9 文档编号:25164546 上传时间:2023-06-05 格式:DOCX 页数:38 大小:72.82KB
下载 相关 举报
深入剖析 redis 主从复制.docx_第1页
第1页 / 共38页
深入剖析 redis 主从复制.docx_第2页
第2页 / 共38页
深入剖析 redis 主从复制.docx_第3页
第3页 / 共38页
深入剖析 redis 主从复制.docx_第4页
第4页 / 共38页
深入剖析 redis 主从复制.docx_第5页
第5页 / 共38页
点击查看更多>>
下载资源
资源描述

深入剖析 redis 主从复制.docx

《深入剖析 redis 主从复制.docx》由会员分享,可在线阅读,更多相关《深入剖析 redis 主从复制.docx(38页珍藏版)》请在冰豆网上搜索。

深入剖析 redis 主从复制.docx

深入剖析redis主从复制

主从概述

redis支持master-slave(主从)模式,redisserver可以设置为另一个redisserver的主机(从机),从机定期从主机拿数据。

特殊的,一个从机同样可以设置为一个redisserver的主机,这样一来master-slave的分布看起来就是一个有向无环图DAG,如此形成redisserver集群,无论是主机还是从机都是redisserver,都可以提供服务)。

在配置后,主机可负责读写服务,从机只负责读。

redis提高这种配置方式,为的是让其支持数据的弱一致性,即最终一致性。

在业务中,选择强一致性还是若已执行,应该取决于具体的业务需求,像微博,完全可以使用弱一致性模型;像淘宝,可以选用强一致性模型。

redis主从复制的实现主要在replication.c中。

这篇文章涉及较多的代码,但我已经尽量删繁就简,达到能说明问题本质。

为了保留代码的原生性并让读者能够阅读原生代码的注释,剖析redis的几篇文章都没有删除代码中的英文注释,并已加注释。

积压空间

在《深入剖析redisAOF持久化策略》中,介绍了更新缓存的概念,举一个例子:

客户端发来命令:

setnameJhon,这一数据更新被记录为:

*3rn$3rnSETrn$4rnnamern$3rnJhonrn,并存储在更新缓存中。

同样,在主从连接中,也有更新缓存的概念。

只是两者的用途不一样,前者被写入本地,后者被写入从机,这里我们把它成为积压空间。

更新缓存存储在server.repl_backlog,redis将其作为一个环形空间来处理,这样做节省了空间,避免内存再分配的情况。

struct redisServer {

    /* Replication (master) */

    // 最近一次使用(访问)的数据集

    int slaveseldb;                 /* Last SELECTed DB in replication output */

    // 全局的数据同步偏移量

    long long master_repl_offset;   /* Global replication offset */

    // 主从连接心跳频率

    int repl_ping_slave_period;     /* Master pings the slave every N seconds */

    // 积压空间指针

    char *repl_backlog;             /* Replication backlog for partial syncs */

    // 积压空间大小

    long long repl_backlog_size;    /* Backlog circular buffer size */

    // 积压空间中写入的新数据的大小

    long long repl_backlog_histlen; /* Backlog actual data length */

    // 下一次向积压空间写入数据的起始位置

    long long repl_backlog_idx;     /* Backlog circular buffer current offset */

    // 积压数据的起始位置,是一个宏观值

    long long repl_backlog_off;     /* Replication offset of first byte in the

                                       backlog buffer. */

    // 积压空间有效时间

    time_t repl_backlog_time_limit; /* Time without slaves after the backlog

                                       gets released. */

}

积压空间中的数据变更记录是什么时候被写入的?

在执行一个redis命令的时候,如果存在数据的修改(写),那么就会把变更记录传播。

redis源码中是这么实现的:

call()->propagate()->replicationFeedSlaves()

注释:

命令真正执行的地方在call()中,call()如果发现数据被修改(dirty),则传播propagrate(),replicationFeedSlaves()将修改记录写入积压空间和所有已连接的从机。

这里可能会有疑问:

为什么把数据添加入积压空间,又把数据分发给所有的从机?

为什么不仅仅将数据分发给所有从机呢?

因为有一些从机会因特殊情况(?

)与主机断开连接,注意从机断开前有暂存主机的状态信息,因此这些断开的从机就没有及时收到更新的数据。

redis为了让断开的从机在下次连接后能够获取更新数据,将更新数据加入了积压空间。

从replicationFeedSlaves()实现来看,在线的slave能马上收到数据更新记录;因某些原因暂时断开连接的slave,需要从积压空间中找回断开期间的数据更新记录。

如果断开的时间足够长,master会拒绝slave的部分同步请求,从而slave只能进行全同步。

下面是源码注释:

// call() 函数是执行命令的核心函数,真正执行命令的地方

/* Call() is the core of Redis execution of a command */

void call(redisClient *c, int flags) {

    ......

    /* Call the command. */

    c->flags &= ~(REDIS_FORCE_AOF|REDIS_FORCE_REPL);

    redisOpArrayInit(&server.also_propagate);

    // 脏数据标记,数据是否被修改

    dirty = server.dirty;

    // 执行命令对应的函数

    c->cmd->proc(c);

    dirty = server.dirty-dirty;

    duration = ustime()-start;

    ......

    // 将客户端请求的数据修改记录传播给 AOF 和从机

    /* Propagate the command into the AOF and replication link */

    if (flags & REDIS_CALL_PROPAGATE) {

        int flags = REDIS_PROPAGATE_NONE;

        // 强制主从复制

        if (c->flags & REDIS_FORCE_REPL) flags |= REDIS_PROPAGATE_REPL;

        // 强制 AOF 持久化

        if (c->flags & REDIS_FORCE_AOF) flags |= REDIS_PROPAGATE_AOF;

        // 数据被修改

        if (dirty)

            flags |= (REDIS_PROPAGATE_REPL | REDIS_PROPAGATE_AOF);

        // 传播数据修改记录

        if (flags !

= REDIS_PROPAGATE_NONE)

            propagate(c->cmd,c->db->id,c->argv,c->argc,flags);

    }

    ......

}

// 向 AOF 和从机发布数据更新

/* Propagate the specified command (in the context of the specified database id)

 * to AOF and Slaves.

 *

 * flags are an xor between:

 * + REDIS_PROPAGATE_NONE (no propagation of command at all)

 * + REDIS_PROPAGATE_AOF (propagate into the AOF file if is enabled)

 * + REDIS_PROPAGATE_REPL (propagate into the replication link)

 */

void propagate(struct redisCommand *cmd, int dbid, robj **argv, int argc,

               int flags)

{

    // AOF 策略需要打开,且设置 AOF 传播标记,将更新发布给本地文件

    if (server.aof_state !

= REDIS_AOF_OFF && flags & REDIS_PROPAGATE_AOF)

        feedAppendOnlyFile(cmd,dbid,argv,argc);

    // 设置了从机传播标记,将更新发布给从机

    if (flags & REDIS_PROPAGATE_REPL)

        replicationFeedSlaves(server.slaves,dbid,argv,argc);

}

// 向积压空间和从机发送数据

void replicationFeedSlaves(list *slaves, int dictid, robj **argv, int argc) {

    listNode *ln;

    listIter li;

    int j, len;

    char llstr[REDIS_LONGSTR_SIZE];

    // 没有积压数据且没有从机,直接退出

    /* If there aren't slaves, and there is no backlog buffer to populate,

     * we can return ASAP. */

    if (server.repl_backlog == NULL && listLength(slaves) == 0) return;

    /* We can't have slaves attached and no backlog. */

    redisAssert(!

(listLength(slaves) !

= 0 && server.repl_backlog == NULL));

    /* Send SELECT command to every slave if needed. */

    if (server.slaveseldb !

= dictid) {

        robj *selectcmd;

        // 小于等于 10 的可以用共享对象

        /* For a few DBs we have pre-computed SELECT command. */

        if (dictid >= 0 && dictid < REDIS_SHARED_SELECT_CMDS) {

            selectcmd = shared.select[dictid];

        } else {

        // 不能使用共享对象,生成 SELECT 命令对应的 redis 对象

            int dictid_len;

            dictid_len = ll2string(llstr,sizeof(llstr),dictid);

            selectcmd = createObject(REDIS_STRING,

                sdscatprintf(sdsempty(),

                "*2rn$6rnSELECTrn$%drn%srn",

                dictid_len, llstr));

        }

        // 这里可能会有疑问:

为什么把数据添加入积压空间,又把数据分发给所有的从机?

        // 为什么不仅仅将数据分发给所有从机呢?

        // 因为有一些从机会因特殊情况(?

)与主机断开连接,注意从机断开前有暂存

        // 主机的状态信息,因此这些断开的从机就没有及时收到更新的数据。

redis 为了让

        // 断开的从机在下次连接后能够获取更新数据,将更新数据加入了积压空间。

        // 将 SELECT 命令对应的 redis 对象数据添加到积压空间

        /* Add the SELECT command into the backlog. */

        if (server.repl_backlog) feedReplicationBacklogWithObject(selectcmd);

        // 将数据分发所有的从机

        /* Send it to slaves. */

        listRewind(slaves,&li);

        while((ln = listNext(&li))) {

            redisClient *slave = ln->value;

            addReply(slave,selectcmd);

        }

        // 销毁对象

        if (dictid < 0 || dictid >= REDIS_SHARED_SELECT_CMDS)

            decrRefCount(selectcmd);

    }

    // 更新最近一次使用(访问)的数据集

    server.slaveseldb = dictid;

    // 将命令写入积压空间

    /* Write the command to the replication backlog if any. */

    if (server.repl_backlog) {

        char aux[REDIS_LONGSTR_SIZE+3];

        // 命令个数

        /* Add the multi bulk reply length. */

        aux[0] = '*';

        len = ll2string(aux+1,sizeof(aux)-1,argc);

        aux[len+1] = 'r';

        aux[len+2] = 'n';

        feedReplicationBacklog(aux,len+3);

        // 逐个命令写入

        for (j = 0; j < argc; j++) {

            long objlen = stringObjectLen(argv[j]);

            /* We need to feed the buffer with the object as a bulk reply

             * not just as a plain string, so create the $..CRLF payload len

             * ad add the final CRLF */

            aux[0] = '$';

            len = ll2string(aux+1,sizeof(aux)-1,objlen);

            aux[len+1] = 'r';

            aux[len+2] = 'n';

            /* 每个命令格式如下:

            $3

            *3

            SET

            *4

            NAME

            *4

            Jhon*/

            // 命令长度

            feedReplicationBacklog(aux,len+3);

            // 命令

            feedReplicationBacklogWithObject(argv[j]);

            // 换行

            feedReplicationBacklog(aux+len+1,2);

        }

    }

    // 立即给每一个从机发送命令

    /* Write the command to every slave. */

    listRewind(slaves,&li);

    while((ln = listNext(&li))) {

        redisClient *slave = ln->value;

        // 如果从机要求全同步,则不对此从机发送数据

        /* Don't feed slaves that are still waiting for BGSAVE to start */

        if (slave->replstate == REDIS_REPL_WAIT_BGSAVE_START) continue;

        /* Feed slaves that are waiting for the initial SYNC (so these commands

         * are queued in the output buffer until the initial SYNC completes),

         * or are already in sync with the master. */

        // 向从机命令的长度

        /* Add the multi bulk length. */

        addReplyMultiBulkLen(slave,argc);

        // 向从机发送命令

        /* Finally any additional argument that was not stored inside the

         * static buffer if any (from j to argc). */

        for (j = 0; j < argc; j++)

            addReplyBulk(slave,argv[j]);

    }

}

主从数据同步机制概述

redis主从同步有两种方式(或者所两个阶段):

全同步和部分同步。

主从刚刚连接的时候,进行全同步;全同步结束后,进行部分同步。

当然,如果有需要,slave在任何时候都可以发起全同步。

redis策略是,无论如何,首先会尝试进行部分同步,如不成功,要求从机进行全同步,并启动BGSAVE……BGSAVE结束后,传输RDB文件;如果成功,允许从机进行部分同步,并传输积压空间中的数据。

下面这幅图,总结了主从同步的机制:

如需设置slave,master需要向slave发送SLAVEOFhostnameport,从机接收到后会自动连接主机,注册相应读写事件(syncWithMaster())。

// 修改主机

void slaveofCommand(redisClient *c) {

    if (!

strcasecmp(c->argv[1]->ptr,"no") &&

        !

strcasecmp(c->argv[2]->ptr,"one")) {

        // slaveof no one 断开主机连接

        if (server.masterhost) {

            replicationUnsetMaster();

            redisLog(REDIS_NOTICE,"MASTER MODE enabled (user request)");

        }

    } else {

        long port;

        if ((getLongFromObjectOrReply(c, c->argv[2], &port, NULL) !

= REDIS_OK))

            return;

        // 可能已经连接需要连接的主机

        /* Check if we are already attached to the specified slave */

        if (server.masterhost && !

strcasecmp(server.masterhost,c->argv[1]->ptr)

            && server.masterport == port) {

            redisLog(REDIS_NOTICE,"SLAVE OF would result into synchronization with the master we are already connected with. No operation performed.");

            addReplySds(c,sdsnew("+OK Already connected to specified masterrn"));

            return;

        }

        // 断开之前连接主机的连接,连接新的。

 replicationSetMaster() 并不会真正连接主机,只是修改 struct server 中关于主机的设置。

真正的主机连接在 replicationCron() 中完成

        /* There was no previous master or the user specified a different one,

         * we can continue. */

        replicationSetMaster(c->argv[1]->ptr, port);

        redisLog(REDIS_NOTICE,"SLAVE OF %s:

%d enabled (user request)",

        

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 小学教育 > 语文

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1