【云原生进阶之PaaS中间件】第一章Redis-2.3.1主从复制部署模式

2023-10-16 12:38:19 浏览数 (2)

 1 部署架构

        Redis在日常部署的时候,可以有多种部署模式:单机、主从、哨兵、集群(分区分片),因此本文将对上面这四种模式进行详细的讲解,特别是集群模式将进行最细致的讲解(现行普遍使用的方式)。

1.1 单机模式

 单机部署很简单,直接下载Redis进行安装即可,此处不作详细讲解,具体Redis的安装请参考:Mac下安装Redis及Redis Desktop Manager,Windows以及Linux下的安装没啥不同。

  单机模式部署有自己的优缺点,可以根据自己需要进行使用,优点如下:

  • 架构简单,部署方便;
  • 高性价比:缓存使用时无需备用节点(单实例可用性可以用supervisor或crontab保证),当然为了满足业务的高可用性,也可以牺牲一个备用节点,但同时刻只有一个实例对外提供服务;
  • 高性能。

  缺点如下:

  • 不保证数据的可靠性;
  • 在缓存使用,进程重启后,数据丢失,即使有备用的节点解决高可用性,但是仍然不能解决缓存预热问题,因此不适用于数据可靠性要求高的业务;
  • 高性能受限于单核CPU的处理能力(Redis是单线程机制),CPU为主要瓶颈,所以适合操作命令简单,排序、计算较少的场景。也可以考虑用Memcached替代。

        为了避免单点Redis服务器故障,准备多台服务器,互相连通。将数据复制多个副本保存在不同的服务器上,连接在一起,并保证数据同步。这样即使是有一台服务器宕机,其他服务依然可以提供服务。实现Redis的高可用。同时实现数据的冗余备份。这种形式也就是主从复制架构。

1.2 主从模式

        Redis 多副本,采用主从(replication)部署结构,相较于单副本而言最大的特点就是主从实例间数据实时同步,并且提供数据持久化和备份策略。主从实例部署在不同的物理服务器上,根据公司的基础环境配置,可以实现同时对外提供服务和读写分离策略。

        具体原理就是,部署多台redis节点,其中只有一台节点是主节点(master),其他的节点都是从节点(slave),也叫备份节点(replica)。只有master节点提供数据的事务性操作(增删改),slave节点只提供读操作。所有slave节点的数据都是从master节点同步过来的。该模式的架构图如下:

        该图只是最简单的一种主从结构方式,所有的slave节点都挂在master节点上,这样做的好处是slave节点与master节点的数据延迟较小;缺点是如果slave节点数量很多,master同步一次数据的耗时就很长。针对这一问题,可以使用下图中的主从架构:

         master下面只挂一个slave节点,其他的slave节点挂在这个slave节点下面,这样,master节点每次只需要把数据同步给它下面的那一个slave节点即可,后续slave节点的数据同步由这个slave节点完成。这样做虽然降低了master节点做数据同步的压力,但也导致slave节点与master节点数据不一致的延迟更高。

1.2.1 优劣势分析
1.2.1.1 优点
  1. 高可靠性,一方面,采用双机主备架构,能够在主库出现故障时自动进行主备切换,从库提升为主库提供服务,保证服务平稳运行。另一方面,开启数据持久化功能和配置合理的备份策略,能有效的解决数据误操作和数据异常丢失的问题。
  2. 读写分离策略,从节点可以扩展主库节点的读能力,有效应对大并发量的读操作。
1.2.1.2 缺点
  1. 故障恢复复杂,如果没有RedisHA系统(需要开发),当主库节点出现故障时,需要手动将一个从节点晋升为主节点,同时需要通知业务方变更配置,并且需要让其他从库节点去复制新主库节点,整个过程需要人为干预,比较繁琐。
  2. 主库的写能力受到单机的限制,可以考虑分片。
  3. 主库的存储能力受到单机的限制,可以考虑Pika。
  4. 原生复制的弊端在早期的版本也会比较突出,如:Redis复制中断后,Slave会发起psync,此时如果同步不成功,则会进行全量同步,主库执行全量备份的同时可能会造成毫秒或秒级的卡顿;又由于COW机制,导致极端情况下的主库内存溢出,程序异常退出或宕机;主库节点生成备份文件导致服务器磁盘IO和CPU(压缩)资源消耗;发送数GB大小的备份文件导致服务器出口带宽暴增,阻塞请求。建议升级到最新版本。

