深入理解Redis集群主从复制原理

「这是我参与2022首次更文挑战的第4天,活动详情查看:2022首次更文挑战」。

一、首先思考一个问题,为什么redis性能这么高还需要分布式方案?

1、实现更高性能:高并发应用,单机性能会有影响,需要更多redis服务器分担压力,实现负载均衡

2、实现高可用:如果单机,防止宕机/硬件故障

3、实现可扩展:单机内存和硬件有限制,实现横向扩展

冗余或者分片存储实现如上特性。

二、主从复制-replication配置

和Kafka,Mysql,Rocketmq一样,redis支持集群部署,集群节点有master和slave之分,主节点是master,从节点是slave(最新叫副本replica).slave会通过复制机制,从master同步最新的数据。Redis提供了非常方便的命令开启主从​复制。

如何配置开启主从复制?

以本机搭建伪集群为例,6379端口是从节点,6378作为主节点。

1、从节点redis.conf配置 replicaof masterip masterport 从节点启动后,自动连接到master节点,开始同步数据.

如果换了新的master节点,这个配置会被重写。

2、或者在redis-server程序启动时候指定

./redis-server --replicaof masterip masterport
复制代码

3、或者登录客户端,执行如下命令

slaveof masterip masterport
复制代码

注意这种方式是运行过程中修改,可以实现故障转移

注意: 一个从节点也可以是其他节点的主节点,形成级联复制的关系。但是其他节点也是从顶层主节点同步数据。

配置好集群后,通过info replication查看集群状态

通过role命令,可以查看节点在集群中的角色信息

注意从节点是只读的。写命令会报错。 

slave如何退出集群? 可以执行如下命令: 

slaveof no one
复制代码

三、主从复制的流程

1、首先是副本-replica加入集群 

2、与master建立连接,通过定时器定时检查是否要从主节点同步数据

​源码说明:​

//每1s执行这个方法void replicationCron(void) {    ...    //检查是否需要连接到master 如果是REPL_STATE_CONNECT状态,必须连接到master    //#define REPL_STATE_CONNECT 1  Must connect to master     if (server.repl_state == REPL_STATE_CONNECT) {        serverLog(LL_NOTICE,"Connecting to MASTER %s:%d",            server.masterhost, server.masterport);        //和master创建连接            if (connectWithMaster() == C_OK) {            serverLog(LL_NOTICE,"MASTER <-> REPLICA sync started");        }    }        //发送ping命令给slave     if ((replication_cron_loops % server.repl_ping_slave_period) == 0 &&        listLength(server.slaves))    {        /* Note that we don't send the PING if the clients are paused during         * a Redis Cluster manual failover: the PING we send will otherwise         * alter the replication offsets of master and slave, and will no longer         * match the one stored into 'mf_master_offset' state. */        int manual_failover_in_progress =            server.cluster_enabled &&            server.cluster->mf_end &&            clientsArePaused();​        if (!manual_failover_in_progress) {            ping_argv[0] = createStringObject("PING",4);            replicationFeedSlaves(server.slaves, server.slaveseldb,                ping_argv, 1);            decrRefCount(ping_argv[0]);        }    }        //发送换行符到所有slave,告诉slave等待接收rdb文件    listRewind(server.slaves,&li);    while((ln = listNext(&li))) {        client *slave = ln->value;​        int is_presync =            (slave->replstate == SLAVE_STATE_WAIT_BGSAVE_START ||            (slave->replstate == SLAVE_STATE_WAIT_BGSAVE_END &&             server.rdb_child_type != RDB_CHILD_TYPE_SOCKET));​        if (is_presync) {            if (write(slave->fd, "\n", 1) == -1) {                /* Don't worry about socket errors, it's just a ping. */            }        }    }    ...}
复制代码

3、全量复制流程-支持无盘复制或者rdb持久化复制 

当slave连接到master后,使用psync(以前是sync命令,它不允许部分重新同步,所以现在改用PSYNC)命令初始化复制,将主节点replication id和处理过最大offset发送到master。

