首页 技术 正文
技术 2022年11月21日
0 收藏 910 点赞 3,651 浏览 5140 个字

Redis主从复制

为了提高性能和系统可用,Redis都会做主从复制,一来可以分担主库压力,二来在主库挂掉的时候从库依旧可以提供服务。Redis的主从复制是异步复制,返回结果给客户端和同步命令到从库是两回事,互不相干,主库也不关心从库的执行结果,对于同步命令执行的结果,从库会直接丢弃并不返回给主库。Redis的主从复制简单高效,但也不太算可靠。

Redis的主从复制是异步复制;全量同步(或增量同步)+命令传播

Slave Server

Slave Server启动初始化配置,根据slaveof配置设置Slave Server的主库host(masterhost)和Slave Server的同步状态(repl_state),和所有Server一样监听客户端链接,开启后台任务。

后台定时任务包含,触发AOF重写、RDB快照、redis监控、状态收集、主从同步相关定时任务等

主从同步后台定时任务包含,从库连接主库、从库重连主库、从库给主库发送同步进度、主库向从库发送心跳包、主库删除超时从库、主库清除同步缓冲区、主库刷新从库状态等

从库连接主库

从库链接主库后,开启同步前的准备和交互,同时从库伴随着和主库交互变换自身状态。下面源代码看一下整个流程(代码有删减)

void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {    /*     * 从库和主库tcp握手成功后,从库的同步状态由REPL_STATE_CONNECT => REPL_STATE_CONNECTING     * 从库向主库发送PING确保可以进行同步操作     * 发送PING,等待接收PONG     */    if (server.repl_state == REPL_STATE_CONNECTING) {        // 修改同步状态 REPL_STATE_CONNECTING => REPL_STATE_RECEIVE_PONG        server.repl_state = REPL_STATE_RECEIVE_PONG;        err = sendSynchronousCommand(SYNC_CMD_WRITE,fd,"PING",NULL);    }    /*     * 接收PONG响应,并修改状态 REPL_STATE_RECEIVE_PONG => REPL_STATE_SEND_AUTH     */    if (server.repl_state == REPL_STATE_RECEIVE_PONG) {        err = sendSynchronousCommand(SYNC_CMD_READ,fd,NULL);        server.repl_state = REPL_STATE_SEND_AUTH;    }    /*     * 以下有多个类似分支,例如:     * 发送身份验证信息     * 从库同步的端口、IP等信息给主库     * 就跳过不罗列了,直接到 同步命令的分支     */    /*     * 从库会先尝试增量同步(发送psync命令+当前同步的进度repl_offset),这种情况是在从库和主库网络闪断后进行;     * 换句话说,如果从库第一次连接主库,那么增量同步是不存在的(毕竟你之前就没有同步过,哪来的增量啊);     * 还有一种情况,如果从库同步进度落后主库同步缓冲区(repl_backlog)太多,也会进行全量同步,具体后话说明     */    if (server.repl_state == REPL_STATE_SEND_PSYNC) {        // 发送增量同步请求命令        ) == PSYNC_WRITE_ERROR) {            err = sdsnew("Write error sending the PSYNC command.");            goto write_error;        }        server.repl_state = REPL_STATE_RECEIVE_PSYNC;        return;    }    /*     * 接收增量同步请求结果     */    psync_result = slaveTryPartialResynchronization(fd,);    if (psync_result == PSYNC_WAIT_REPLY) return; /* Try again later... */    /*     * 不支持增量同步,即不支持psync命令,Redis2.8以上才支持     * 就进行全量同步,发送sync命令     */    if (psync_result == PSYNC_NOT_SUPPORTED) {        // 发送sync命令        ,server.repl_syncio_timeout*) == -) {        }    }    /*     * 发送完同步命令后,回调readSyncBulkPayload方法获取主库回复同步数据     */    if (aeCreateFileEvent(server.el,fd, AE_READABLE,readSyncBulkPayload,NULL)            == AE_ERR)    {    }    /*     * 修改同步状态为REPL_STATE_TRANSFER,即同步数据传输状态     */    server.repl_state = REPL_STATE_TRANSFER;    return;}

以上代码有点长,总结一下步骤:1)slave发送自身信息到master;2)尝试增量同步,成功则等待master回送同步数据;3)不支持增量同步或者增量同步失败,则进行全量同步,并等待master回送同步数据。

