Redis源码分析:主从复制

来源:互联网 发布:码字电脑 知乎 编辑:程序博客网 时间:2024/06/08 09:04

源码版本:2.4.4
更新2014.3.17,base 2.8.7

redis的主从复制实现简单却功能强大,其具有以下特点:
1. 一个master支持多个slave连接,slave可以接受其他slave的连接
2. 主从同步时,master和slave都是非阻塞的

redis主从复制可以用来:
1. data redundancy 
2. slave作为master的扩展,提供一些read-only的服务
3. 可以将数据持久化放在slave做,从而提升master性能

通过简单的配置slave(master端无需配置),用户就能使用redis的主从复制
相关配置(redis.conf):
slaveof <masterip> <masterport>
表示该redis服务作为slave,masterip和masterport分别为master 的ip和port

masterauth 
<master-password>
如果master设置了安全密码,则此处设置为相应的密码

slave-serve-stale-data yes
当slave丢失master或者同步正在进行时,如果发生对slave的服务请求:
slave-serve-stale-data设置为yes则slave依然正常提供服务
slave-serve-stale-data设置为no则slave返回client错误:"SYNC with master in progress"

repl-ping-slave-period 10
slave发送PINGS到master的时间间隔

repl-timeout 60
IO超时时间


代码:
slave端
slave状态:
/* Slave replication state - slave side */
#define REDIS_REPL_NONE 0 /* No active replication */
#define REDIS_REPL_CONNECT 1 /* Must connect to master */
#define REDIS_REPL_CONNECTING 2 /* Connecting to master */
#define REDIS_REPL_TRANSFER 3 /* Receiving .rdb from master */
#define REDIS_REPL_CONNECTED 4 /* Connected to master */
初始化时设置
server.replstate = REDIS_REPL_CONNECT
即slave需要连接master
slave周期性调用replicationCron,查看slave状态:
[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. void replicationCron(void) {  
  2.     /*判断是否IO超时*/  
  3.     if (server.masterhost && server.replstate == REDIS_REPL_TRANSFER &&  
  4.         (time(NULL)-server.repl_transfer_lastio) > server.repl_timeout)  
  5.     {  
  6.         redisLog(REDIS_WARNING,"Timeout receiving bulk data from MASTER...");  
  7.         replicationAbortSyncTransfer(); //终止连接,并设置server.replstate = REDIS_REPL_CONNECT;  
  8.     }  
  9.   
  10.     /* Timed out master when we are an already connected slave? */  
  11.     if (server.masterhost && server.replstate == REDIS_REPL_CONNECTED &&  
  12.         (time(NULL)-server.master->lastinteraction) > server.repl_timeout)  
  13.     {  
  14.         redisLog(REDIS_WARNING,"MASTER time out: no data nor PING received...");  
  15.         freeClient(server.master);  
  16.     }  
  17.   
  18.     /* Check if we should connect to a MASTER */  
  19.     if (server.replstate == REDIS_REPL_CONNECT) {  
  20.         redisLog(REDIS_NOTICE,"Connecting to MASTER...");  
  21.         if (connectWithMaster() == REDIS_OK) { //连接master  
  22.             redisLog(REDIS_NOTICE,"MASTER <-> SLAVE sync started");  
  23.         }  
  24.     }  
  25.       
  26.     /* If we have attached slaves, PING them from time to time. 
  27.      * So slaves can implement an explicit timeout to masters, and will 
  28.      * be able to detect a link disconnection even if the TCP connection 
  29.      * will not actually go down. */  
  30.     if (!(server.cronloops % (server.repl_ping_slave_period*10))) {  
  31.         listIter li;  
  32.         listNode *ln;  
  33.   
  34.         listRewind(server.slaves,&li);  
  35.         while((ln = listNext(&li))) {  
  36.             redisClient *slave = ln->value;  
  37.   
  38.             /* Don't ping slaves that are in the middle of a bulk transfer 
  39.              * with the master for first synchronization. */  
  40.             if (slave->replstate == REDIS_REPL_SEND_BULK) continue;  
  41.             if (slave->replstate == REDIS_REPL_ONLINE) {  
  42.                 /* If the slave is online send a normal ping */  
  43.                 addReplySds(slave,sdsnew("PING\r\n"));  
  44.             } else {  
  45.                 /* Otherwise we are in the pre-synchronization stage. 
  46.                  * Just a newline will do the work of refreshing the 
  47.                  * connection last interaction time, and at the same time 
  48.                  * we'll be sure that being a single char there are no 
  49.                  * short-write problems. */  
  50.                 if (write(slave->fd, "\n", 1) == -1) {  
  51.                     /* Don't worry, it's just a ping. */  
  52.                 }  
  53.             }  
  54.         }  
  55.     }  
  56. }  

当server.replstate == REDIS_REPL_CONNECT时,slave连接master,连接成功后,slave执行syncWithMaster函数,syncWithMaster将向master发送SYNC命令
[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. int connectWithMaster(void) {  
  2.     int fd;  
  3.   
  4.     fd = anetTcpNonBlockConnect(NULL,server.masterhost,server.masterport);  
  5.     if (fd == -1) {  
  6.         redisLog(REDIS_WARNING,"Unable to connect to MASTER: %s",  
  7.             strerror(errno));  
  8.         return REDIS_ERR;  
  9.     }  
  10.   
  11.     if (aeCreateFileEvent(server.el,fd,AE_READABLE|AE_WRITABLE,syncWithMaster,NULL) ==  
  12.             AE_ERR)  
  13.     {  
  14.         close(fd);  
  15.         redisLog(REDIS_WARNING,"Can't create readable event for SYNC");  
  16.         return REDIS_ERR;  
  17.     }  
  18.   
  19.     server.repl_transfer_s = fd;  
  20.     server.replstate = REDIS_REPL_CONNECTING;  
  21.     return REDIS_OK;  
  22. }  

master端:
master对于slave的连接和client的连接统一处理,在接收到slave发出的SYNC命令后,执行syncCommand,syncCommand 将查看当前状态,如果正在做快照,则等待,否则启动后台进程做快照。
[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. void syncCommand(redisClient *c) {  
  2.     /* ignore SYNC if aleady slave or in monitor mode */  
  3.     if (c->flags & REDIS_SLAVE) return;  
  4.   
  5.     /* Refuse SYNC requests if we are a slave but the link with our master 
  6.      * is not ok... */  
  7.     if (server.masterhost && server.replstate != REDIS_REPL_CONNECTED) {  
  8.         addReplyError(c,"Can't SYNC while not connected with my master");  
  9.         return;  
  10.     }  
  11.   
  12.     /* SYNC can't be issued when the server has pending data to send to 
  13.      * the client about already issued commands. We need a fresh reply 
  14.      * buffer registering the differences between the BGSAVE and the current 
  15.      * dataset, so that we can copy to other slaves if needed. */  
  16.     if (listLength(c->reply) != 0) {  
  17.         addReplyError(c,"SYNC is invalid with pending input");  
  18.         return;  
  19.     }  
  20.   
  21.     redisLog(REDIS_NOTICE,"Slave ask for synchronization");  
  22.     /* Here we need to check if there is a background saving operation 
  23.      * in progress, or if it is required to start one */  
  24.     if (server.bgsavechildpid != -1) {  
  25.        .....  
  26.     } else {  
  27.         /* Ok we don't have a BGSAVE in progress, let's start one */  
  28.         redisLog(REDIS_NOTICE,"Starting BGSAVE for SYNC");  
  29.         if (rdbSaveBackground(server.dbfilename) != REDIS_OK) {  
  30.             redisLog(REDIS_NOTICE,"Replication failed, can't BGSAVE");  
  31.             addReplyError(c,"Unable to perform background save");  
  32.             return;  
  33.         }  
  34.         c->replstate = REDIS_REPL_WAIT_BGSAVE_END;  
  35.     }  
  36.     c->repldbfd = -1;  
  37.     c->flags |= REDIS_SLAVE;  
  38.     c->slaveseldb = 0;  
  39.     listAddNodeTail(server.slaves,c);  
  40.     return;  
  41. }  

在完成快照后,执行updateSlavesWaitingBgsave函数,updateSlavesWaitingBgsave将查看当前master的各个slave的状态,如果发现有在等待bgsave完成的,则注册事件sendBulkToSlave,sendBulkToSlave将快照文件发送给slave
[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. void updateSlavesWaitingBgsave(int bgsaveerr) {  
  2.     listNode *ln;  
  3.     int startbgsave = 0;  
  4.     listIter li;  
  5.   
  6.     listRewind(server.slaves,&li);  
  7.     while((ln = listNext(&li))) {  
  8.         redisClient *slave = ln->value;  
  9.   
  10.         if (slave->replstate == REDIS_REPL_WAIT_BGSAVE_START) {  
  11.             startbgsave = 1;  
  12.             slave->replstate = REDIS_REPL_WAIT_BGSAVE_END;  
  13.         } else if (slave->replstate == REDIS_REPL_WAIT_BGSAVE_END) {  
  14.             struct redis_stat buf;  
  15.   
  16.             if (bgsaveerr != REDIS_OK) {  
  17.                 freeClient(slave);  
  18.                 redisLog(REDIS_WARNING,"SYNC failed. BGSAVE child returned an error");  
  19.                 continue;  
  20.             }  
  21.             if ((slave->repldbfd = open(server.dbfilename,O_RDONLY)) == -1 ||  
  22.                 redis_fstat(slave->repldbfd,&buf) == -1) {  
  23.                 freeClient(slave);  
  24.                 redisLog(REDIS_WARNING,"SYNC failed. Can't open/stat DB after BGSAVE: %s", strerror(errno));  
  25.                 continue;  
  26.             }  
  27.             slave->repldboff = 0;  
  28.             slave->repldbsize = buf.st_size;  
  29.             slave->replstate = REDIS_REPL_SEND_BULK;  
  30.             <span style="color:#ff0000;">aeDeleteFileEvent(server.el,slave->fd,AE_WRITABLE); //删除之前的写回调</span>  
  31.             if (<span style="color:#ff0000;">aeCreateFileEvent(server.el, slave->fd, AE_WRITABLE, sendBulkToSlave, slave) == AE_ERR</span>) { //注册新的写回调  
  32.                 freeClient(slave);  
  33.                 continue;  
  34.             }  
  35.         }  
  36.     }  
  37.     if (startbgsave) {  
  38.         if (rdbSaveBackground(server.dbfilename) != REDIS_OK) {  
  39.             listIter li;  
  40.   
  41.             listRewind(server.slaves,&li);  
  42.             redisLog(REDIS_WARNING,"SYNC failed. BGSAVE failed");  
  43.             while((ln = listNext(&li))) {  
  44.                 redisClient *slave = ln->value;  
  45.   
  46.                 if (slave->replstate == REDIS_REPL_WAIT_BGSAVE_START)  
  47.                     freeClient(slave);  
  48.             }  
  49.         }  
  50.     }  
  51. }  
为了避免阻塞应用,每次只传输16K数据
[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. void sendBulkToSlave(aeEventLoop *el, int fd, void *privdata, int mask) {  
  2.     ......  
  3.     lseek(slave->repldbfd,slave->repldboff,SEEK_SET); //指针移动到上次发送的位置  
  4.     buflen = read(slave->repldbfd,buf,REDIS_IOBUF_LEN); //读取16K数据  
  5.     ......  
  6.     if ((nwritten = write(fd,buf,buflen)) == -1) { //传输数据到slave  
  7.         if (errno != EAGAIN) {  
  8.             redisLog(REDIS_WARNING,"Write error sending DB to slave: %s",  
  9.                 strerror(errno));  
  10.             freeClient(slave);  
  11.         }  
  12.         return;  
  13.     }  
  14.     slave->repldboff += nwritten; //更新已发送位置  
  15.     ......  
  16. }  

在slave完成第一次的同步后,后续如果master接收到改变db状态的命令,则调用replicationFeedSlaves将相应变更发送slave
[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. /* Call() is the core of Redis execution of a command */  
  2. void call(redisClient *c) {  
  3.     long long dirty, start = ustime(), duration;  
  4.   
  5.     dirty = server.dirty;  
  6.     c->cmd->proc(c);  
  7.     dirty = server.dirty-dirty;  
  8.     duration = ustime()-start;  
  9.     slowlogPushEntryIfNeeded(c->argv,c->argc,duration);  
  10.   
  11.     if (server.appendonly && dirty > 0)  
  12.         feedAppendOnlyFile(c->cmd,c->db->id,c->argv,c->argc);  
  13.     if ((dirty > 0 || c->cmd->flags & REDIS_CMD_FORCE_REPLICATION) &&  
  14.         listLength(server.slaves))  
  15.         replicationFeedSlaves(server.slaves,c->db->id,c->argv,c->argc);  
  16.     if (listLength(server.monitors))  
  17.         replicationFeedMonitors(server.monitors,c->db->id,c->argv,c->argc);  
  18.     server.stat_numcommands++;  
  19. }  


总结:
1. redis主从复制,并没有增加太多额外代码,但是功能强大,支持多个slave,并且支持slave作为master。
2. redis虽然宣称主从复制无阻塞,但是,由于redis使用单线程服务,而和slave的交互由处理线程统一处理,因此,对性能有一定影响

0 0
原创粉丝点击