2 主从模式原理

        从主从模式的架构图可以看出,主从模式的核心就是master节点与slave节点之间的数据同步。需要注意的是,Redis和大部分中间件的主从模式中的数据同步都是由slave节点主动发起的,原因是主从模式中只有一个master节点,剩余的全是slave节点,如果由master节点主动推送数据到各个slave节点,首先维护成本太大,master节点上要维护所有slave的地址信息,而且在增加slave节点的时候,也要同步维护到master上,这样master才能将数据同步到所有的slave上面;其次master性能受影响,节点之间同步数据肯定要通过网络传输数据,由master节点建立所有slave节点的连接会对master的性能产生较大影响。而由slave发起数据同步则避免了上述问题,只需在每个slave中维护一个master的地址即可。

2.1 数据同步模式

        Redis中主从节点数据同步有两种方式:全量数据同步和增量数据同步。

2.1.1 全量数据同步

        全量数据同步一般发生在slave节点初始化阶段,需要将master上的所有数据全部复制过来。全量同步的流程图如下:

  1. slave节点根据配置的master节点信息,连接上master节点,并向master节点发送SYNC命令;
  2. master节点收到SYNC命令后,执行BGSAVE命令异步将内存数据生成到rdb快照文件中,同时将生成rdb文件期间所有的写命令记录到一个缓冲区,保证数据同步的完整性;
  3. master节点的rdb快照文件生成完成后,将该rdb文件发送给slave节点;
  4. slave节点收到rdb快照文件后,丢弃所有内存中的旧数据,并将rdb文件中的数据载入到内存中;
  5. master节点将rdb快照文件发送完毕后,开始将缓冲区中的写命令发送给slave节点;
  6. slave节点完成rdb文件数据的载入后,开始执行接收到的写命令。

        以上就是master-slave全量同步的原理,执行完上述动作后,slave节点就可以接受来自用户的读请求,同时,master节点与slave节点进入命令传播阶段,在该阶段master节点会将自己执行的写命令发送给slave节点,slave节点接受并执行写命令,从而保证master节点与slave节点的数据一致性。

2.1.2 增量数据同步

        Redis2.8版本之前,是不支持增量数据同步的,只支持全量同步。增量数据同步是指slave节点初始化完成后,master节点执行的写命令同步到slave节点的过程。该过程比较简单,master节点每执行一个写命令后就会将该命令发送给slave节点执行,从而达到数据同步的目的。

        但有一点需要注意,当增量复制过程中发生了异常导致同步失败时,是要支持断点续传的,也就是在异常恢复之后,是要支持从上次断掉的地方继续同步的,而不是全量数据同步。这就需要在master节点和slave节点分别维护一个复制偏移量(offset),代表master向slave节点同步的字节数。master节点每次向slave节点发送N个字节后,master节点的offset增加N;slave节点每次接收到master节点发送过来的N个字节后,slave节点的offset增加N。master节点和slave节点的这两个偏移量分别保存在master_repl_offset和slave_repl_offset这两个字段中。

2.2 主从复制的工作流程

2.2.1 主从复制的三个阶段
  1. 建立连接阶段工作流程(结果:master中保存slave的IP端口,slave保存master的IP端口,同时之间建立socket连接);
    1. 设置master的地址和端口,保存master信息
      1. slave节点:发送指令 slaveof ip port
      2. master节点:接收到指令,响应slave节点
      3. slave节点:保存master的IP和端口
    2. 建立socket连接(slave节点:根据保存的信息创建和master连接的socket)
    3. 发送ping命令
      1. slave 定时器发送ping指令(测试连接是否断开)
      2. master响应pong指令(回应slave连接未断开)
    4. 身份验证
      1. slave:发送指令 auth password (master设置密码,就需要进行权限校验)
      2. master 验证授权
      3. slave发送自己的端口给到master(master通过slave的端口实现监听)
      4. master保存slave的端口号
代码语言:javascript复制
# redis设置密码的方式和连接方式
# 配置文件方式
master节点:redis.conf ==> requirepass <password>
slave节点:redis.conf  ==>	masterauth <password>
# 命令方式
master:config set requirepass <password>
config get requirepass
slave:auth <password>
客户端:redis-cli -a <password>

2. 数据同步阶段(结果:slave包含有master端的全部数据,master端保存有slave当前数据同步的位置)

  •     1. 请求同步数据(slave发送psync2指令)
代码语言:javascript复制
redis 1.0 :sync 指令
redis 2.8 :psync 指令
redis 4.0 :psync2 指令
      1. slave 发送 psync2 指令
      2. master执行bgsave指令,并且创建一个复制缓冲区
    • 2. 创建RDB文件同步数据
      1. master生成RDB文件,通过socket发送给slave
    • 3. 恢复RDB同步数据
      1. slave节点接收RDB文件,清空原有的slave数据,执行RDB文件恢复过程

        上述的几个步骤统称为“全量复制”

  •         4. 请求部分同步数据
    1. master 发送复制缓冲区中的aof指令
    2. slave发送命令告知RDB恢复已经完成
  •         5. 恢复部分同步数据

                    1. slave接收aof指令,执行 bgrewriteaof,恢复数据

                        “全量复制”后的三步称之为 “增量复制”

    3. 命令传播阶段(实时保证数据同步)

        当master数据库状态被修改后,导致主从服务器数据库状态不一致,此时需要让主从数据同步到一致状态。master将接收到的数据变更命令发送给slave,slave接收命令后执行命令。

  • 1.命令传播阶段中的心跳机制

