分布式部署Redis方式
分布式系统,涉及到一个非常关键的问题:单点问题
单点问题:如果某个服务器,只有一个节点(只搞一个物理服务器,来部署这个服务器程序),会遇到一些困难:
- 可用性问题,如果这个机器挂了,意味着服务就中断了
- 性能/支撑的并发量也是比较有限的
引入分布式系统,主要也就是为了解决上述的单点问题,在分布式系统中,往往希望有多个服务器来部署redis服务,从而构成一个redis集群,此时就可以让这个集群给整个分布式系统中其他的服务,提供更稳定。高效的数据存储功能
在分布式系统中,希望使用多个服务器来部署reids,往往存在以下几种redis的部署方式
- 主从模式
- 主从+哨兵模式
- 集群模式
主从复制能解决的问题
主从模式:在若干个redis节点中,有的是"主"节点,有的是"从"节点;假设有三个物理服务器(称为是三个节点),分别部署了一个redis-server进程;此时就可以把其中的一个节点,作为"主节点",另外两个节点作为"从节点";从节点,得听主节点的(从节点上的数据要跟随主节点变化:从节点的数据要和主节点保持一致)
本来,在主节点上保存一堆数据;引入从节点之后,就是要把主节点上面的数据,复制出来,放到从节点中,后续,主节点这边对于数据有任何修改,都会把这样的修改给同步到从节点上(从节点,就是主节点的副本)
Question:如果我改了从节点的数据,是否是把从节点的数据往主节点上同步呢?
Answer:Redis主从模式中,从节点的数据,不允许修改,只能读取数据!!!
由于从节点的数据都是时刻和主节点保持一致的,因此其他的客户端从从节点这里读取数据,和从主节点这里读取数据都是一样的;后续如果有客户端来读取数据,就可以从上述节点中,随机挑一个节点,给这个客户端提供读取数据的服务
之前只是一个单个redis服务器节点,此时机器挂了,整个redis就挂了;上述这个主从结构,这些redis服务器不太可能"同时挂了"
Question:
- 但是整个机房是否可能被一锅端了?
- 如果是挂了某个从节点?
- 如果是挂了主节点呢?
- 是否可以引入多个主节点呢?
Answer:
- 这个时候如果考虑到更多的可用性,就可以把这些机器放到多个不同的机房中(异地多活)
- 没啥影响,此时继续从主节点或者其他从节点读取数据,得到的效果完全一致
- 还是有一点影响,从节点只能读数据,如果需要写数据,就没得写了(可用性是提高了,但是还没到非常理想的程度)
- 一山不容二虎,听谁的是个麻烦事!!
更准确的说,主从模式,主要是针对"读操作",进行并发量&可用性的提高;而写操作的话,不论是可用性还是并发,都是非常依赖主节点,主节点又不能搞多个;实际业务场景中,读操作往往就是比写操作更频繁
主从结构,是分布式系统中比较经典的一种结构,不仅仅是redis,像MySQL等其他常用组件也常见的
如何启动多个redis-server
配置redis主从结构,首先需要启动多个redis服务器;正常来说,每个redis服务器程序,应该在一个单独的主机上(才是分布式)
可以在一个云服务器主机上,运行多个redis-server进程的,需要保证多个redis-server的端口是要不同的;本来redis-server默认的端口是6379,此时就不能让新启动的redis-server再继续使用6379了
Question:如何求指定redis-server的端口呢?
Answer:
- 可以在启动程序的时候,通过命令行来指定端口号 --port选项
- 也可以直接在配置文件中,来设定端口
此处准备搞一个主节点,两个从节点;主节点的配置不用变,只需要修改从节点的配置即可
bash
redis-server ./slave.conf
bash
redis-server ./slave2.conf
当前这几个节点并没有构成主从结构,而是各自为政,要想成为主从结构,还需要进一步的配置
bash
redis-cli -p 6380
配置主从复制
有三种方法:
- 在配置⽂件中加⼊ slaveof {masterHost} {masterPort} 随 Redis 启动⽣效
- 在 redis-server 启动命令时加⼊ --slaveof {masterHost} {masterPort} ⽣效
- 直接使⽤ redis 命令:slaveof {masterHost} {masterPort} ⽣效。
使用配置文件来配置主从结构(持久)!!
redis服务器配置文件需要重启才能生效
这种停止redis-server的方式,是和直接运行redis-server命令的方式搭配的
如果使用service redis-server start这种方式启动的,就必须使用service redis-server stop 来进行停止的;此时如果使用kill -9 的方式停止,kill掉之后,这个redis-server进程就能自动启动(服务器就是要稳定性和高可用,但是服务器上的某些程序,可能也难以避免出现挂了的情况,如果服务器进程挂了,要是能自动重启进程(把进程再启动起来),对于整体的服务不会产生严重影响,往往会有另外一个进程来专门监控指定的服务器进程的运行状态)
程序启动起来,并且还多了一些TCP连接(其中一个redis节点,和主节点之间的tcp连接,从节点启动之后就会和主节点建立tcp连接;主节点就相当于服务器,从节点就相当于客户端)
查看主从结构信息
主节点这边数据产生的任何修改,从节点就能立即感知到,就是刚才看到的这些tcp连接起到的效果;从节点不能写入数据
bash
info replication
//查看主从信息
okffset:主节点会受到源源不断的"修改数据"的请求,从节点就需要从主节点这里同步这些修改请求,从节点和主节点之间的数据同步,不是瞬间完成的,offset就相当于主节点和从节点之间,同步数据的进度!
lag:延迟
master_replid:当从节点出现网络波动的情况时才会起作用
reol_backlog:积压缓冲区,支持部分同步机制的实现
slave_read_only:可以在配置文件中配置修改
断开主从结构和修改主从结构
断开复制 slaveof 命令不但可以建⽴复制,还可以在从节点执⾏ slaveof no one 来断开与主节点复制关系,直接使用这个命令(redis客户端)断开现有的主从复制连接
例如在 6380 节点上执⾏ slaveof no one 来断开复制。 断开复制主要流程
- 断开与主节点复制关系
- 从节点晋升为主节点
从节点断开复制后并不会抛弃原有数据,只是⽆法再获取主节点上的数据变化
通过 slaveof 命令还可以实现切主操作,将当前从节点的数据源切换到另⼀个主节点。执⾏ slaveof {newMasterIp} {newMasterPort} 命令即可。 切主操作主要流程
- 断开与旧主节点复制关系
- 与新主节点建⽴复制关系
- 删除从节点当前所有数据
- 从新主节点进⾏复制操作。
此时,6380看起来像是个主节点,实际上不是,仍然是从节点,只是作为6381同步数据的来源,自身仍然是不能修改数据的
通过slaveof是修改了主从结构,此处的修改时临时性的,如果重新启动redis服务器,仍然会按照配置文件中的内容重启服务器
主从复制时的安全,只读,传输延时
安全性
对于数据⽐较重要的节点,主节点会通过设置 requirepass 参数进⾏密码验证,这时所有的客⼾ 端访问必须使⽤ auth 命令实⾏校验。从节点与主节点的复制连接是通过⼀个特殊标识的客⼾端来完 成,因此需要配置从节点的masterauth 参数与主节点密码保持⼀致,这样从节点才可以正确地连接到 主节点并发起复制流程。
只读
默认情况下,从节点使⽤ slave-read-only=yes 配置为只读模式。由于复制只能从主节点到从节 点,对于从节点的任何修改主节点都⽆法感知,修改从节点会造成主从数据不⼀致。所以建议线上不要修改从节点的只读模式。
传输延迟
主从节点⼀般部署在不同机器上,复制时的⽹络延迟就成为需要考虑的问题,Redis 为我们提供 了 repl-disable-tcp-nodelay(这个选项可以用于在主从同步通信过程中,关闭tcp的nagle算法,从节点更快速的和主节点进行同步) 参数⽤于控制是否关闭 TCP_NODELAY,默认为 no,即开启 tcpnodelay 功能(TCP内部支持了nagle算法),说明如下
- 当关闭时,主节点产⽣的命令数据⽆论⼤⼩都会及时地发送给从节点,这样主从之间延迟会变⼩, 但增加了⽹络带宽的消耗。适⽤于主从之间的⽹络环境良好的场景,如同机房部署(减少了tcp的传输延迟,增加了网络带宽)
- 当开启时(默认开启),主节点会合并较⼩的 TCP 数据包从⽽节省带宽。默认发送时间间隔取决于 Linux 的内 核,⼀般默认为 40 毫秒。这种配置节省了带宽但增⼤主从之间的延迟。适⽤于主从⽹络环境复杂 的场景,如跨机房部署(增加tcp的传输延迟,节省了网络带宽)
nagle算法目的和tcp的捎带应答是一样的,针对小的tcp数据报,进行合并,减少了包的个数
主从复制的拓扑结构
拓扑结构:若干个节点之间,按照什么样的方式进行组织连接
⼀主⼀从拓扑
如果写数据请求太多,此时也是会给主节点造成一些压力,可以通过关闭主节点的AOF,只在从节点上开启AOF
但是这种设定方式,有一个缺陷:主节点一旦挂了,不能让他自动重启(如果自动重启,此时没有AOF文件,就会丢失数据,进一步的主从同步,会把从节点的数据也给删了)
改进方法:当主节点挂了之后,就需要让主节点从从节点这里获取到AOF文件,在启动
实际开发中,读请求远远超过写请求
⼀主多从结构
主节点上的数据发生改变,就会把改变的数据同时同步给所有的从节点;随着从节点个数的增加,同步一条数据,就需要传输多次(扁平化结构)
树形主从结构
主节点就不需要那么高的网卡带宽了,一旦数据进行修改了,同步的延时是比刚才更长的
主从复制的基本流程
- 先保存主节点的IP和端口(变量)
- 建立TCP连接(三次握手),验证的是 通信双方能否正确读写数据(系统层面)路是通的
- 发送ping,收到pong,验证主节点能否正常工作(站在应用程序的角度)车能跑
- redis主节点开启了密码
- 复制数据最关键操作
- 5.全量同步+6.增量同步
redis提供了psync命令,完成数据同步的过程,psync不需要咱们手动执行,redis服务器会在建立好主从同步关系之后,自动执行psync,从节点负责执行psync,从节点从主节点这边拉取数据
bash
PSYNC replicationid offset
replicationid的作用
replication-复制,是主节点生成的,主节点启动的时候就会生成(从节点晋升成主节点的时候,也会生成),即使是同一个主节点,每次重启,生成的replicationid都是不同的,从节点和主节点建立了复制关系,就会从主节点这边获取到replication id;当前讨论的是一个主节点 多个从节点
bash
info replication
获取到当前replication id 的值
一般情况下,replid2是用不上的,有一个主节点A(生成replid),还有一个从节点B(获取到A的relid),如果A和B通信过程中出现了一些网络抖动,B可能就会认为A挂了;B就会自己成为主节点(给自己生成replid),此时B也会记得之前旧的replid,就是通过replid2,后续网络稳定,B还可以根据replid2重新回到A的怀抱(需要手动干预,哨兵机制可以自动完成这个过程)
offset的作用
offset-偏移量,主节点和从节点都会维护偏移量(整数)
主节点的偏移量:主节点上会受到很多的修改操作的命令,每个命令都要占据几个字节,主节点会把这些修改命令,每个命令的字节数进行累加;
从节点的偏移量:描述了从节点这里的数据同步到了哪里;如果从节点和主节点的偏移量一致,说明已经赶上了!从节点(slave)每秒钟上报⾃⾝的复制偏移量给主节点,因此主节点也会保存从节点的复制偏移量
replid + offset 共同标识了⼀个 "数据集". 如果两个节点, 他们的 replid 和 offset 都相同, 则这两个节点上持有的数据, 就⼀定相同.
psync 运⾏流程
- 从节点发送 psync 命令给主节点,replid 和 offset 的默认值分别是 ? 和 -1
- 主节点根据 psync 参数和⾃⾝数据情况决定响应结果:
- 如果回复 +FULLRESYNC replid offset,则从节点需要进⾏全量复制流程
- 如果回复 +CONTINEU,从节点进⾏部分复制流程
- 如果回复 -ERR,说明 Redis 主节点版本过低,不⽀持 psync 命令。从节点可以使⽤ sync 命令进⾏ 全量复制
- psync ⼀般不需要⼿动执⾏. Redis 会在主从复制模式下⾃动调⽤执⾏
- sync 会阻塞 redis server 处理其他请求. psync 则不会.
全量复制和部分复制
Question:
什么时候进行全量复制?
什么时候进行部分复制?
Answer:
- 首次和主节点进行数据同步
- 主节点不方便进行部分复制的时候
- 从节点之前已经从主节点上复制过数据了,因为网络抖动或者从节点重启了
- 从节点需要重新从主节点这边进行同步数据,此时看看能不能只同步一小部分(大部分数据都是一致的)
全量复制的流程
- 从节点发送 psync 命令给主节点进⾏数据同步,由于是第⼀次进⾏复制,从节点没有主节点的运 ⾏ ID 和复制偏移量,所以发送 psync ? -1
- 主节点根据命令,解析出要进⾏全量复制,回复 +FULLRESYNC 响应
- 从节点接收主节点的运⾏信息进⾏保存
- 主节点执⾏ bgsave 进⾏ RDB ⽂件的持久化,rdb文件是二进制格式的,比较省空间
- 主节点发送 RDB ⽂件给从节点,从节点保存 RDB 数据到本地硬盘,不能使用原有的rdb文件,而是必须要重新生成,已有的rdb文件可能会和当前的数据存在较大的差异,在主节点生成rdb文件和传输rdb文件的过程中,还会继续收到很多新的修改操作,新修改的数据,也必须要同步给从节点
- 主节点将从⽣成 RDB 到接收完成期间执⾏的写命令,写⼊缓冲区中,等从节点保存完 RDB ⽂件 后,主节点再将缓冲区内的数据补发给从节点,补发的数据仍然按照 rdb 的⼆进制格式追加写⼊到收 到的 rdb ⽂件中. 保持主从⼀致性
- 从节点清空⾃⾝原有旧数据
- 从节点加载 RDB ⽂件得到与主节点⼀致的数据
- 如果从节点加载 RDB 完成之后,并且开启了 AOF 持久化功能,它会进⾏ bgrewrite 操作,得到最近的 AOF ⽂件,产生很多AOF日志,由于当前收到的是大批量的数据,会有冗余数据,所以进行bgrewrite操作
全量复制的无硬盘模式
主节点,进行全量复制的时候,也支持"无硬盘模式",主节点生成的rdb文件的二进制数据,不是保存到文件中,而是直接网络传输了(省下了一系列读硬盘和写硬盘的操作),从节点之前也是把收到的rdb数据,写入到硬盘中,然后再加载,现在也可以省略这个过程,直接把收到的数据进行加载了
即使引入了无硬盘模式,仍然这个操作是比较重量,比较耗时的,网络传输(大规模数据->全量复制)是没法省的,相比于网络传输来说,读写硬盘是小头
有磁盘复制 vs ⽆磁盘复制(diskless) 默认情况下, 进⾏全量复制需要主节点⽣成 RDB ⽂件到主节点的磁盘中, 再把磁盘上的 RDB ⽂件通过发送给从节点. Redis 从 2.8.18 版本开始⽀持⽆磁盘复制. 主节点在执⾏ RDB ⽣成流程时, 不会⽣成 RDB ⽂ 件到磁盘中了, ⽽是直接把⽣成的 RDB 数据通过⽹络发送给从节点. 这样就节省了⼀系列的写 硬盘和读硬盘的操作开销
关于runid和replid
一个redis服务器上,replication id 和run id是都存在的,两个不同的id,看起来非常像
长度相同,格式也非常相似
run id是每个节点都不相同的,replid则是具有主从关系的节点
官方文档,明确表示,psync使用的是replicationid
runid是标识一次redis的"运行",和主从复制没有什么关系,和哨兵有关系
replid和主从复制有关系,在主从复制中起作用的
replid+offset共同标识了一个数据集合!!!
部分复制的流程
出现网络抖动,主节点最近修改的数据可能无法及时同步过来,更严重的,从节点已经感知不到主节点了(从节点可能会晋升主节点),网络抖动,一般都是"暂时的",此时就可以让主节点和从节点重新建立联系;当主节点和从节点重新建立连接之后,就需要进行数据的同步
psync带有具体的replid和offset值,主节点就要根据psync的参数进行判定,当前这次是按照全量复制合适还是按照部分复制合适
- 当主从节点之间出现⽹络中断时,如果超过 repl-timeout 时间,主节点会认为从节点故障并终端 复制连接
- 主从连接中断期间主节点依然响应命令,但这些复制命令都因⽹络中断⽆法及时发送给从节点,所 以暂时将这些命令滞留在复制积压缓冲区中
- 当主从节点⽹络恢复后,从节点再次连上主节点
- 从节点将之前保存的 replicationId (描述数据的来源,如果replicationid不一样,说明这个从节点就是专捅义父,需要全量复制)和 复制偏移量(数据的复制的进度,从节点之前同步数据的进度,主节点就看这个进度是否在当前的积压缓冲区之内,如果确实是在积压缓冲区之内,此时就可以直接进行部分复制,就只把最近的数据同步过去即可)作为 psync 的参数发送给主节点,请求进⾏部分 复制
- 主节点接到 psync 请求后,进⾏必要的验证。随后根据 offset 去复制积压缓冲区查找合适的数据, 并响应 +CONTINUE 给从节点
- 主节点将需要从节点同步的数据发送给从节点,最终完成⼀致性。
复制积压缓冲区 复制积压缓冲区是保存在主节点上的⼀个固定⻓度的队列,默认⼤⼩为 1MB,当主节点有连接的从节 点(slave)时被创建,这时主节点(master)响应写命令时,不但会把命令发送给从节点,还会写⼊ 复制积压缓冲区,如图所⽰。
由于缓冲区本质上是先进先出的定⻓队列,所以能实现保存最近已复制数据的功能,⽤于部分复制和 复制命令丢失的数据补救。复制缓冲区相关统计信息可以通过主节点的 info replication 中:
bash
127.0.0.1:6379> info replication
# Replication
role:master
...
repl_backlog_active:1 // 开启复制缓冲区
repl_backlog_size:1048576 // 缓冲区最⼤⻓度
repl_backlog_first_byte_offset:7479 // 起始偏移量,计算当前缓冲区可⽤范围
repl_backlog_histlen:1048576 // 已保存数据的有效⻓度
根据统计指标,可算出复制积压缓冲区内的可⽤偏移量范围:[repl_backlog_first_byte_offset, repl_backlog_first_byte_offset + repl_backlog_histlen]
这个相当于⼀个基于数组实现的环形队列. 上述区间中的值就是 "数组下标" .
如果当前从节点需要的数据, 已经超出了主节点的积压缓冲区的范围, 则⽆法进⾏部分复制, 只 能全量复制了.
实时复制的流程
- 全量复制:从节点刚连上主节点之后,进行的数据初始化工作
- 部分复制:全量复制的特殊情况,优化手段,目的和全量复制一样
- 实时复制:从节点,已经和主节点,同步好数据了(从节点这一时刻已经和主节点数据一致了),但是之后,主节点这边会源源不断的收到新的修改数据的请求,主节点上的数据也会随之改变,也需要能够同步给从节点
- 从节点和主节点之间会建立TCP的长连接,然后主节点会把自己受到的修改数据的请求,通过上述连接发给从节点,从节点再根据这些修改请求,修改内存中的数据,这个过程也是需要时间的,正常来说是比较短的,但是如果是树形结构(级别很多),延时也就变大了
- 在进行实时复制的时候,需要保证连接处于可用状态--心跳包机制,主节点默认每10s给从节点发送一个ping命令,从节点收到就返回pong;从节点默认每隔1s就给主节点发起一个特点的请求,就会上报当前从节点复制的进度(offset)
- 如果没有收到pong,有一个阈值(60s),就会认为从节点有问题,判定从节点下线
- 这些时间都是可以修改的!
小结
主从复制解决的问题:单点问题
- 单个 redis 节点, 可⽤性不⾼.
- 单个 redis 节点, 性能有限
主从复制的特点:
- Redis 通过复制功能实现主节点的多个副本
- 主节点⽤来写, 从节点⽤来读. 这样做可以降低主节点的访问压⼒
- 复制⽀持多种拓扑结构,可以在适当的场景选择合适的拓扑结构
- 复制分为全量复制, 部分复制和实时复制
- 主从节点之间通过⼼跳机制保证主从节点通信正常和数据⼀致性。
主从复制配置的过程:
- 主节点配置不需要改动
- 从节点在配置⽂件中加⼊ slaveof 主节点ip 主节点端⼝ 的形式即可
主从复制的缺点
- 从机多了, 复制数据的延时⾮常明显
- 主机挂了, 从机不会升级成主机. 只能通过⼈⼯⼲预的⽅式恢复.
问题补充-关于从节点何时晋升成主节点的问题
从节点和主节点之间断开连接,有两种情况
- 从节点主动和主节点断开连接
bash
slaveof no one
这个时候,从节点就能够晋升成主节点;意味着我们要主动修改redis的组成结构(拓扑结构)
- 主节点挂了
这个时候,从节点不会晋升成主节点,必须通过人工干预的方式,恢复主节点,这个是脱离我们掌控的(哨兵机制)
问题补充-关于redis主节点无法重启的问题
这里有AOF文件,这个文件是redis服务器启动的时候,需要去加载的,当前这三个redis-server用的是同一个AOF文件,本身不太科学
之前最开始创建从节点的配置文件没有改日志,就导致生成AOF文件的路径/文件名是同一个
通过service redis-server start启动的服务器是通过一个redis这样的用户,来启动的(所属的用户是redis用户)主要是怕,通过root启动的redis,权限太高,一旦redis被黑客攻破了,后果就会比较严重
redis-server需要按照可读可写的方式打开这个AOF文件,而这个文件对于root之外的用户只有读权限;因此,service redis-server start启动的redis服务器无法打开这个文件,就启动失败了
解决方案:把三个redis服务器生成的文件,也给区分开,更靠谱的是,直接把三个redis服务器的工作目录区分开(修改配置文件中的dir选项)
- 停止之前的redis服务器
- 删除之前工作目录下已经生成的AOF文件,或者也可以通过chown命令修改AOF文件所属目录
- 给从节点创建出新的目录用来作为从节点的工作目录,并且修改从节点的配置文件,设定成新的目录为工作目录
- 启动redis服务器
这就是主从复制啦!!