携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第12天,点击查看活动详情
要想设计一个高可用的redis服务,一定要从redis的多服务节点来考虑。比如redis 的主从复制、哨兵机制、切片集群
主从复制
主从复制的实现方案就是,将一台redis服务器,同步数据到多台redis服务器上,即一主多从模式,主从之间采用多是读写分离模式。
主服务器可以进行读写操作,当发生写操作,自动将写操作同步给服务器,而从服务器一般是只读到,并接受主服务器同步过来的写操作,然后执行。
所有的修改都在主服务器,然后将最新的数据同步给从服务器,这样就使得主从数据一致。
主从服务器之间的命令复制是异步的。
主服务器在收到写命令后,不会等从服务器执行完才返回结果给客户端,而是自己执行完就返回,如果从服务器还没有执行完,主从服务器之间的数据就不一致了 。而且,无法实现强一致性保证,数据不一致是难以避免的。
但是同步其实没有那么简单。
第一次同步
首先需要确认谁是主服务器,谁是从服务器。
relicaof在需要成为从服务器的服务器上执行此命令
# 服务器 B 执行这条命令
replicaof <服务器 A 的 IP 地址> <服务器 A 的 Redis 端口号>
然后b就成为了a的从服务器
然后进行第一次同步
同步的过程分为 3 个阶段:
- 建立连接、协商同步
- 主服务器同步数据给从服务器
- 主服务器发送新的写操作给从服务器
第一阶段:建立链接、协商同步
执行replicaof命令后,从服务器就会给主服务器发送psync命令,表示要进行数据同步。
psync命令包含两个参数: 主服务器的runID和复制进度offset
- runID:每个redis启动时都会产生一个随机的ID来唯一标识自己,第一次同步不知道runID,所以设置为?
- offset,表示复制的进度,第一次同步没有进度,所以标识为-1
主服务器收到psync后,返回fullsync,表示进行全量复制。并且记录runID和offset。第一阶段为了全量复制做好了准备。
第二阶段:主服务器同步数据给从服务器
主服务器会执行bgsave命令(非阻塞)来生成RDB文件,然后发送给从服务器。
从服务器收到RDB后,会清空当前数据,然后载入RDB文件。
但是,这期间的写操作命令,没有记录到RDB文件中,这时主从数据就不一致了。
为了解决这个问题,redis将主服务器在
- 生成RDB期间
- 发送RDB期间
- 从服务器加载RDB期间收到的写操作命令,写入到了replication buffer缓冲区。
第三阶段:主服务器发送新的写操作给从服务器
等从服务器加载完RDB后,主服务器将replication buffer缓冲区的写命令发送给从服务器,然后执行,主从数据就一致了。
到这里,第一次同步工作结束。
命令传播
主从服务器,在第一次同步之后双发会维护一个TCP连接。
后续主服务器,可以通过这个连接继续将写操作命令传播给从服务器,然后从服务执行保证数据一致。
该TCP连接是长连接,避免连接断开频繁所带来的性能开销。
这是基于长连接的命令传播,用于保证主从服务器的数据一致性。
分摊主服务器的压力
当从服务器数量太大,全部直接与主服务器进行全量同步的话,会导致两个问题:
- 因为是通过bgsave来生成RDB文件,所以主服务器会fork()子进程,如果服务器内存数据大,fork()会阻塞主线程
- 传输RDB文件会占用主服务带宽,所以过多从服务器的话会影响主服务器响应
就好像刚开始的创业公司人少老板可以直接领导下属,人多了之后需要经理人。
所以redis也一样,从服务可以拥有自己的从服务器。
主服务器生成RDB和传输RDB的压力可以分摊到经理的从服务器上。
同样在从服务器的从服务器上执行
replicaof <目标服务器的IP> 6379
成为目标服务器的从服务器。
增量复制
主从之间采用长连接进行命令传播。
当网络断开后,命令无法传播,数据可能就不一致了,
客户端可能会读到旧值。
如果网络恢复后,怎噩梦继续保持主从数据一致呢?
2.8版本以前,再进行一次全量复制,开销太大。
2.8以后,将采用增量复制的方式继续同步,也就是只会传播断连期间的写操作命令。
3个步骤:
- 在恢复网络后,会发送psync命令给主服务器,此时的psync命令里的offset参数不是-1。
- 主服务器收到命令后,用continue响应命令,告诉从服务器接下来使用增量复制的方式同步
- 然后主服务将断连期间的写命令发送给从服务器,执行。
主服务器怎么知道要将哪些增量数据发送给从服务器呢?
- repl_backlog_buffer 一个环形缓冲区,用于主从服务器断连后,从中找到差异的数据。
- replication offset标识上面缓冲区的同步进度,主从都有个自的偏移量,主服务使用master_repl_offset来记录写的位置,从服务器使用slave_repl_offset记录读的位置。
repl_backog_buffer什么时候写入呢?
主服务器命令传播时,不仅会发送写命令给从服务器,还会将写命令写入到repl_backlog_buffer缓冲区里,因此缓冲区会保存最近传播到写命令。
断连重连后,从服务会通过pysnc发送自己的复制偏移量slave_repl_offset,主服务器根据master_repl_offset和slave_repl_offset之间的差距决定执行增量同步还是全量同步:
- 如果要读取的数据还在缓冲区内,将采用增量同步
- 不在就是全量同步
如果采用增量,会将差异数据放入replication buffer缓冲区(缓存将要传播给从服务器的命令)
repl_backlog_buffer缓冲区的默认大小时1M,并且由于是一个环形缓冲区,所以当缓冲区写满后,主服务器继续写入的话,就会覆盖之前的数据。
如果主服务器的写入速度远超从服务器的读速度,缓存区会被快速覆盖。那么重连后,就会采用全量同步损耗很大。
为了避免频繁全量同步,减少被覆盖的几率
repl_backlog_buffer应该多大?
推算
second * write_size_per_second
服务器断线后重新连接上主服务器所需的平均时间(秒)
主服务器平均每秒产生的写命令的数据量大小
为了应对突发状况,设置为上面的两倍
通过此参数 repl-backlog-size 1mb
总结
主从复制有三种模式:
全量复制、基于长连接的命令传播、增量复制。
第一次同步,采用全量复制。
然后维护一个长连接TCP,进行命令传播保证数据一致性
如果