Redis源码阅读之主从复制——Slave视角
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:好的(执行完了也不告诉你)
……
Redis源码阅读之主从复制——Slave视角的更多相关文章
- Redis源码阅读(二)高可用设计——复制
Redis源码阅读(二)高可用设计-复制 复制的概念:Redis的复制简单理解就是一个Redis服务器从另一台Redis服务器复制所有的Redis数据库数据,能保持两台Redis服务器的数据库数据一致 ...
- Redis源码阅读(六)集群-故障迁移(下)
Redis源码阅读(六)集群-故障迁移(下) 最近私人的事情比较多,没有抽出时间来整理博客.书接上文,上一篇里总结了Redis故障迁移的几个关键点,以及Redis中故障检测的实现.本篇主要介绍集群检测 ...
- Redis源码阅读(三)集群-连接初始化
Redis源码阅读(三)集群-连接建立 对于并发请求很高的生产环境,单个Redis满足不了性能要求,通常都会配置Redis集群来提高服务性能.3.0之后的Redis支持了集群模式. Redis官方提供 ...
- Redis源码阅读(五)集群-故障迁移(上)
Redis源码阅读(五)集群-故障迁移(上) 故障迁移是集群非常重要的功能:直白的说就是在集群中部分节点失效时,能将失效节点负责的键值对迁移到其他节点上,从而保证整个集群系统在部分节点失效后没有丢失数 ...
- Redis源码阅读(四)集群-请求分配
Redis源码阅读(四)集群-请求分配 集群搭建好之后,用户发送的命令请求可以被分配到不同的节点去处理.那Redis对命令请求分配的依据是什么?如果节点数量有变动,命令又是如何重新分配的,重分配的过程 ...
- Redis源码阅读(一)事件机制
Redis源码阅读(一)事件机制 Redis作为一款NoSQL非关系内存数据库,具有很高的读写性能,且原生支持的数据类型丰富,被广泛的作为缓存.分布式数据库.消息队列等应用.此外Redis还有许多高可 ...
- Redis源码阅读-Adlist双向链表
Redis源码阅读-链表部分- 链表数据结构在Adlist.h Adlist.c Redis的链表是双向链表,内部定义了一个迭代器. 双向链表的函数主要是链表创建.删除.节点插入.头插入.尾插入. ...
- [Redis源码阅读]sds字符串实现
初衷 从开始工作就开始使用Redis,也有一段时间了,但都只是停留在使用阶段,没有往更深的角度探索,每次想读源码都止步在阅读书籍上,因为看完书很快又忘了,这次逼自己先读代码.因为个人觉得写作需要阅读文 ...
- Redis源码阅读---连接建立
对于并发请求很高的生产环境,单个Redis满足不了性能要求,通常都会配置Redis集群来提高服务性能.3.0之后的Redis支持了集群模式. Redis官方提供的集群功能是无中心的,命令请求可以发送到 ...
随机推荐
- 多媒体开发库 之 SDL 详解
SDL 简介 SDL(Simple DirectMedia Layer)是一套开放源代码的跨平台多媒体开发库,使用C语言写成.SDL提供了数种控制图像.声音.输出入的函数,让开发者只要用相同或是相似的 ...
- 红包项目总结---MVC版
起因: 针对传统版的明显缺陷做优化.主要是提升可维护性. 效果 线上: 未发布 线下:http://10.27.5.1/svn/FED/code/hongbao/year-end hb-fac ...
- SQL Server 中函数的理解总结
T-SQL语言为我们提供了更加灵活的方式操作数据,那就是函数,函数总的分为三大类:标量函数:(传入一个参数,再传出一个参数)聚合函数(传入多个参数,传出一个参数),表值函数(传入一个结果集对象,让我们 ...
- Mac苹果系统 多系统启动:The rEFInd Boot Manager
苹果系统 多系统启动 下载安装REFIT: 首先安装一下:REFIT, 在这个页面下载: http://refit.sourceforge.net/#download 选择mac disk image ...
- JDBC连接数据库程序
废话少说,看了尚学堂的视频以及某大神的博客,总结出以下.(本文以oracle数据库为例) 创建一个JDBC连接数据库的程序,需要着手做以下几件事情: 注意,这里边使用了java.sql.Stateme ...
- Java之JSON数据
特别注意:使用JSON前需要导包 操作步骤地址:http://blog.csdn.net/baidu_37107022/article/details/70876993 1.定义 JSON(JavaS ...
- nodeJS之流stream
前面的话 当内存中无法一次装下需要处理的数据时,或者一边读取一边处理更加高效时,我们就需要用到数据流.NodeJS中通过各种Stream来提供对数据流的操作.本文将详细说明NodeJS中的流strea ...
- Nginx实用教程(一):启动、停止、重载配置
Nginx是一个功能强大的web服务器和负载均衡软件,由俄罗斯人开发.Nginx包括一个master进程和数个worker进程,master进程用于读取.解析配置文件和管理worker进程,worke ...
- rsync的用法
一.用法例子 1.增量备份本地文件#rsync -av ebook/ tmp/ //注意:文件名中最好不要有 :#rsync -avzrtopgL --progress /src /dst 2.本地和 ...
- java hascode
有部分代码如下: Cat cat=new Cat("Kitty",2);system.out.println(cat):问题:输出什么? 调用并执行toString()方法,两种情 ...