目录
1、秃顶面试官:今天我们聊了聊redis的主从模式啊~
2、秃顶面试官:Redis有哪几种方式进行数据的持久化?
3、秃顶面试官:RDB持久化是什么呢?触发机制又是什么呢?
4、秃顶面试官:嗯,你上面提到了fork,能简单说下吗?
5、秃顶面试官:嗯,不错,RDB持久化的流程是如何的呢?
6、秃顶面试官:那当数据量很大时,RDB文件会不会占用很大磁盘空间呢?
7、秃顶面试官:那RDB持久化方式有什么优缺点呢?
8、秃顶面试官:嗯,说的不错,那AOF持久化又是什么呢?
9、秃顶面试官:如何开启AOF呢?
10、秃顶面试官:AOF的工作流程是怎样的呢?
11、秃顶面试官:可以详细说说aof工作流程每一步的具体细节吗?
12、秃顶面试官:如果AOF文件在写的时候,机器突然掉电导致AOF文件命令写入不全,怎么办?
13、秃顶面试官:主从模式如何搭建呢?
14、秃顶面试官:嗯,可以,那从节点可以写数据吗?
15、秃顶面试官:主的数据会立刻同步到从吗?
16、秃顶面试官:嗯,挺好,来说下主从复制的过程吧
17、秃顶面试官:嗯,说下全量同步和部分同步呢
18、秃顶面试官:嗯,部分同步是如何实现的呢,有可观测数据吗?
19、秃顶面试官:嗯,不错,那全量复制呢?
秃顶面试官:嗯,那如果RDB文件过大,会怎么样?
20、秃顶面试官:那如果在全量复制的过程中,有读请求来了,会怎样?
21、秃顶面试官:不错不错,那在主从数据复制完成之后,主接到了一个写命令,这个改动是如何同步到从的呢?
前言:本文redis基于6.2.4
1、秃顶面试官:今天我们聊了聊redis的主从模式啊~
花花:阔以的阔以的啊
秃顶面试官:……
花花:????
秃顶面试官:…………你说啊
花花:额……这就开始了吗,(好吧,来嘛…….^.^)
我们知道在分布式系统中为了解决单点问题,通常会把数据复制多个副本部署到其他机器,满足故障恢复和负载均衡等需求。Redis也是如此,它为我们提供了复制功能,实现了相同数据的多个Redis副本。复制功能是高可用Redis的基础,主从、哨兵和集群都是在复制的基础上实现高可用的。而复制功能是基于RDB文件来的,RDB则是redis默认的开启持久化机制!
秃顶面试官:等下,不好意思打断一下哈
2、秃顶面试官:Redis有哪几种方式进行数据的持久化?
花花:哎,Redis支持RDB和AOF两种持久化机制,持久化功能有效地避免因进程退出造成的数据丢失问题,当下次重启时利用之前持久化的文件即可实现数据恢复。
3、秃顶面试官:RDB持久化是什么呢?触发机制又是什么呢?
AOF(append only file)持久化:以独立日志的方式记录每次写命令,
重启时再重新执行AOF文件中的命令达到恢复数据的目的。AOF的主要作用
是解决了数据持久化的实时性,目前已经是Redis持久化的主流方式RDB持久化是把当前进程的数据生成快照保存到硬盘的过程。
触发RDB持久化过程分为手动触发和自动触发。
手动触发分别对应save和bgsave命令:
- save命令:阻塞当前Redis服务器,直到RDB过程完成为止,对于内存比较大的实例会造成长时间阻塞,线上环境禁止使用。
- bgsave命令:Redis进程执行fork操作创建子进程,RDB持久化过程由子进程负责,完成后自动结束。阻塞只发生在fork阶段,一般时间很短。
除了执行命令手动触发之外,Redis内部还存在自动触发RDB的持久化机制,例如以下场景:
1)使用save相关配置,如“save m n”。表示m秒内数据集存在n次修改
时,自动触发bgsave。
2)如果从节点执行全量复制操作,主节点自动执行bgsave生成RDB文件并发送给从节点。
3)执行debug reload命令重新加载Redis时,也会自动触发save操作。
4)默认情况下执行shutdown命令时,如果没有开启AOF持久化功能则自动执行bgsave。
4、秃顶面试官:嗯,你上面提到了fork,能简单说下吗?
花花:嗯~ 简单来说就是fork是linux操作系统内置的一个重要的函数,fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程(包括代码、数据和分配给进程的资源),也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事。它的作用是从已经存在的进程中创建一个子进程,而原进程称为父进程。
5、秃顶面试官:嗯,不错,RDB持久化的流程是如何的呢?
花花:来,请看图
1)执行bgsave命令,Redis父进程判断当前是否存在正在执行的子进程,如RDB/AOF子进程,如果存在bgsave命令直接返回。
2)父进程执行fork操作创建子进程,fork操作过程中父进程会阻塞,通过info stats命令查看latest_fork_usec选项,可以获取最近一个fork操作的耗时,单位为微秒。
PS: 对于高流量的Redis实例OPS可达5万以上,如果fork操作耗时在秒级别将拖慢Redis几万条命令执行,对线上应用延迟影响非常明显。正常情况下fork耗时应该是每GB消耗20毫秒左右。
3)父进程fork完成后,bgsave命令返回“Background saving started”信息并不再阻塞父进程,可以继续响应其他命令。
4)子进程继续创建RDB文件,根据父进程的内存生成临时快照文件,完成后对原有文件进行原子替换。执行lastsave命令可以获取最后一次生成RDB的时间,对应info统计的rdb_last_save_time选项。
5)进程发送信号给父进程表示完成,父进程更新统计信息,具体见info Persistence( 见 4)中的右边的图)下的rdb_*相关选项。
PS: 保存:RDB文件保存在dir配置指定的目录下,文件名通过dbfilename配置指定。可以通过执行config set dir{newDir}和config set dbfilename{newFileName}运行期动态执行,当下次运行时RDB文件会保存到新目录。save配置,redis.conf中是注释的,但是默认值就是
save 3600 1
save 300 100
save 60 10000
(秃顶面试官:这小伙子可以啊,讲得如此详细)
6、秃顶面试官:那当数据量很大时,RDB文件会不会占用很大磁盘空间呢?
花花:Redis默认采用LZF算法对生成的RDB文件做压缩处理,压缩后的文件远远小于内存大小,默认开启,可以通过参数config set rdbcompression {yes|no} 动态修改。虽然压缩RDB会消耗CPU,但可大幅降低文件的体积,方便保存到硬盘或通过网络发送给从节点,因此线上建议开启。
如果Redis加载损坏的RDB文件时拒绝启动,并打印如下日志:
# Short read or OOM loading DB. Unrecoverable error, aborting now.
这时可以使用Redis提供的redis-check-dump工具检测RDB文件并获取对应的错误报告。
7、秃顶面试官:那RDB持久化方式有什么优缺点呢?
花花:我喝口水先
RDB的优点:
- RDB是一个紧凑压缩的二进制文件,代表Redis在某个时间点上的数据快照。非常适用于备份,全量复制等场景。比如每6小时执行bgsave备份,并把RDB文件拷贝到远程机器或者文件系统中(如hdfs),用于灾难恢复。
- Redis加载RDB恢复数据远远快于AOF的方式。
RDB的缺点:
- RDB方式数据没办法做到实时持久化/秒级持久化。因为bgsave每次运行都要执行fork操作创建子进程,属于重量级操作,频繁执行成本过高。
- RDB文件使用特定二进制格式保存,Redis版本演进过程中有多个格式的RDB版本,存在老版本Redis服务无法兼容新版RDB格式的问题。针对RDB不适合实时持久化的问题,Redis提供了AOF持久化方式来解决。
8、秃顶面试官:嗯,说的不错,那AOF持久化又是什么呢?
花花:AOF(append only file)持久化是以独立日志的方式记录每次写命令,重启时再重新执行AOF文件中的命令达到恢复数据的目的。AOF的主要作用是解决了数据持久化的实时性,目前已经是Redis持久化的主流方式。
9、秃顶面试官:如何开启AOF呢?
花花:开启AOF功能需要设置配置:appendonly yes,默认不开启。AOF文件名通过appendfilename配置设置,默认文件名是appendonly.aof。保存路径同RDB持久化方式一致,通过dir配置指定。
10、秃顶面试官:AOF的工作流程是怎样的呢?
花花:AOF的工作流程操作:命令写入(append)、文件同步(sync)、文件重写(rewrite)、重启加载(load),如下图所示。
1)所有的写入命令会追加到aof_buf(缓冲区)中。
2)AOF缓冲区根据对应的策略向硬盘做同步操作。
3)随着AOF文件越来越大,需要定期对AOF文件进行重写,达到压缩的目的。
4)当Redis服务器重启时,可以加载AOF文件进行数据恢复。
秃顶面试官:小伙子很可以~
11、秃顶面试官:可以详细说说aof工作流程每一步的具体细节吗?
(花花:这是来找茬的吧,多亏我早有准备)
花花:
1)命令写入:AOF命令写入的内容直接是文本协议格式。例如set hello world这条命
令,在AOF缓冲区会追加如下文本:
*3\r\n$3\r\nset\r\n$5\r\nhello\r\n$5\r\nworld\r\n
*3表示本次命令有三个参数:
$3表示下一个参数长度为3,即 set
$5表示下一个参数长度为5,即hello
$5表示下一个参数长度为5,即world
2) 文件同步:Redis提供了多种AOF缓冲区同步文件策略,由参数appendfsync控制,
系统调用write和fsync说明:
- write操作会触发延迟写(delayed write)机制。Linux在内核提供页缓冲区用来提高硬盘IO性能。write操作在写入系统缓冲区后直接返回。同步硬盘操作依赖于系统调度机制,例如:缓冲区页空间写满或达到特定时间周期。同步文件之前,如果此时系统故障宕机,缓冲区内数据将丢失。
- fsync针对单个文件操作(比如AOF文件),做强制硬盘同步,fsync将阻塞直到写入硬盘完成后返回,保证了数据持久化。
3)重写机制:随着命令不断写入AOF,文件会越来越大,为了解决这个问题,Redis
引入AOF重写机制压缩文件体积。AOF文件重写是把Redis进程内的数据转化为写命令同步到新AOF文件的过程。
秃顶面试官:打断一下哈,重写后的AOF文件为什么可以变小?
花花:有如下原因:
1)进程内已经超时的数据不再写入文件。
2)旧的AOF文件含有无效命令,如del key1、hdel key2、srem keys、set a111、set a222等。重写使用进程内数据直接生成,这样新的AOF文件只保留最终数据的写入命令。
3)多条写命令可以合并为一个,如:lpush list a、lpush list b、lpush list c可以转化为:lpush list a b c。为了防止单条命令过大造成客户端缓冲区溢出,对于list、set、hash、zset等类型操作,以64个元素为界拆分为多条。AOF重写降低了文件占用空间,除此之外,另一个目的是:更小的AOF文件可以更快地被Redis加载。
AOF重写过程可以手动触发和自动触发:
- 手动触发:直接调用bgrewriteaof命令。
- 自动触发:根据auto-aof-rewrite-min-size和auto-aof-rewrite-percentage参数确定自动触发时机。
·auto-aof-rewrite-min-size:表示运行AOF重写时文件最小体积,默认为64MB。
·auto-aof-rewrite-percentage:代表当前AOF文件空间(aof_current_size)和上一次重写后AOF文件空间(aof_base_size)的比值。
自动触发时机=aof_current_size>auto-aof-rewrite-min-size&&(aof_current_size-aof_base_size)/aof_base_size>=auto-aof-rewrite-percentage
其中aof_current_size和aof_base_size可以在info Persistence统计信息中查看。
4)重启加载
AOF和RDB文件都可以用于服务器重启时的数据恢复,如果开启了AOF且有AOF持久化文件,则加载AOF持久化文件
12、秃顶面试官:如果AOF文件在写的时候,机器突然掉电导致AOF文件命令写入不全,怎么办?
花花:Redis为我们提供了aof-load-truncated配置来兼容这种情况,默认开启。加载AOF时,当遇到此问题时会忽略并继续启动。其他的对于错误格式的AOF文件,先进行备份,然后采用redis-check-aof --fix命令进行修复,修复后使用diff-u对比数据的差异,找出丢失的数据,有些可以人工修改补全。
秃顶面试官:不错不错,持久化就说到这吧,说说主从吧
13、秃顶面试官:主从模式如何搭建呢?
花花:主从模式的搭建非常简单,比如我现在要搭建一个一主(6381)两从(6379,6380),先起一个主实例,在启动从实例的时候可以通过如下三种方式来搭建主从复制:
1)在从实例的配置文件中加入replicaof {masterHost} {masterPort} 随Redis启动生效。
2)在redis-server启动命令后加入--replicaof {masterHost} {masterPort}生效。
3)直接使用命令:replicaof {masterHost} {masterPort}生效
PS:对于数据比较重要的节点,主节点会通过设置requirepass参数进行密码验证,这时所有的客户端访问必须使用auth命令实行校验。从节点与主节点的复制连接是通过一个特殊标识的客户端来完成,因此需要配置从节点的masterauth参数与主节点密码保持一致,这样从节点才可以正确地连接到主节点并发起复制流程。
都超过启动成功后,查看下状态 :
主:
从:
秃顶面试官:确实很简单,如果我想让从提升为主,如何操作
花花:replicaof命令不但可以建立复制,还可以在从节点执行replicaof no one来断开与主节点复制关系。断开复制主要流程:
1)断开与主节点复制关系。
2)从节点晋升为主节点。
从节点断开复制后并不会抛弃原有数据,只是无法再获取主节点上的数据变化。
通过replicaof命令还可以实现切主操作,所谓切主是指把当前从节点对主节点的复制切换到另一个主节点。执行replicaof {newMasterIp} {newMasterPort}命令即可
切主操作流程如下:
1)断开与旧主节点复制关系。
2)与新主节点建立复制关系。
3)删除从节点当前所有数据。
4)对新主节点进行复制操作。
14、秃顶面试官:嗯,可以,那从节点可以写数据吗?
花花:从节点是可以写数据的,但是呢,由于复制只能从主节点到从节点,对于从节点的任何修改主节点都无法感知,修改从节点会造成主从数据不一致,因此,一般都给从节点配置上
replica-read-only yes
15、秃顶面试官:主的数据会立刻同步到从吗?
花花:主从节点一般部署在不同机器上,复制时的网络延迟就成为需要考虑的问题,Redis为我们提供了repl-disable-tcp-nodelay参数用于控制是否关闭TCP_NODELAY,默认关闭,说明如下:
- 当关闭时,主节点产生的命令数据无论大小都会及时地发送给从节点,这样主从之间延迟会变小,但增加了网络带宽的消耗。适用于主从之间的网络环境良好的场景,如同机架或同机房部署。
- 当开启时,主节点会合并较小的TCP数据包从而节省带宽。默认发送时间间隔取决于Linux的内核,一般默认为40毫秒。这种配置节省了带宽但增大主从之间的延迟。适用于主从网络环境复杂或带宽紧张的场景,如跨机房部署。
PS:部署主从节点时需要考虑网络延迟、带宽使用率、防灾级别等因素,如要求低延迟时,建议同机架或同机房部署并关闭repl-disable-tcp-nodelay;如果考虑高容灾性,可以同城跨机房部署并开启repl-disable-tcp-nodelay
16、秃顶面试官:嗯,挺好,来说下主从复制的过程吧
(花花:啊~终于到这里了,这才是这次的重点啊)
花花:好的呢,主从复制过程大致分为6个过程:
1)保存主节点(master)信息。执行replicaof后从节点只保存主节点的地址信息便直接返回,这时建立复制流程还没有开始,在从节点6379执行info replication可以看到如下信息:
2)从节点(slave)内部通过每秒运行的定时任务维护复制相关逻辑,当定时任务发现存在新的主节点后,会尝试与该节点建立网络连接,从节点会建立一个socket套接字,专门用于接受主节点发送的复制命令。如果从节点无法建立连接,定时任务会无限重试直到连接成功或者执行
replicaof no one取消复制
3)发送ping命令。
连接建立成功后从节点发送ping请求进行首次通信,ping请求主要目的如下:
- 检测主从之间网络套接字是否可用。
- 检测主节点当前是否可接受处理命令。
如果发送ping命令后,从节点没有收到主节点的pong回复或者超时,比如网络超时或者主节点正在阻塞无法响应命令,从节点会断开复制连接,下次定时任务会发起重连。、
4)权限验证。如果主节点设置了requirepass参数,则需要密码验证,从节点必须配置masterauth参数保证与主节点相同的密码才能通过验证;如果验证失败复制将终止,从节点重新发起复制流程。
5)同步数据集。主从复制连接正常通信后,对于首次建立复制的场景,主节点会把持有的数据全部发送给从节点,这部分操作是耗时最长的步骤。Redis在2.8版本以后采用新复制命令psync进行数据同步,原来的sync命令依然支持,保证新旧版本的兼容性。新版同步划分两种情况:全量同步和部分同步。
6)命令持续复制。当主节点把当前的数据同步给从节点后,便完成了复制的建立流程。接下来主节点会持续地把写命令发送给从节点,保证主从数据一致性。
17、秃顶面试官:嗯,说下全量同步和部分同步呢
花花:
- 全量复制:一般用于初次复制场景,Redis早期支持的复制功能只有全量复制,它会把主节点全部数据一次性发送给从节点,当数据量较大时,会对主从节点和网络造成很大的开销。
- 部分复制:用于处理在主从复制中因网络闪断等原因造成的数据丢失场景,当从节点再次连上主节点后,如果条件允许,主节点会补发丢失数据给从节点。因为补发的数据远远小于全量数据,可以有效避免全量复制的过高开销。
18、秃顶面试官:嗯,部分同步是如何实现的呢,有可观测数据吗?
花花:psync命令运行需要以下组件支持:
- 主从节点各自复制偏移量。
- 主节点复制积压缓冲区。
- 主节点运行id。
复制偏移量
参与复制的主从节点都会维护自身复制偏移量。主节点(master)在处理完写入命令后,会把命令的字节长度做累加记录,统计信息在inforelication中的master_repl_offset指标中,从节点也会每秒钟上报自身的复制偏移量给主节点:
通过对比主从节点的复制偏移量,可以判断主从节点数据是否一致。
PS:可以通过主节点的统计信息,计算出master_repl_offset - slave_offset字节量,判断主从节点复制相差的数据量,根据这个差值判定当前复制的健康度。如果主从之间复制偏移量相差较大,则可能是网络延迟或命令阻塞等原因引起。
复制积压缓冲区
复制积压缓冲区是保存在主节点上的一个固定长度的队列,默认大小为1MB,当主节点有连接的从节点(slave)时被创建,这时主节点(master)响应写命令时,不但会把命令发送给从节点,还会写入复制积压缓冲区:
由于缓冲区本质上是先进先出的定长队列,所以能实现保存最近已复制数据的功能,用于部分复制和复制命令丢失的数据补救。复制缓冲区相关统计信息保存在主节点的info replication中:
repl_backlog_active: 开启复制缓冲区
repl_backlog_size: 缓冲区最大长度
repl_backlog_first_byte_offset: 其起始偏移量,计算当前缓冲区可用范围
repl_backlog_histlen: 已保存数据的有效长度
主节点运行ID
每个Redis节点启动后都会动态分配一个40位的十六进制字符串作为运行ID。运行ID的主要作用是用来唯一识别Redis节点,比如从节点保存主节点的运行ID识别自己正在复制的是哪个主节点。如果只使用ip+port的方式识别主节点,那么主节点重启变更了整体数据集(如替换RDB/AOF文件),从节点再基于偏移量复制数据将是不安全的,因此当运行ID变化后从节点将做全量复制。可以运行info server命令查看当前节点的运行ID
需要注意的是Redis关闭再启动后,运行ID会随之改变,如何在不改变运行ID的情况下重启呢?
当需要调优一些内存相关配置,例如:hash-max-ziplist-value等,这些配置需要Redis重新加载才能优化已存在的数据,这时可以使用debug reload命令重新加载RDB并保持运行ID不变,从而有效避免不必要的全量复制。 debug reload命令会阻塞当前Redis节点主线程,阻塞期间会生成本地
RDB快照并清空数据之后再加载RDB文件。因此对于大数据量的主节点和无法容忍阻塞的应用场景,谨慎使用。
psync命令
从节点使用psync命令完成部分复制和全量复制功能,命令格式:psync {runId} {offset}
参数含义如下:
·runId:从节点所复制主节点的运行id。
·offset:当前从节点已复制的数据偏移量
19、秃顶面试官:嗯,不错,那全量复制呢?
花花:全量复制是Redis最早支持的复制方式,也是主从第一次建立复制时必须经历的阶段。触发全量复制的命令是sync和psync
全量复制流程如下:
流程说明:
1)发送psync命令进行数据同步,由于是第一次进行复制,从节点没有复制偏移量和主节点的运行ID,所以发送psync-1。
2)主节点根据psync-1解析出当前为全量复制,回复+FULLRESYNC响应。
3)从节点接收主节点的响应数据保存运行ID和偏移量offset。
4)主节点执行bgsave保存RDB文件到本地。
5)主节点发送RDB文件给从节点,从节点把接收的RDB文件保存在本地并直接作为从节点的数据文件。
秃顶面试官:嗯,那如果RDB文件过大,会怎么样?
花花:对于数据量较大的主节点,比如生成的RDB文件超过6GB以上时要格外小心。传输文件这一步操作非常耗时,速度取决于主从节点之间网络带宽,通过细致分析3)到5)日志的时间差,可以算出RDB文件从创建到传输完毕消耗的总时间。如果总时间超过repl-timeout所配置的值(默认60秒),从节点将放弃接受RDB文件并清理已经下载的临时文件,导致全量复制失败。
PS:关于无盘复制:为了降低主节点磁盘开销,Redis支持无盘复制,生成的RDB文件不保存到硬盘而是直接通过网络发送给从节点,通过repl-diskless-sync参数控制,默认关闭。无盘复制适用于主节点所在机器磁盘性能较差但网络带宽较充裕的场景。注意无盘复制目前依然处于试验阶段,线
上使用需要做好充分测试。
6)对于从节点开始接收RDB快照到接收完成期间,主节点仍然响应读写命令,因此主节点会把这期间写命令数据保存在复制客户端缓冲区内,当从节点加载完RDB文件后,主节点再把缓冲区内的数据发送给从节点,保证主从之间数据一致性。如果主节点创建和传输RDB的时间过长,对于高流量
写入场景非常容易造成主节点复制客户端缓冲区溢出。默认配置为client-output-buffer-limit slave 256MB 64MB 60,如果60秒内缓冲区消耗持续大于64MB或者直接超过256MB时,主节点将直接关闭复制客户端连接,造成全量同步失败。对应日志如下:
7)从节点接收完主节点传送来的全部数据后会清空自身旧数据
8)从节点清空数据后开始加载RDB文件,对于较大的RDB文件,这一步操作依然比较耗时,可以通过计算日志之间的时间差来判断加载RDB的总耗时。
秃顶面试官:说的非常详细
20、秃顶面试官:那如果在全量复制的过程中,有读请求来了,会怎样?
花花:如果此时从节点正出于全量复制阶段或者复制中断,那么从节点在响应读命令可能拿到过
期或错误的数据。对于这种场景,Redis复制提供了replica-serve-stale-data参数,默认开启状态。如果开启则从节点依然响应所有命令。对于无法容忍不一致的应用场景可以设置no来关闭命令执行,此时从节点除了info和replicaof命令之外所有的命令只返回“SYNC with master in progress”信息。
9)从节点成功加载完RDB后,如果当前节点开启了AOF持久化功能,它会立刻做bgrewriteaof操作,为了保证全量复制后AOF持久化文件立刻可用。
21、秃顶面试官:不错不错,那在主从数据复制完成之后,主接到了一个写命令,这个改动是如何同步到从的呢?
花花:这就得说说异步复制了。主节点不但负责数据读写,还负责把写命令同步给从节点。写命令的发送过程是异步完成,也就是说主节点自身处理完写命令后直接返回给客户端,并不等待从节点复制完成。
主节点复制流程:
1)主节点接收处理命令。
2)命令处理完之后返回响应结果。
3)对于修改命令异步发送给从节点,从节点在主线程中执行复制的命令。
由于主从复制过程是异步的,就会造成从节点的数据相对主节点存在延迟。具体延迟多少字节,我们可以在主节点执行info replication命令查看相关指标获得。如下
Redis的复制速度取决于主从之间网络环境,repl-disable-tcp-nodelay,命令处理速度等。正常情况下,延迟在1秒以内。
秃顶面试官:回答的相当不错啊,那这次先这样~
花花:okey!