命令传播阶段,master和slave间的交换信息通过心跳机制进行维护,实现双方连接保持在线:

  •         1. master:通过ping指令判断slave是否在线(周期由:repl-ping-slave-period 决定,默认10s)
    •  2. slave:通过 REPLCONF ACK {offset}指令判断master是否在线,并向master汇报slave自己的偏移量,获取最新的数据变更指令。(周期1s一次)
  • 2. 服务器运行ID(runid): 每台服务器的唯一身份标识(同一个服务器每次运行都有不同的runid),40位字符组成,runid在服务器间传输被用作身份标识,master首次连接slave时,会将自己的runid发送给slave,slave保存runid。
  • 3. 复制缓冲区: 一个FIFO(先进先出)队列,用于存储服务器执行过的命令,每次传播命令,master都会将传播的命令记录下来,并保存在复制缓冲区。

  • 4. 复制缓冲区的组成:
    • 1. 偏移量
    • 2. 字节值
  • 5. 复制缓冲区的内部工作原理:
    • 1. 通过offset区分不同的slave当前数据传播的差异
    • 2. master记录已发送的信息对应的offset (master中会有多个slave节点的偏移量)
    • 3. slave记录已接收的信息对应的offset(一个自己从master节点接收到的偏移量)

        master 通过命令传播程序发送命令到slave节点时,可能会由于网络原因导致一部分slave节点没有接收到命令,通过复制缓冲区保存命令,并通过master节点和slave节点确认偏移量来保证数据的准确同步。

偏移量:同步信息,比对master和slave的差异,当slave断线后,恢复数据使用

2.2.2 主从数据同步三阶段的总结(数据同步阶段的细节)
  • 1. slave节点通过保存的master端口IP和master节点建立socket连接,连接成功之后。
  • 2. slave发送指令
代码语言:javascript复制
psync2	?	-1
# psync2 <runid> <offset>
# slave 节点第一次连接不知道master节点的runid所以为 “?”
# -1 :代表全部
  • 3. master节点接收到指令后,执行bgsave 生成RDB文件,记录当前的复制偏移量 offset。并发送
代码语言:javascript复制
 FULLRESYNC runid offset
#  FULLRESYNC:全量

        通过socket发送RDB文件给到slave(发送RDB文件期间offset可能会发生变化)。

  • 4. slave收到 FULLRESYNC 保存master的runid和offset,清空当前全部数据,通过socket接收RDB文件,恢复RDB文件(2,3,4为全量复制的过程)
  • 5. slave发送命令
代码语言:javascript复制
psync2 runid offset
  • 6. master 接收命令,判断runid是否匹配,判定offset是否在复制缓冲区中
  • 7. master判断如果 runid和offset有一个不满足,执行全量复制(回退到2节点)
  • 8. master判断如果 runid和offset校验通过,master.offset 和 slave.offset 相同,忽略
  • 9. master判断如果 runid和offset校验通过,master.offset 和 slave.offset 不相同,发送
代码语言:javascript复制
 CONTINUE offset

        通过socket发送复制缓冲区中的 master.offset 到 slave.offset 的数据。

  • 10. slave 收到 CONTINUE 保存master的offset,接收信息后,执行 bgwriteaof 恢复数据(5,6,7,8,9,10 为增量复制的过程)
  • 11. slave发送心跳命令 replconf ack offset
  • 12. master判断如果 offset 不满足,执行全量复制(回退到2节点)
  • 13. master判断如果 offset校验通过,master.offset 和 slave.offset 相同,忽略
  • 14. master判断如果 offset校验通过,master.offset 和 slave.offset 不相同,发送
代码语言:javascript复制
 CONTINUE offset

        通过socket发送复制缓冲区中的 master.offset 到 slave.offset 的数据。

  • 15. slave 收到 CONTINUE 保存master的offset,接收信息后,执行 bgwriteaof 恢复数据
