前言
明天持续来看看无关 Redis 的一个问题,主从复制。通常,对于大多数的场景来说,读比写更多,于是对于缓存的程度扩大,其中的一个形式“主从复制”就是一个常见的思路。有了主从复制,那么能够扩大出很多从节点来应答大量的读申请。那么问题来了 Redis 的主从复制是如何实现的呢?
PS:本文仅关怀复制的机制,不关怀主节点下线从新选等等异常情况
前置常识
- 你须要晓得 Redis 的长久化形式,RDB 和 AOF
- Redis 执行命令的基本思路
审题
题目自身不简单,提问者问这个问题的想法可能会有上面几个方面
- 理解 Redis 的主从复制机制的话,如果在理论应用过程中呈现问题就更容易排查
- 在设计复制机制的时候须要留神和思考什么问题
- 这样的设计是否能利用在别的场景中
尝试思考
假如你齐全没有看过 Redis 源码来思考这个问题,能够从上面几个角度去尝试剖析,并猜想答案。
- 首先,想到一个关系户,也就是咱们罕用的 Mysql,它也有主从复制,如果你理解 binlog 那么能够尝试从这里着手,尽管不同,但思路应该是差不多的。
- 而后,简化问题,主从复制,无非就是将数据发送过来,对方承受保留
- 不可能每次都复制的是全量数据,那么必定须要有机制去确保如何每次复制增量的数据
-
复制的是什么?
- 复制的是数据自身?数据只有变动就将变动的 kv 间接扔给从节点?
- 复制的是执行命令?将客户端执行的命令发送给子节点执行一次?
解决
有了下面的思考,其实理论也就有思路的。首先主从复制必定有两种状况,一种就是第一次复制,也就是要执行一次全量复制,将主节点的所有数据到复制到从节点下来;另一种就是增量复制,在数据同步之后后续的增量数据放弃同步。
全量同步
长久化数据
因为须要全量同步所有数据,咱们晓得 Redis 数据在内存外面,既然要发送,那势必须要先长久化一次。也就是先 SYNC 一遍,通过办法 startBgsaveForReplication
来实现的
代码地位在:https://github.com/redis/redis/blob/14f802b360ef52141c83d477ac626cc6622e4eda/src/replication.c#L855
这个问题不大,就是保留一个 RDB 文件。
发送数据
这个也很不难,就是将数据间接扔过来就好了。
代码地位在:https://github.com/redis/redis/blob/14f802b360ef52141c83d477ac626cc6622e4eda/src/replication.c#L1402
增量同步
后续的工作就是增量同步后续产生的数据了。在猜想时咱们想到有两种复制形式,一种是间接复制数据,这种形式复制 RDB 是可行,在全量同步的时候用这个必定更好,如果同步命令那么从节点还需再执行一次过于简单和麻烦,还耗时。而对于后续的增量同步来说,必定是同步命令来的更高效(不过还是得看理论)。
上面就是流传命令的办法:
/* Propagate the specified command (in the context of the specified database id)
* to AOF and Slaves.
*
* flags are an xor between:
* + PROPAGATE_NONE (no propagation of command at all)
* + PROPAGATE_AOF (propagate into the AOF file if is enabled)
* + PROPAGATE_REPL (propagate into the replication link)
*
* This is an internal low-level function and should not be called!
*
* The API for propagating commands is alsoPropagate().
*
* dbid value of -1 is saved to indicate that the called do not want
* to replicate SELECT for this command (used for database neutral commands).
*/
static void propagateNow(int dbid, robj **argv, int argc, int target) {if (!shouldPropagate(target))
return;
/* This needs to be unreachable since the dataset should be fixed during
* replica pause (otherwise data may be lost during a failover) */
serverAssert(!(isPausedActions(PAUSE_ACTION_REPLICA) &&
(!server.client_pause_in_transaction)));
if (server.aof_state != AOF_OFF && target & PROPAGATE_AOF)
feedAppendOnlyFile(dbid,argv,argc);
if (target & PROPAGATE_REPL)
replicationFeedSlaves(server.slaves,dbid,argv,argc);
}
这个办法就是将增量命令流传给 AOF 和 Slaves,AOF 就是长久化的另一种形式,而 Slaves 就是咱们须要同步的从节点了。具体 replicationFeedSlaves
办法就不具体看了。
监控状态
这个其实是咱们在猜想的时候漏掉的,想来也是,master 必定须要晓得 slave 的状态,如果连不上了,必定要解决,在 replication.c 中有这样一个办法
/* Replication cron function, called 1 time per second. */
void replicationCron(void) {
看名字和正文就秒懂了,每秒执行一次的同步定时工作。
而其中调用了 replicationFeedSlaves
办法,也就是 PING 一下,看看活着没
replicationFeedSlaves(server.slaves, -1, ping_argv, 1);
可能导致的问题
第一次同步 RDB 工夫太长?
如果咱们 redis 寄存的数据很多,第一次同步会有两个工夫,一个是 bgsave 的工夫,这个工夫其实还好,毕竟平时就是要执行的,而第二个工夫就是传输数据的工夫,这个工夫就取决于带宽了。
不过首先这个操作时,主节点仍旧能够被读写,只不过操作均被缓存了,所以倒是不用放心这段时间无奈被应用。难就在如果数据过多可能真的会导致一个问题就是,同步 -> 超时 -> 重试,而后一直循环,所以为了防止这样的状况呈现,倡议 Redis 返回别间接把主机全副内存吃完。通常 maxmemory 设置为 75% 就绝对不会呈现问题,也不容易 OOM。
当然,有人必定会问,能不能间接先手动拷贝 RDB 文件来缩小同步工夫,实际操作过我通知你,不要手动操作,容易呈现意想不到的问题,当呈现问题之后,数据还是会不同步,还是会执行从新同步,还不如第一次就手动让程序本人来。
优化
流传 cache
命令在流传的阶段设置了主从同步发送的缓冲区,通过保护一个缓冲区来保障当主节点无需期待,从节点本人凭实力拿就好了,即便有一段时间忽然抖动了一下,也没事,缓冲区外面还有,持续同步就行嘞。但当齐全超过缓冲区的接受范畴,那么还是须要执行一次全量同步来保证数据统一。
无盘加载
之前看代码的时候就留神到了一个参数 repl_diskless_sync
翻译过去就是无盘同步,显然这个优化是 Redis 留神到第一次同步的时候,如果马上写入 RDB 显然是有点慢了,间接 dump 内存必定会来的更快,所以这就是无盘,也就是不先落盘。
总结
最初用一张图来总结整个过程
咱们看着这个图咱们也能够想到,其实这样复制的策略在绝大多数复制的场景中都是实用的,如果理论没有命令这个说法,那就将数据拆分成小块 (chunk) 来同步。须要留神点和优化点可能 Redis 都帮你想好了,对着抄就能够了。所以,我称为一种设计为”单向同步“,那么如果什么是多向同步呢?也就是多集体同时编辑或操作数据,相互同步的策略,此时就须要一些 diff 算法和策略了,你也能够思考设计看看,看具体会遇到什么问题。
参考链接
- https://redis.io/docs/management/replication/
- https://redis-doc-test.readthedocs.io/en/latest/topics/replic…
- https://developer.baidu.com/article/detail.html?id=294748
- http://machicao2013.github.io/gitbook/redis/replication/princ…
- https://segmentfault.com/a/1190000040248346
- https://www.xiaolincoding.com/redis/cluster/master_slave_repl…