Redis教程:主从复制

目录

1 主从配置方法

2 主从复制的作用

3 主从复制的机制

3.1 全量数据同步(full resyncchrozation)

3.2 增量同步

4 主从复制的实现

4.1 主从关系的建立


1 主从配置方法

  • 配置文件: 在从服务器的配置文件中加入:slaveof   ip  port
  • 启动命令: redis-server启动命令后加入   --slaveof   ip  port    <本地端口:--port  xxxx>
  • 客户端命令: Redis服务器启动后,直接通过客户端执行命令:slaveof  ip  port,则该Redis实例成为从节点。

从模式:

# Redis使用后台模式
daemonize yes
# 关闭保护模式
#protected-mode no
# 注释以下内容开启远程访问
# bind 127.0.0.1
# 修改启动端口为6379
port 6380
# 修改pidfile指向路径(单机多服务建议修改)
pidfile /usr/local/redis-4.0.4/conf/redis_6380.pid
#数据库的存放位置(单机多服务建议修改)
dir /usr/local/redis-4.0.4/db/slave_xxx
#Slaveof命令可以将当前服务器转变为指定服务器的从属服务器(slave server)。
slaveof 192.168.235.128 6379

2 主从复制的作用

  • 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
  • 故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务的冗余。
  • 负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点),分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。
  • 读写分离:可以用于实现读写分离,主库写、从库读,读写分离不仅可以提高服务器的负载能力,同时可根据需求的变化,改变从库的数量;
  • 高可用基石:除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础

3 主从复制的机制

       Redis的主从复制功能除了支持一个Master节点对应多个Slave节点的同时进行复制外,还支持Slave节点向其它多个Slave节点进行复制。这样就使得架构师能够灵活组织业务缓存数据的传播,例如使用多个Slave作为数据读取服务的同时,专门使用一个Slave节点为流式分析工具服务。Redis的主从复制功能分为两种数据同步模式进行:全量数据同步和增量数据同步。

3.1 全量数据同步(full resyncchrozation)

      先执行一次全同步 — 请求master BgSave出自己的一个RDB Snapshot文件发给slave,slave接收完毕后,清除掉自己的旧数据,然后将RDB载入内存。

      上图简要说明了Redis中Master节点到Slave节点的全量数据同步过程。当Slave节点给定的replication id 和Master的replication id不一致时,或者Slave给定的上一次增量同步的offset的位置在Master的环形内存中(replication backlog)无法定位时(后文会提到),Master就会对Slave发起全量同步操作。这时无论您是否在Master打开了RDB快照功能,它和Slave节点的每一次全量同步操作过程都会更新/创建Master上的RDB文件。在Slave连接到Master,并完成第一次全量数据同步后,接下来Master到Slave的数据同步过程一般就是增量同步形式了(也称为部分同步)。增量同步过程不再主要依赖RDB文件,Master会将新产生的数据变化操作存放在replication backlog这个内存缓存区,这个内存区域是一个环形缓冲区,也就是说是一个FIFO的队列。 

3.2 增量同步

      进行增量同步 — master作为一个普通的client连入slave,将所有写操作转发给slave,没有特殊的同步协议。具体过程如下:

        为什么在Master上新增的数据除了根据Master节点上RDB或者AOF的设置进行日志文件更新外,还会同时将数据变化写入一个环形内存结构(replication backlog),并以后者为依据进行Slave节点的增量更新呢?主要原因有以下几个:

  • 由于网络环境的不稳定,网络抖动/延迟都可能造成Slave和Master暂时断开连接,这种情况要远远多于新的Slave连接到Master的情况。如果以上所有情况都使用全量更新,就会大大增加Master的负载压力——写RDB文件是有大量I/O过程的,虽然Linux Page Cahe特性会减少性能消耗。

  • 另外在数据量达到一定规模的情况下,使用全量更新进行和Slave的第一次同步是一个不得已的选择——因为要尽快减少Slave节点和Master节点的数据差异。所以只能占用Master节点的资源和网络带宽资源。

  • 使用内存记录数据增量操作,可以有效减少Master节点在这方面付出的I/O代价。而做成环形内存的原因,是为了保证在满足数据记录需求的情况下尽可能减少内存的占用量。这个环形内存的大小,可以通过repl-backlog-size参数进行设置。

       Slave重连后会向Master发送之前接收到的Master replication id信息和上一次完成部分同步的offset的位置信息。如果Master能够确定这个replication id和自己的replication id(有两个)一致且能够在环形内存中找到这个offset的位置,Master就会发送从offset的位置开始向Slave发送增量数据。那么连接正常的各个Slave节点如何接受新数据呢?连接正常的Slave节点将会在Master节点将数据写入环形内存后,主动接收到来自Master的数据复制信息。

       这里就有一个问题了,我们的replication backlog的size设置为多大合适?

       redis为replication backlog设置的默认大小为1M(repl-backlog-size),但是这个值是可以调整的,如果主服务器需要执行大量的写命令,又或者主从服务器之间断线后重连的时间比较长,那么这个大小也许并不合适。如果replication backlog的大小设置不恰当,那么PSYNC命令的复制同步模式就不能正常发挥作用,因此,正确估算和设置replication backlog的size非常重要。

建议:repl-backlog-size​​​​​​​ = reconnect_time_second * write_size_per_second*2

  • reconnect_time_second : 重连时间,以秒未单位
  • write_size_per_second : 每秒写入的命令大小