2.2.3 主从复制数据同步阶段需要注意的问题
  • 1. 如果master数据量巨大,数据同步阶段应该避开流量高峰期,避免造成master阻塞,影响业务正常运行。
  • 2. 复制缓冲区大小设定不合理,会导致数据溢出(数据丢失)。如进行全量复制周期太长,进行部分复制时发现数据已经丢失的情况,则必须进行第二次全量复制,致使slave陷入死循环。
  • 3. 数据溢出就会造成复制缓冲区中的数据不完整(数据丢失),不完整的情况下,master和slave进行增量同步时,就不能正常完成。在增量同步不能正常完成的情况下,slave和master会再次进行全量复制。全量复制的过程中,又有大量的信息进入到复制缓冲区导致数据溢出,就会进入一个死循环。
代码语言:javascript复制
# 调整复制缓冲区的大小
master节点: repl-backlog-size 1mb
  • 4. 多个slave节点同时对master请求数据同步,master发送的RDB文件增多,会对带宽造成巨大冲击,如果master带宽不足,需要适当错峰。
  • 5. slave节点过多时,可以调整拓扑结构变为树状结构,中间的节点既是master又是slave节点(采用树状结构时,由于层级关系,导致深层次的slave节点与最顶层master数据同步延迟较大,数据一致性会变差)。
2.2.4 主从复制的配置(如何配置主从)

        连接slave节点,在slave节点的redis.conf配置文件中添加一行配置:

代码语言:javascript复制
slaveof <masterip> <masterport>
# masterip: master节点的ip
# masterport: master节点的port
2.2.5 redis.conf主要配置说明
代码语言:javascript复制
###网络相关###
# bind 127.0.0.1 # 绑定监听的网卡IP,注释掉或配置成0.0.0.0可使任意IP均可访问
protected-mode no # 关闭保护模式,使用密码访问
port 6379  # 设置监听端口,建议生产环境均使用自定义端口
timeout 30 # 客户端连接空闲多久后断开连接,单位秒,0表示禁用

###通用配置###
daemonize yes # 在后台运行
pidfile /var/run/redis_6379.pid  # pid进程文件名
logfile /usr/local/redis/logs/redis.log # 日志文件的位置

###RDB持久化配置###
save 900 1 # 900s内至少一次写操作则执行bgsave进行RDB持久化
save 300 10
save 60 10000 
# 如果禁用RDB持久化,可在这里添加 save ""
rdbcompression yes #是否对RDB文件进行压缩,建议设置为no,以(磁盘)空间换(CPU)时间
dbfilename dump.rdb # RDB文件名称
dir /usr/local/redis/datas # RDB文件保存路径,AOF文件也保存在这里

###AOF配置###
appendonly yes # 默认值是no,表示不使用AOF增量持久化的方式,使用RDB全量持久化的方式
appendfsync everysec # 可选值 always, everysec,no,建议设置为everysec

###设置密码###
requirepass 123456 # 设置复杂一点的密码
2.2.6 主从复制模式常见问题
2.2.6.1 全量复制问题

1. master节点重启,runid发生变化,带来的全量复制问题。

        解决方案:(master内部的优化方案,不需要手动操作)master内部创建 master_replid 变量,在master关闭时执行shutdown save 命令。

  • master_replid : 使用 runid 想用的策略生成,长度41 位,并发送给所有的slave节点(通过 redis-check-rdb [dump文件名] 命令可以查看该信息)
  • shutdown save 命令:关闭时进行RDB持久化,将runid与offset 保存到RDB文件中,重启时加载RDB文件,恢复数据。

2. 频繁全量复制,缓冲区设置太小导致 (调整缓冲区大小 通过repl-backlog-size命令)

        如果从服务器断线重连,会根据新的主服务器id和之前的主服务器id进行对比来判定是否是之前的主服务器来选择进行完整重同步还是部分重同步。

2.2.6.2 节点间数据不一致

        由于网络原因导致slave节点间的数据不一致(slave 中的偏移量不相同)(分布式环境中很常见的问题,是否需要解决看业务容忍度),可以通过优化主从间的网络环境解决,同机房部署。或者通过监控 主从节点的延迟(比较slave节点的offset差异),暂时屏蔽延迟大的slave节点。(通过 slave-serve-stale-data yes|no ,这个命令执行后,slave节点的数据就不能访问了。)

参考链接

运维:终于不用再背着数万实例的Redis集群了!_Cluster

Redis学习之4种模式实践及机制解析(单机、主从、哨兵、集群)

redis架构_剑八-的博客-CSDN博客

Redis高可用方案—主从(masterslave)架构

Redis高可用架构—哨兵(sentinel)机制详细介绍

Redis高可用架构—Redis集群(Redis Cluster)详细介绍

Redis学习之Redis集群模式缺陷及其处理

  • Redis学习之4种模式实践及机制解析(单机、主从、哨兵、集群)
  • Redis学习之API学习及Jedis源码原理分析
  • Redis学习之Jedis源码原理分析探究(BIO手写Jedis客户端)

0 人点赞