深入学习 Redis - 主从结构配置、流程、底层原理(全网最详细)
目录
前言
一、主从模式
1.1、概述
1.2、配置 redis 主从结构
1.2.1、复制配置文件,修改
1.2.2、配置主从结构
1.2.3、启动 redis 服务
1.2.4、查看复制状态
1.3、slaveof 命令
1.3.1、断开主从复制关系
1.3.2、切换主从复制关系
1.3.3、只读
1.3.4、网络延迟问题
1.4、拓扑结构
1.4.1、一主一从
1.4.2、一主多从
1.4.3、树形主从
1.5、主从复制流程
1.5.1、大致流程
1.5.2、数据同步
a)replication id
b)offset
psync 运行流程
全量复制流程
部分复制流程
实时复制流程
心跳包
误区:runid 是什么?
1.6、 拓展:主从结构配置误区(配置前必看!)
前言
谈到分布式系统,涉及到一个关键问题:单点问题
如果一个服务器只有一个节点(只用一个物理服务器部署整个程序),那么出现以下问题:
- 可用性:如果这个机器挂了,意味着服务就中断了.
- 性能:支持的并发量也是比较有限的.
因此引入分布式系统,主要也是为了解决上述的单点问题~ 分布式系统中,往往需要多个服务器来部署 redis 服务,从而构成 redis 集群,此时就可以让这个集群给整个分布式系统中的其他服务,提供更稳定 / 更高效的数据存储功能.
本章主要来讲一下 redis 部署方式中的主从模式.
Ps:本章节相关操作不需要记忆!后续工作中如果用到了能查到即可. 重点理解流程和原理.
重点:为什么引入主从模式?什么是主从模式(由简到详)?主从模式有哪些结构?主从模式是如何同步数据的?实时复制时的心跳包机制?
一、主从模式
1.1、概述
在 redis 主从模式下,由一个主节点和多个从节点构成(所谓一山不能容二虎),从节点通过复制主节点得到,并且后续主节点数据发生变化,从节点也必须 “听” 主节点的(从节点的数据跟随主节点一起变化,保持一致) .
如果我修改了从节点的数据呢?redis 主从模式中,从节点上的数据不允许修改,只能读取数据,而主节点既可以进行 “写操作” 也可以进行 “读操作” ~ 为什么这么搞呢?实际上更准确的说,主从模式,主要是针对 “读操作” 进行 并发量 & 可用性 的提高,因为实际的业务场景中,读操作往往比写操作更频繁~
如果节点挂了怎么办?
如果是挂掉了某一个从节点,没什么影响,此时继续从主节点或者其他从节点读取数据,效果是完全一样的.
如果改掉的是主节点,是有一定影响的,因为主节点还负责 “写数据” .
这样结构的好处?
由于从节点的数据和主节点时刻保持一致,因此其他客户端从从节点这里读取数据,和主节点这里读取数据是没区别的! 如果后续有客户端来读取数据,就可以从节点中随便挑一个,来读取数据,引入更多的资源,支持的并发量也就大大提高了~
举个例子?
从节点就像是刚到公司的实习生,是不能在没有熟悉业务和文档的前提下去干活的,因此刚来的实习生任务只有一个,就是熟悉业务(读操作),而你的导师就像主节点,只能有一个,他的工作就是完成自己需求(写操作,读操作),并且他来告诉你你要去读什么文档、熟悉什么业务(从节点通过复制主节点得到).
1.2、配置 redis 主从结构
配置 redis 主从结构,需要启动多个 redis 服务器,分配在一个个单独的主机上(分布式),但是考虑到我们目前每个人,大概只有一个云服务器,因此就来实现一下,在一个云服务器主机上,运行多个 redis-server 进程~
本来 redis-server 的端口是 6379 ,此时就不能让其他节点启动时也用 6379 了,我们有一下两种方式来指定 redis-server 的端口号:
- 启动程序时,通过命令的方式来指定端口号(--port 选项)
- 直接在配置文件中,来设定端口(推荐,因为修改配置文件,是持久有效的).
假设我要配置一个主节点和两个从节点,步骤如下:
1.2.1、复制配置文件,修改
主节点的配置不变,只需要修改从节点的配置即可,因此我们只需要复制两份主节点的配置文件,并修改这两份文件的端口和后台运行方式即可.
1.2.2、配置主从结构
在两个配置文件末尾加 slaveof 配置主从结构(绑定父节点为 6379 端口).
1.2.3、启动 redis 服务
通过 redis-server 命令来启动刚刚配置好的这两个节点.
Ps:如果 redis 服务启动后修改了配置文件就需要重启才能起效。
1.如果是通过 redis-server 启动服务器,就必须搭配 kill 命令来停止.
2.如果是通过 service redis-server start 启动服务器,必须搭配 service redis-server stop 来停止.
3.如果使用 kill 命令停止 service redis-server start ,这个 redis-server 进程会自动启动.
通过 netstat -anp 命令就可以查看从节点和主节点的绑定情况
在复制两个会话,启动两个从节点的 redis 客户端,此时 redis 从节点上就只能读数据,不能写入数据了
1.2.4、查看复制状态
在 redis 客户端,通过 info replication 就可以查看当前节点的复制状态了.
(这里简单看一下,后面有详细介绍~)
值得注意的是,从节点和主节点之间的数据同步,不是瞬间完成的,并且同时主节点上也会 “源源不断” 的收到其他 “修改数据” 的请求,因此这个时候就需要使用 offset 来记录当前主节点和从节点的数据同步情况,当 从节点 的 offset 等于 主节点的 offset 时,表明此时数据完全一致.
1.3、slaveof 命令
1.3.1、断开主从复制关系
在当前从节点的 redis 客户端中使用 slaveof no one 可以断开现有的主从复制关系,并且自身成为新的主节点~
Ps:这里从节点断开复制后并不会抛弃原有数据,只是无法再获取主节点上的数据变化,这就像是你是一个土匪,加入了别人的帮派,几年以后,感觉自己学了不少东西,于是就脱离了现在的帮派,自己出来占山为王~
1.3.2、切换主从复制关系
通过 slaveof 命令还可以实现切主操作,将当前从节点的数据源切换到另⼀个主节点,并且切换后会先删除当前节点的所有数据,再复制新的主节点数据。执行 slaveof {newMasterIp} {newMasterPort} 命令即可。
例如将端口号为 6381 从节点的 “主”,换成 端口号为 6380,如下
Ps:此处的修改是临时的,如果重启 redis 服务器,就会按照最初再配置文件中设置的内容来建立主从关系.
1.3.3、只读
默认情况下,从节点使⽤ slave-read-only=yes 配置为只读模式。由于复制只能从主节点到从节 点,对于从节点的任何修改主节点都⽆法感知,修改从节点会造成主从数据不⼀致。所以建议线上不 要修改从节点的只读模式。
1.3.4、网络延迟问题
TCP 内部支持 naqle 算法,目的就是为了节省网络带宽(目的和 tcp 的捎带应答一样,针对小的 tcp 数据报,就多攒点,攒够了再发,这样就减少了发送的次数)。通过 repl-disable-tcp-nodelay 这个选项就可以进行配置 naqle 算法是否开启.
值得注意的是,实际的工作中,需要根据实际场景来决定是否开启:
- 开启,能节省网络带宽,但是会增加 tcp 的传输延迟.
- 关闭,能减少 tcp 传输延迟,但是会增加网络带宽.
一般游戏开发,或者是视频直播,这种即时性要求特别强的,就像需要关闭 naqle 算法.
1.4、拓扑结构
拓扑结果就描述了若干个节点之间,按照什么样的方式进行组织连接的.
1.4.1、一主一从
这种结构有一个特点,就是当写请求的数据太多的时候,也会给主节点带来压力,可以通过关闭主节点的 AOF ,只在从节点上开启 AOF 来分担持久化压力,避免影响性能。
但是这种设定方式有一个缺陷,就是主节点一旦挂了,就不能让他自动重启,如果自动重启,此时没有 AOF 文件就会丢失数据,进一步的同步到从节点上,就会把 从节点的数据也给搞丢了.
改进的办法就是当主节点挂了以后,让主节点从从节点这里获取 AOF 文件再启动.
实际的开发中,读请求 远远超过 写请求,也难辞上述的 一主一从结构就有点难以应对了,就需要接下来的一主多从结构来支持
1.4.2、一主多从
这种结构就是适合有大量到 读请求 场景,帮助主节点分担大部分的 读请求 压力, 但实际随着从节点个数的增加,向主节点中写入一条数据,就需要同步给多个从节点,反而影响性能.
为了解决这个问题,就需要引入树形主从~
1.4.3、树形主从
这种结构,从节点也可以将其他从节点当作主节点,分担了主节点将数据同步到从节点上的压力,但是一旦数据进行修改,同步的延时是要比刚才更长的.
1.5、主从复制流程
1.5.1、大致流程
- 保存主节点信息:从节点会去保存主节点的 ip 和端口.
- 主从建立连接:通过 TCP 三次握手建立连接,主要是为了验证通信双方是否能正确的读写数据(道路能否建立起来).
- 发送 ping 命令:验证主节点是否能够正常工作(车是否能在这个道路上跑).
- 权限验证:redis 主节点如果设置了密码参数,就需要先通过密码的验证.
- 同步数据集:下面会重点介绍.
- 命令持续复制:当从节点复制完主节点的所有数据后,主节点还会“源源不断”的收到写请求,机会继续将修改后的数据复制到从节点上.
1.5.2、数据同步
Redis 使用 psync 命令完成主从数据同步,从节点会自动执行 psync,也就是从节点会去主节点这边拉取数据.
psync replicationid offset
这个命令主要就是要理解两个参数:replicationid 、offset.
a)replication id
replication id 就表示要从那个主节点上获取数据.
replication id 是由主节点生成的,生成的时机是在,主节点启动的时候会生成(同一主节点,每次重启,生成的 replication id 都是不同的),从节点晋升为主节点的时候也会生成。
当从节点和主节点建立了复制关系,就会从主节点这边拉取到 replication id.
通过 info replication 可以获取到当前 replication id 的值.
这里还可以看到 replid2,但是一般是用不上的,有一个情况就是假设有两个节点,主节点 A 和 从节点 B,如果 A 和 B 通信的过程中出现了网络抖动,B 可能就认为 A 挂了,就会把 replid 的值交给 replid2,然后 B 就会自己成为主节点,给自己生成一个 replid,此时,B 也会记得之前的主节点 A 的 replid,就是现在的 replid2。
等后续网络稳定了,B 还可以根据 replid2 重新回到 A 的怀抱中~(这里需要手动干预,但是哨兵机制可以自动完成)
打个比方
这就好比你在一个公司干的挺好的,但是公司有一天突然倒闭了,于是就出来自己创业,但是创业的途中你还和之前公司的老板保持很好的关系,突然有一天,原来的公司重新建立起来了,你就通过保持那层关系,回到了之前的那个公司的怀抱~
b)offset
从节点和主节点之间的数据同步,不是瞬间完成的,并且在复制数据的同时,主节点上也会 “源源不断” 的收到其他 “修改数据” 的请求,那么从节点怎么知道数据已经同步到哪里了呢?
- 主节点和从节点上都会维护一个偏移量(整数),主节点的偏移量,就是把收到的 “写操作” 的命令(每个命令都要占几个字节)按字节进行累加的.
- 从节点的偏移量就描述了,现在从节点这里的数据同步到哪里了.
- 如果从节点的偏移量和主节点偏移量一样了,就说明两个节点的数据完全一致了.
在 psync 命令中,offset 有两种写法:
- offset 写作 -1,就是获取全量数据.
- offset 写成具体的正整数就是从当前偏移量位置来进行获取.
打个比方
这就像是一个学习的过程,老师在课堂上讲课是一个进度,你下来自己巩固老师讲课的内容,是另一个进度,当你完善巩固赶上的老师进度以后,你的知识储备也就差不多和老师讲的大差不差了~
psync 运行流程
- 从节点发送 psync replid offset 指令,主节点根据参数进行解析,结合自身情况,看是要全量数据同步,还是部分数据同步(增量数据同步).
- 如果反馈 "fullresync" 表示进行全量数据同步.
- 如果反馈 "continue" 表示进行增量数据同步.
- 如果反馈 "err" 表示错误,因为比较老版本的 redis 服务器是不支持 psync 的,但是可以使用 sync 来代替(redis 1.0.0 就有了).
那么什么时候进行全量复制,什么时候进行部分复制呢?
- 全量复制:首次和主节点进行数据同步时(判断 replication id 不一样),或者是在部分复制的时候,发现已经超出积压缓冲区的数据范围了,也会进行全量复制.
- 部分复制:从节点之前从主节点上父之过数据了,或者是有网络抖动或者从节点重启而丢失数据(大部分数据是一致的).
全量复制流程
- 从节点发送 psync 命令给主节点,由于是第一次进行复制,从节点没有主节点的复制偏移量,所以偏移量发送为 -1,也就是需要进行全量复制.
- 主节点根据命令,解析出要进行全量复制,返回 "fullresync" 响应.
- 从节点接受主节点的响应信息并保存.
- 主节点执行 bgsave ,一定会重新生成一个新的 rdb 文件(因为已有的 rdb 文件和最新的数据可能存在较大差异),进行持久化.
- 主节点发送 rdb 文件给从节点,从节点会将接收到的 rdb 文件保存到本地硬盘.
- 主节点将生成 rdb 到从节点接收完 rdb 文件期间执行的写命令,写入缓冲区,等从节点保存完 rdb 文件后,主节点再将缓冲区中的数据补发给从节点,从节点就会把补发的数据仍然按照二进制格式追加写入到 rdb 文件中,保证主从一致.
- 从节点清空自身的旧数据.
- 从节点加载 rdb 文件,保证和主节点数据一致.
- 如果从节点加载 rdb 文件完成后,并且开启了 aof,就会把刚刚加载数据过程中产生的 aof 日志通过 bgrewrite 操作,清楚冗余数据,并进行整理.
Ps:除了上述讲到的全量复制,主节点进行全量复制的时候,也支持 “无硬盘模式”(diskless). 主节点生成的 rdb 文件不会直接保存到文件中了,而是直接进行网络传输,接着从节点也将收到的数据直接进行加载了,这样主节点和从节点省下来一系列读写硬盘的操作.
即使引入了无硬盘模式,整个操作还是比较重量的,比骄耗时的,因为网络传输是没法省的,相比于网络传输,读写硬盘就是小头,因为涉及到网络传输,就离不开封装和分用的过程...
部分复制流程
- 当主从节点之间出现网络中断时,如果超过了 repl-timeout 时间,主节点就会认为从节点故障并中断复制连接.
- 主从连接中断期间,主节点会继续接受到 “写请求” 的命令,就会把这些命令滞留在积压缓冲区中(积压缓冲区就是内存中一个简单的队列,会记录最近一段时间修改的数据,总量是有限的,随着时间的推移,就会把之前的旧数据逐渐删除掉)。
- 当主从节点网络恢复以后,从节点再次连接上主节点.
- 从节点将之前保存的 replication id 和 offset 作为 psync 的参数发送给主节点,请求进行部分复制.
- 主节点接收到 psync 请求后,进行必要的验证,随后根据 offset 去积压缓冲区找合适的数据,并响应 continue 给从节点. (如果主节点的 “写请求” 过多,超出积压缓冲区的范围,就只能进行全量复制了)
- 主节点将积压缓冲区的数据发送给从节点,保证一致性.
实时复制流程
从节点和主节点数据同步完成了(也就是说这一刻,从节点和主节点数据一致了),但是之后,主节点这边也会 “源源不断” 的收到新的 “写请求” ,主节点上的数据就会随之改变,这种改变也需要能够同步给从节点.
此时,从节点和主节点之间会建立 TCP 长连接,然后主节点把自己收到的 “写请求” 修改的数据,发给从节点,从节点再修改自己内存中的数据.
Ps:这个过程也是需要时间的,正常来说,延时是比较短的,但是如果是多级从节点的树形结构,延时就会上升了.
心跳包
进行实时复制的时候,需要保证连接处于可用状态,因此引入了心跳包机制.
主节点:默认,每隔 10s 给从节点发送一个 ping 命令,从节点收到就会返回 pong.
从节点:默认,每隔 1s 就给主节点发起一个特定的请求,就会上报当前从节点的复制数据的进度(offset).
如果主节点发现从节点通信延迟超过 repl-timeout 配置的值(默认 60 秒),则判定从节点下线,断 开复制客⼾端连接。从节点恢复连接后,⼼跳机制继续进⾏。
Ps:这里的 10s,1s,60s 数值都不要去背,因为都是可以修改的,需要我们去结合实际场景合理分配.
误区:runid 是什么?
网上有很多资料都会将 run id 和 replication id 混着说,实际上,一个 redis 服务器上 ,replication id 和 run id 都是存在的, runid 主要是用来支撑实现 redis 哨兵功能的,和主从复制没有什么关系.
replid 和 offset 共同标识了一个数据集合.
1.6、 拓展:主从结构配置误区(配置前必看!)
刚刚所讲的主从配置实际上是有问题的,因为最开始创建从节点的配置文件没有改 appendfilename 属性,导致生成的 aof 文件路径/文件名 都是同一个, 如下图
-rw- 就表示 root 用户可读可写,而 r--r-- 就表示这个文件除了 root 意外的用户,只有读权限,又因为 service redis-server start 启动 redis 服务器是通过 redis 这样的用户来启动的(防止权限过高,redis 被黑客攻破),所以其他节点使用 service redis-server start 启动 redis 服务器无法打开这个文件,就启动失败了,如何解决呢?
解决方案:把三个 redis 服务器生成的文件,也区分开,最靠谱的办法就是,直接把三个 redis 服务器的 工作目录 分开(修改 配置文件 中的 dir 选项)
步骤如下:
- 停止之前的 redis 服务器.
- 删除之前工作目录下生成的 aof 文件,或者也可以通过 chown 命令修改 aof 文件所属用户
-
chown redis:redis appendonly.aof
- 给从节点创建出新的工作目录,并修改从节点配置文件,设定成新的目录为工作目录.
- 启动 redis 服务器.
码字不易~