4 主从复制的实现

下面这段是slava机器上的log

9322:S 22 Feb 21:58:19.363 * Connecting to MASTER 192.168.235.128:6379 
9322:S 22 Feb 21:58:19.365 * MASTER <-> SLAVE sync started                                           
9322:S 22 Feb 21:58:19.366 * Non blocking connect for SYNC fired the event.                          
9322:S 22 Feb 21:58:19.367 * Master replied to PING, replication can continue...                     
9322:S 22 Feb 21:58:19.369 * Trying a partial resynchronization (request ac9f4efccb61b7e1415c2dcef3cc9580cbab5a02:1).                                                                                     
9322:S 22 Feb 21:58:19.371 * Full resync from master: f6857e8cabdcfef6d8fc376013ec37c21c7bb0eb:23254 
9322:S 22 Feb 21:58:19.372 * Discarding previously cached master state.                              
9322:S 22 Feb 21:58:19.432 * MASTER <-> SLAVE sync: receiving 205 bytes from master                  
9322:S 22 Feb 21:58:19.435 * MASTER <-> SLAVE sync: Flushing old data                                
9322:S 22 Feb 21:58:19.436 * MASTER <-> SLAVE sync: Loading DB in memory                             
9322:S 22 Feb 21:58:19.437 * MASTER <-> SLAVE sync: Finished with success 

 首先,在redis内部,所有的命令都维护在一张表格里:

struct redisCommand redisCommandTable[] = {
    {"get",getCommand,2,"r",0,NULL,1,1,1,0,0},
    {"set",setCommand,-3,"wm",0,NULL,1,1,1,0,0},
    {"setnx",setnxCommand,3,"wm",0,NULL,1,1,1,0,0},
    {"setex",setexCommand,4,"wm",0,NULL,1,1,1,0,0},
 
    ........

    }

PS:当然所有的这些command,在启动的时候会调用populateCommandTable函数进行分析,然后把所有的redis command放在server.commands,它是一个字典。

dict commands; / Command table */

当我们在客户端执行slaveof 的,在客户端这一侧会调用replicaofCommand这个函数。

4.1 主从关系的建立

主从复制建立的方式有三种:

  • 在redis.conf文件中配置slaveof 选项,然后指定该配置文件启动Redis生效。
  • 在redis-server启动命令后加上--slaveof 启动生效。
  • 直接使用 slaveof 命令在从节点执行生效。

      无论是通过哪一种方式来建立主从复制,都是从节点来执行slaveof命令,那么从节点执行了这个命令到底做了什么,我们上源码伪码:

void replicaofCommand(client *c) {

 1、判断当前环境是否在集群模式下,因为集群模式下不行执行该命令。
 2、是否执行的是SLAVEOF NO ONE命令,该命令会断开主从的关系,设置当前节点为主节点服务器。
 3、设置从节点所属主节点的IP和port。调用了replicationSetMaster()函数。
}

其中replicationSetMaster()函数执行操作的也很简单,总结为两步:

  • 清理之前所属的主节点的信息。
  • 设置新的主节点IP和port等。 因为,当前从节点有可能之前从属于另外的一个主节点服务器,因此要清理所有关于之前主节点的缓存、关闭旧的连接等等。然后设置该从节点的新主节点,设置了IP和port,还设置了以下状态:

      slaveof命令是一个异步命令,执行命令时,从节点保存主节点的信息,确立主从关系后就会立即返回,后续的复制流程在节点内部异步执行。那么,如何触发复制的执行呢?

      周期性执行的函数:replicationCron()函数,该函数被服务器的时间事件的回调函数serverCron()所调用,而serverCron()函数在Redis服务器初始化时,被设置为时间事件的处理函数。

// void initServer(void) Redis服务器初始化
aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL)

replicationCron这个函数每秒才执行一次,以下代码执行到serverCron函数的实现:

    /* Replication cron function -- used to reconnect to master,
     * detect transfer failures, start background RDB transfers and so forth. */
    run_with_period(1000) replicationCron();

       主从关系建立后,从节点服务器的server.repl_state被设置为REPL_STATE_CONNECT,而replicationCron()函数会被每秒执行一次,该函数会发现我(从节点)现在有主节点了,而且我要的状态是要连接主节点(REPL_STATE_CONNECT)。

      replicationCron()函数处理这以情况的代码如下:

/* Check if we should connect to a MASTER */
// 如果处于要必须连接主节点的状态,尝试连接
if (server.repl_state == REPL_STATE_CONNECT) {
    serverLog(LL_NOTICE,"Connecting to MASTER %s:%d",
        server.masterhost, server.masterport);
    // 以非阻塞的方式连接主节点
    if (connectWithMaster() == C_OK) {
        serverLog(LL_NOTICE,"MASTER <-> SLAVE sync started");
    }
}

      replicationCron()函数根据从节点的状态,调用connectWithMaster()非阻塞连接主节点。connectWithMaster()函数执行的操作可以总结为:

       根据IP和port非阻塞的方式连接主节点,得到主从节点进行通信的文件描述符fd,并保存到从节点服务server.repl_transfer_s中,并且将刚才的REPL_STATE_CONNECT状态设置为REPL_STATE_CONNECTING。 监听fd的可读和可写事件,并且设置事件发生的处理程序syncWithMaster()函数。 至此,主从网络建立就完成了,逐步进入了协商交互阶段,使用的是状态机处理的,

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值