master节点拥有如下两个属性,一个replication id(标志实例),一个offset(标志写入从节点的stream) 

Replication ID, offset
复制代码

如果主节点缓冲区中没有足够的积压工作,或者如果复制副本引用的是不再已知的历史记录(复制ID),则会发生完全重新同步 

​源码说明:​

    //没有在rdb进程,没有aof重写进程    if (server.rdb_child_pid == -1 && server.aof_child_pid == -1) {        time_t idle, max_idle = 0;        int slaves_waiting = 0;        int mincapa = -1;        listNode *ln;        listIter li;​        listRewind(server.slaves,&li);        while((ln = listNext(&li))) {            client *slave = ln->value;            //判断slave是否是等待bgsave状态            if (slave->replstate == SLAVE_STATE_WAIT_BGSAVE_START) {            //多久没有发送心跳或查询数据了 空闲时间间隔                idle = server.unixtime - slave->lastinteraction;                if (idle > max_idle) max_idle = idle;                slaves_waiting++;                mincapa = (mincapa == -1) ? slave->slave_capa :                                            (mincapa & slave->slave_capa);            }        }​        if (slaves_waiting &&            (!server.repl_diskless_sync ||             max_idle > server.repl_diskless_sync_delay))        {            /* Start the BGSAVE. The called function may start a             * BGSAVE with socket target or disk target depending on the             * configuration and slaves capabilities. */             //bgsave rdb生成            startBgsaveForReplication(mincapa);        }    }
复制代码

复制过程中,slave状态转换流程。

4、​命令传播阶段,执行完全量同步后,主从会进行命令传播​实现数据一致。

四、复制id理解

每次实例作为主实例从头开始重新启动,或者将复制副本提升为主实例,都会为此实例生成一个新的复制ID。如果两个replica的复制id相同,则他们可能在不同的时间,有相同的数据,对于保存最新数据集的给定历史记录(复制ID),偏移量作为一个逻辑时间来理解。需要通过Replication ID, offset两个数据来判断。用来判断从节点同步数据到哪了​。

五、主从复制常见问题

1、slave本身有数据,会怎么样?

slave先删除自身的数据,再用rdb文件加载。

2、生成rdb文件的过程中,客户端写命令怎么处理?

保存到内存缓存中,rdb发送完成后发送到slave。 

3、Redis复制如何处理key过期的? 

1、副本不会使key过期,而是等待主机使key过期。当主机使key过期(或由于LRU而将其逐出)时,它将合成一个DEL命令,该命令将传输到所有副本。

2、但是,由于主机驱动的expire,有时副本可能仍然具有逻辑上已过期的内存密钥,因为主服务器无法及时提供DEL命令。为了处理这个问题,副本使用它的逻辑时钟来报告一个key不存在,只用于不违反数据集一致性的读取操作(因为来自主服务器的新命令将到达)

3、在Lua脚本执行期间,不执行密钥过期。当Lua脚本运行时,从概念上讲,主节点中的时间是冻结的,因此给定的键在脚本运行的所有时间内都将存在或不存在。这可以防止key在脚本中间过期,并且需要key才能以保证在数据集中具有相同效果的方式将相同的脚本发送到副本。

一旦复制副本升级为主副本,它将开始独立地使key过期,并且不需要旧主副本的任何帮助。 

六、主从复制总结

1、解决了数据备份的问题,但是rdb文件大,传输大文件,恢复时间也长

2、如果master异常,需要手工将replica选举为master

3、1主多从,1主1从的情况下,还是存在单点问题 

4、Redis版本2.8.18​后支持无盘复制,性能更高。

七、复制说明

1、默认用异步复制,通过异步确认同步的命令数量

2、1个master可以有多个副本

3、副本也可以有自己的副本,从redis4.0开始,副本都会从主节点接收完全相同的复制流

4、复制既可以用于可扩展性,也可以用于只读查询的多个副本​

Guess you like

Origin juejin.im/post/7055324634090045454