找回密码
 用户注册

QQ登录

只需一步,快速开始

查看: 6150|回复: 0

Redis源码分析:主从复制

[复制链接]
发表于 2012-2-7 10:13:12 | 显示全部楼层 |阅读模式
源码版本:redis 2.4.4
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状态:
  1. /*
  2. Slave replication state - slave side */
  3. #define REDIS_REPL_NONE 0 /* No
  4. active replication */
  5. #define REDIS_REPL_CONNECT 1 /* Must connect to master
  6. */
  7. #define REDIS_REPL_CONNECTING 2 /* Connecting to master */
  8. #define
  9. REDIS_REPL_TRANSFER 3 /* Receiving .rdb from master */
  10. #define
  11. REDIS_REPL_CONNECTED 4 /* Connected to master */
  12. 初始化时设置
  13. server.replstate =
  14. REDIS_REPL_CONNECT
  15. 即slave需要连接master
  16. slave周期性调用replicationCron,查看slave状态:
  17. void replicationCron(void) {
  18.     /*判断是否IO超时*/
  19.     if (server.masterhost && server.replstate == REDIS_REPL_TRANSFER &&
  20.         (time(NULL)-server.repl_transfer_lastio) > server.repl_timeout)
  21.     {
  22.         redisLog(REDIS_WARNING,"Timeout receiving bulk data from MASTER...");
  23.         replicationAbortSyncTransfer(); //终止连接,并设置server.replstate = REDIS_REPL_CONNECT;
  24.     }
  25.     /* Timed out master when we are an already connected slave? */
  26.     if (server.masterhost && server.replstate == REDIS_REPL_CONNECTED &&
  27.         (time(NULL)-server.master->lastinteraction) > server.repl_timeout)
  28.     {
  29.         redisLog(REDIS_WARNING,"MASTER time out: no data nor PING received...");
  30.         freeClient(server.master);
  31.     }
  32.     /* Check if we should connect to a MASTER */
  33.     if (server.replstate == REDIS_REPL_CONNECT) {
  34.         redisLog(REDIS_NOTICE,"Connecting to MASTER...");
  35.         if (connectWithMaster() == REDIS_OK) { //连接master
  36.             redisLog(REDIS_NOTICE,"MASTER <-> SLAVE sync started");
  37.         }
  38.     }
  39.    
  40.     /* If we have attached slaves, PING them from time to time.
  41.      * So slaves can implement an explicit timeout to masters, and will
  42.      * be able to detect a link disconnection even if the TCP connection
  43.      * will not actually go down. */
  44.     if (!(server.cronloops % (server.repl_ping_slave_period*10))) {
  45.         listIter li;
  46.         listNode *ln;
  47.         listRewind(server.slaves,&li);
  48.         while((ln = listNext(&li))) {
  49.             redisClient *slave = ln->value;
  50.             /* Don't ping slaves that are in the middle of a bulk transfer
  51.              * with the master for first synchronization. */
  52.             if (slave->replstate == REDIS_REPL_SEND_BULK) continue;
  53.             if (slave->replstate == REDIS_REPL_ONLINE) {
  54.                 /* If the slave is online send a normal ping */
  55.                 addReplySds(slave,sdsnew("PING\r\n"));
  56.             } else {
  57.                 /* Otherwise we are in the pre-synchronization stage.
  58.                  * Just a newline will do the work of refreshing the
  59.                  * connection last interaction time, and at the same time
  60.                  * we'll be sure that being a single char there are no
  61.                  * short-write problems. */
  62.                 if (write(slave->fd, "\n", 1) == -1) {
  63.                     /* Don't worry, it's just a ping. */
  64.                 }
  65.             }
  66.         }
  67.     }
  68. }
  69. 当server.replstate ==
  70. REDIS_REPL_CONNECT时,slave连接master,连接成功后,slave执行syncWithMaster函数,syncWithMaster将向master发送SYNC命令
  71. int connectWithMaster(void) {
  72.     int fd;
  73.     fd = anetTcpNonBlockConnect(NULL,server.masterhost,server.masterport);
  74.     if (fd == -1) {
  75.         redisLog(REDIS_WARNING,"Unable to connect to MASTER: %s",
  76.             strerror(errno));
  77.         return REDIS_ERR;
  78.     }
  79.     if (aeCreateFileEvent(server.el,fd,AE_READABLE|AE_WRITABLE,syncWithMaster,NULL) ==
  80.             AE_ERR)
  81.     {
  82.         close(fd);
  83.         redisLog(REDIS_WARNING,"Can't create readable event for SYNC");
  84.         return REDIS_ERR;
  85.     }
  86.     server.repl_transfer_s = fd;
  87.     server.replstate = REDIS_REPL_CONNECTING;
  88.     return REDIS_OK;
  89. }
复制代码