全量同步

1、从库发送sync命令给主库,发起全量同步,并等待数据返回

2、主库接收到sync命令,把内存数据保存到rdb文件并把文件内容发送给从库

3、从库接收同步数据并保存到本地rdb文件,最后把rdb文件内容写入到内存数据库,至此全量同步完成

以上是简述一下同步流程,但其中并不止那么简单,例如:同时多个slave发起全量同步请求,主库也只会进行一次bgsave,保存内存快照到rdb文件。

rdb是redis持久化的一种,是当前redis内存数据的快照。所以全量同步,主库发送给从库的是,是内存中每一个key和它对应的value(key => value)。

以下源码分析第三步操作(代码有删减)

void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {    // 读取第一行,获取同步数据量的大小    ) {        ,) ==  && strlen(buf+) >= CONFIG_RUN_ID_SIZE) {        } else {            // 同步数据量的大小            server.repl_transfer_size = strtol(buf+,NULL,);        }        return;    }    // 读取数据    nread = read(fd,buf,readlen);    // 更新最后同步通信时间    server.repl_transfer_lastio = server.unixtime;    // 保存数据到本地rdb文件    if (write(server.repl_transfer_fd,buf,nread) != nread) {    }    if (eof_reached) {        // 从rdb文件中载入数据到内存        serverLog(LL_NOTICE, "MASTER <-> SLAVE sync: Loading DB in memory");        if (rdbLoad(server.rdb_filename) != C_OK) {        }        // 同步完后,slave修改主库信息,开始接收主库命令扩散        replicationCreateMasterClient(server.repl_transfer_s);        serverLog(LL_NOTICE, "MASTER <-> SLAVE sync: Finished with success");    }    return;}/* * 同步完后,slave修改主库信息,开始接收主库命令扩散 */void replicationCreateMasterClient(int fd) {    server.master = createClient(fd);  // 这里设置监听接收主库的发送的数据    server.master->flags |= CLIENT_MASTER;    server.master->authenticated = ;    server.repl_state = REPL_STATE_CONNECTED;  // 修改从库同步状态    /*     * reploff和replrunid这两个是增量同步的必要参数     */    server.master->reploff = server.repl_master_initial_offset; // master的同步缓冲区的总量(当前从库同步进度)    memcpy(server.master->replrunid, server.repl_master_runid,        sizeof(server.repl_master_runid));   // 保存主库runid}

命令传播

完成全量同步(增量同步)后,主库接受客户端命令,修改了数据,为了保持主从数据一致,这些命令也需要在从库上执行一遍,哪怎么操作呢?当然不是客户端逐一修改所有从库了,而是由主库执行命令成功后,异步地把命令发送给所有从库。

这部分主要工作在于主库,就不说了,下一篇主库角度再说。从库接收主库的命令传播,其实和其他客户端的命令一样的行为,只是从库会判断是否来自主库发送的命令,更新自己同步进度,主库不关心从库执行命令的结果,所以从库也不会发送执行结果给主库,省略了一次网络IO。

心跳检测

开篇我们说过,Redis会有个定时任务在后台执行,从库会每秒向主库发送ack+同步进度;主库也会定时发送PING命令检测它所有从库的存活。

/* * 从库会每秒向主库发送ack+同步进度 * psync ack repl_off */if (server.masterhost && server.master &&    !(server.master->flags & CLIENT_PRE_PSYNC))    replicationSendAck();/* * 主库定时发送PING命令检测它所有从库的存活 */) {    ping_argv[] = createStringObject();    replicationFeedSlaves(server.slaves, server.slaveseldb,        ping_argv, );    decrRefCount(ping_argv[]);}

