为什么Redis主从模式能保持数据一致

125 阅读4分钟

为什么Redis主从模式能保持数据一致。想要知道答案,我们得深入分析Redis实例之间如何进行数据同步。

概述

在具体分析今天的问题之前,我们需要了解 Redis 具有高可靠性,又是什么意思呢?其实,这里有两层含义:一是数据尽量少丢失,二是服务尽量少中断。AOF 和 RDB 保证了前者,而对于后者,Redis 的做法就是增加副本冗余量,将一份数据同时保存在多个实例上。即使有一个实例出现了故障,需要过一段时间才能恢复,其他实例也可以对外提供服务,不会影响业务使用。多实例保存同一份数据,听起来好像很不错,但是,我们必须要考虑一个问题:这么多副本,它们之间的数据如何保持一致呢?数据读写操作可以发给所有的实例吗?实际上,Redis 提供了主从库模式,以保证数据副本的一致,主从库之间采用的是读写分离的方式。

1)读操作:主库、从库都可以接收;

2)写操作:首先到主库执行,然后,主库将写操作同步给从库。

到了这里我们发现几个问题:

1)主从库为什么要采用读写分离的方式;

2)主库如何把数据同步给从库;

3)主从库之前网络断了怎么办;

不过既然Redis具有高可用性,说明这些问题都已经有了答案。

设计

主从库为什么要采用读写分离的方式

你可以设想一下,如果不管是主库还是从库,都能接收客户端的写操作,那么,一个直接的问题就是:如果客户端对同一个数据(例如 k1)前后修改了三次,每一次的修改请求都发送到不同的实例上,在不同的实例上执行,那么,这个数据在这三个实例上的副本就不一致了(分别是 v1、v2 和 v3)。在读取这个数据的时候,就可能读取到旧的值。

如果我们非要保持这个数据在三个实例上一致,就要涉及到加锁、实例间协商是否完成修改等一系列操作,但这会带来巨额的开销,当然是不太能接受的。

主库如何把数据同步给从库

当我们启动多个 Redis 实例的时候,它们相互之间就可以通过 replicaof(Redis 5.0 之前使用 slaveof)命令形成主库和从库的关系,之后会按照三个阶段完成数据的第一次同步。

数据同步

第一阶段

建立连接,协商同步,psync(runId=?,offest=-1),此时主库会调用FULLRESYNC进行同步。

第二阶段

主库执行 bgsave 命令,生成 RDB 快照文件,接着将文件发给从库。从库接收到 RDB 文件后,会先清空当前数据库,然后加载 RDB 文件。这是因为从库在通过 replicaof 命令开始和主库同步前,可能保存了其他数据。为了避免之前数据的影响,从库需要先把当前数据库清空。可能会有人质疑,主库生成RDB快照文件,不会阻塞主库的读写操作吗?会不会有额外的性能开销?那这些问题我们今天就不展开讨论了,感兴趣的同学可以读下源码。

rdbSaveBackground就是用来处理在后台将数据保存到磁盘上的函数:

int rdbSaveBackground(char *filename, rdbSaveInfo *rsi) {
    pid_t childpid;
​
    if (hasActiveChildProcess()) return C_ERR;
    ...
​
    if ((childpid = redisFork()) == 0) {
        int retval;
​
        /* Child */
        redisSetProcTitle("redis-rdb-bgsave");
        retval = rdbSave(filename,rsi);
        if (retval == C_OK) {
            sendChildCOWInfo(CHILD_INFO_TYPE_RDB, "RDB");
        }
        exitFromChild((retval == C_OK) ? 0 : 1);
    } else {
        /* Parent */
        ...
    }
    ...
}

第三阶段

增量复制

repl_backlog_buffer

最后,也就是第三个阶段,主库会把第二阶段执行过程中新收到的写命令,再发送给从库。具体的操作是,当主库完成 RDB 文件发送后,就会把此时 replication buffer 中的修改操作发给从库,从库再重新执行这些操作。这样一来,主从库就实现同步了。

主从库如果断开连接了,下次建立连接时会通过repl_backlog_buffer环形缓冲区进行增量复制,psync(runId=1,offest=100),offest=100用于标记从库在`repl_backlog_buffer`中的位置。

总结

为什么Redis主从模式能保持数据一致?

  • 采用读写分离,避免加锁、实例间协商是否完成修改等操作,减少不必要的性能损耗;

  • 主从实例间通过RDB快照进行数据同步,同步期间主库的写操作额外记录一份到replication buffer中,同步完成时,发送给从库,从库再重新执行这些操作。

  • 后续的数据同步通过repl__backlog__buffrt来标记主从实例环形缓冲区中的位置,从库执行这些操作。

参考资料

[^1]time.geekbang.org/column/arti…