master端:
  1. master对于slave的连接和client的连接统一处理,在接收到slave发出的SYNC命令后,执行syncCommand,syncCommand
  2. 将查看当前状态,如果正在做快照,则等待,否则启动后台进程做快照。
  3. void syncCommand(redisClient *c) {
  4.     /* ignore SYNC if aleady slave or in monitor mode */
  5.     if (c->flags & REDIS_SLAVE) return;
  6.     /* Refuse SYNC requests if we are a slave but the link with our master
  7.      * is not ok... */
  8.     if (server.masterhost && server.replstate != REDIS_REPL_CONNECTED) {
  9.         addReplyError(c,"Can't SYNC while not connected with my master");
  10.         return;
  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.     redisLog(REDIS_NOTICE,"Slave ask for synchronization");
  21.     /* Here we need to check if there is a background saving operation
  22.      * in progress, or if it is required to start one */
  23.     if (server.bgsavechildpid != -1) {
  24.        .....
  25.     } else {
  26.         /* Ok we don't have a BGSAVE in progress, let's start one */
  27.         redisLog(REDIS_NOTICE,"Starting BGSAVE for SYNC");
  28.         if (rdbSaveBackground(server.dbfilename) != REDIS_OK) {
  29.             redisLog(REDIS_NOTICE,"Replication failed, can't BGSAVE");
  30.             addReplyError(c,"Unable to perform background save");
  31.             return;
  32.         }
  33.         c->replstate = REDIS_REPL_WAIT_BGSAVE_END;
  34.     }
  35.     c->repldbfd = -1;
  36.     c->flags |= REDIS_SLAVE;
  37.     c->slaveseldb = 0;
  38.     listAddNodeTail(server.slaves,c);
  39.     return;
  40. }
  41. 在完成快照后,执行updateSlavesWaitingBgsave函数,updateSlavesWaitingBgsave将查看当前master的各个slave的状态,如果发现有在等待bgsave完成的,则注册事件sendBulkToSlave,sendBulkToSlave将快照文件发送给slavevoid updateSlavesWaitingBgsave(int bgsaveerr) {
  42.     listNode *ln;
  43.     int startbgsave = 0;
  44.     listIter li;
  45.     listRewind(server.slaves,&li);
  46.     while((ln = listNext(&li))) {
  47.         redisClient *slave = ln->value;
  48.         if (slave->replstate == REDIS_REPL_WAIT_BGSAVE_START) {
  49.             startbgsave = 1;
  50.             slave->replstate = REDIS_REPL_WAIT_BGSAVE_END;
  51.         } else if (slave->replstate == REDIS_REPL_WAIT_BGSAVE_END) {
  52.             struct redis_stat buf;
  53.             if (bgsaveerr != REDIS_OK) {
  54.                 freeClient(slave);
  55.                 redisLog(REDIS_WARNING,"SYNC failed. BGSAVE child returned an error");
  56.                 continue;
  57.             }
  58.             if ((slave->repldbfd = open(server.dbfilename,O_RDONLY)) == -1 ||
  59.                 redis_fstat(slave->repldbfd,&buf) == -1) {
  60.                 freeClient(slave);
  61.                 redisLog(REDIS_WARNING,"SYNC failed. Can't open/stat DB after BGSAVE: %s", strerror(errno));
  62.                 continue;
  63.             }
  64.             slave->repldboff = 0;
  65.             slave->repldbsize = buf.st_size;
  66.             slave->replstate = REDIS_REPL_SEND_BULK;
  67.             aeDeleteFileEvent(server.el,slave->fd,AE_WRITABLE);
  68.             if (aeCreateFileEvent(server.el, slave->fd, AE_WRITABLE, sendBulkToSlave, slave) == AE_ERR) {
  69.                 freeClient(slave);
  70.                 continue;
  71.             }
  72.         }
  73.     }
  74.     if (startbgsave) {
  75.         if (rdbSaveBackground(server.dbfilename) != REDIS_OK) {
  76.             listIter li;
  77.             listRewind(server.slaves,&li);
  78.             redisLog(REDIS_WARNING,"SYNC failed. BGSAVE failed");
  79.             while((ln = listNext(&li))) {
  80.                 redisClient *slave = ln->value;
  81.                 if (slave->replstate == REDIS_REPL_WAIT_BGSAVE_START)
  82.                     freeClient(slave);
  83.             }
  84.         }
  85.     }
  86. }
复制代码

在slave完成第一次的同步后,后续如果master接收到改变db状态的命令,则调用replicationFeedSlaves将相应变更发送slave
  1. /* Call() is the core of Redis execution of a command */
  2. void call(redisClient *c) {
  3.     long long dirty, start = ustime(), duration;
  4.     dirty = server.dirty;
  5.     c->cmd->proc(c);
  6.     dirty = server.dirty-dirty;
  7.     duration = ustime()-start;
  8.     slowlogPushEntryIfNeeded(c->argv,c->argc,duration);
  9.     if (server.appendonly && dirty > 0)
  10.         feedAppendOnlyFile(c->cmd,c->db->id,c->argv,c->argc);
  11.     if ((dirty > 0 || c->cmd->flags & REDIS_CMD_FORCE_REPLICATION) &&
  12.         listLength(server.slaves))
  13.         replicationFeedSlaves(server.slaves,c->db->id,c->argv,c->argc);
  14.     if (listLength(server.monitors))
  15.         replicationFeedMonitors(server.monitors,c->db->id,c->argv,c->argc);
  16.     server.stat_numcommands++;
  17. }
复制代码
总结:
1. redis主从复制,并没有增加太多额外代码,但是功能强大,支持多个slave,并且支持slave作为master。
2. redis虽然宣称主从复制无阻塞,但是,由于redis使用单线程服务,而和slave的交互由处理线程统一处理,因此,对性能有影响。在slave第一次和master做同步时,如果master快照文件较大,则快照文件的传输将耗费较长时间,文件传输过程中master无法提供服务。

作者:yfkiss 发表于2012-2-6 22:07:47 原文链接

您需要登录后才可以回帖 登录 | 用户注册

本版积分规则

Archiver|手机版|小黑屋|ACE Developer ( 京ICP备06055248号 )

GMT+8, 2024-5-3 17:37 , Processed in 0.023444 second(s), 6 queries , Redis On.

Powered by Discuz! X3.5

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表