风险

Redis的主从复制是很高效,也没有太多花哨的东西,基于异步同步,客户端不需要等待同步结果,但是也是这样的高效同步带来一些风险。

1)主库发送仅且发送一次命令给从库,如果超时,命令丢失,从库没有接收到,会造成不一致;

2)从库内存满了,主库也没法知道,而且从库收到命令传播依旧会更新自己同步进度;

3)异步复制带来的同步间隙,造成短时间内不一致,这点要根据具体业务处理;

4)客户端修改从库数据,也会导致主从不一致,可以把从库设置成只读;

废话

从库未全量同步过

Slave:Master,我要增量同步(psync ? -1)

Master:EXO ME?你没全量同步过,不行,你必须全量同步

Slave:好的,师父。全量同步(Sync)

Master:同意。等着吧。

(Slave等啊等)

Master:接着,rdb

Slave:收!

(Slave全量同步完后,上线工作了,命令传播)

Master:这是我刚执行完的命令,你执行一下

Slave:好的(执行完了也不告诉你)

Master:这是我刚执行完的命令,你执行一下

Slave:好的(执行完了也不告诉你)

……

主从网络断开重连后

Slave:师父,刚掉线了,我是不是错过了什么,我要增量同步一下(psync Master_id repl_off)

Master:刚哪浪去了?问你又不答我。行了,增量同步一下吧,等着

Slave:好的,师父。

(Slave等啊等)

Master:接着,这些都是你刚没有执行的命令

Slave:收!

(Slave增量同步完后,上线工作了,命令传播)

Master:这是我刚执行完的命令,你执行一下

Slave:好的(执行完了也不告诉你)

Master:这是我刚执行完的命令,你执行一下

Slave:好的(执行完了也不告诉你)

……

主从网络断开重连后,slave落后太多

Slave:师父,刚掉线了,我是不是错过了什么,我要增量同步一下(psync Master_id repl_off)

Master:刚哪浪去了?问你又不答我。不行不行,你落后了(repl_off进度太旧了),你先全量一下我们在聊吧

Slave:好的,师父。全量同步(Sync)

Master:同意。等着吧。

(Slave等啊等)

Master:接着,rdb

Slave:收!

(Slave全量同步完后,上线工作了,命令传播)

Master:这是我刚执行完的命令,你执行一下

Slave:好的(执行完了也不告诉你)

Master:这是我刚执行完的命令,你执行一下

Slave:好的(执行完了也不告诉你)

……

相关推荐
python开发_常用的python模块及安装方法
adodb:我们领导推荐的数据库连接组件bsddb3:BerkeleyDB的连接组件Cheetah-1.0:我比较喜欢这个版本的cheeta…
日期:2022-11-24 点赞:878 阅读:9,075
Educational Codeforces Round 11 C. Hard Process 二分
C. Hard Process题目连接:http://www.codeforces.com/contest/660/problem/CDes…
日期:2022-11-24 点赞:807 阅读:5,551
下载Ubuntn 17.04 内核源代码
zengkefu@server1:/usr/src$ uname -aLinux server1 4.10.0-19-generic #21…
日期:2022-11-24 点赞:569 阅读:6,399
可用Active Desktop Calendar V7.86 注册码序列号
可用Active Desktop Calendar V7.86 注册码序列号Name: www.greendown.cn Code: &nb…
日期:2022-11-24 点赞:733 阅读:6,176
Android调用系统相机、自定义相机、处理大图片
Android调用系统相机和自定义相机实例本博文主要是介绍了android上使用相机进行拍照并显示的两种方式,并且由于涉及到要把拍到的照片显…
日期:2022-11-24 点赞:512 阅读:7,811
Struts的使用
一、Struts2的获取  Struts的官方网站为:http://struts.apache.org/  下载完Struts2的jar包,…
日期:2022-11-24 点赞:671 阅读:4,892