当前位置: 首页 > news >正文

四、Redis主从复制与读写分离

一、环境搭建

准备环境

IP角色
192.168.10.101Master
192.168.10.102Slave
192.168.10.103Slave

创建配置/数据/日志目录

# 创建配置目录
mkdir -p /usr/local/redis/conf
# 创建数据目录
mkdir -p /usr/local/redis/data
# 创建日志目录
mkdir -p /usr/local/redis/log

修改配置文件

创建一份配置文件至 conf 目录。

vim /usr/local/redis/conf/redis.conf

修改三个 Redis 节点配置文件中以下内容:

# 放行访问IP限制
bind 0.0.0.0
# 后台启动
daemonize yes
# 日志存储目录及日志文件名
logfile "/usr/local/redis/log/redis.log"
# rdb数据文件名
dbfilename dump.rdb
# aof模式开启和aof数据文件名
appendonly yes
appendfilename "appendonly.aof"
# rdb数据文件和aof数据文件的存储目录
dir /usr/local/redis/data
# 设置密码
requirepass 123456
# 从节点访问主节点密码(必须与 requirepass 一致)
masterauth 123456
# 从节点只读模式
replica-read-only yes

在从节点中额外添加以下内容:

# 下面的配置无需在主节点中配置
# 从节点属于哪个主节点
slaveof 192.168.10.101 6379

启动

三个节点分别运行以下命令:

/usr/local/redis/bin/redis-server /usr/local/redis/conf/redis.conf

检查

每个节点自带的客户端连接至 Redis 服务。

/usr/local/redis/bin/redis-cli

通过 info replication 查看主从信息,检查环境是否搭建成功。

127.0.0.1:6379> info replication

然后在主节点插入一条数据,测试从节点是否可读取(是否复制成功),测试从节点是否可写数据(从节点只读模式是否生效)。

二、原理剖析

复制配置

配置基本的 Redis 复制功能是很简单的:只需要将以下内容加进 slave 的配置文件:

# 从节点属于哪个主节点,从哪个主节点进行复制
slaveof 192.168.10.101 6379

info replication讲解

主节点
127.0.0.1:6379> info replication
# Replication
# 角色
role:master
# 从节点的连接数
connected_slaves:2
# 从节点详细信息 IP PORT 状态 命令(单位:字节长度)偏移量 延迟秒数
# 主节点每次处理完写操作,会把命令的字节长度累加到master_repl_offset中。
# 从节点在接收到主节点发送的命令后,会累加记录子什么偏移量信息slave_repl_offset,同时,也会每秒钟上报自身的复制偏移量到主节点,以供主节点记录存储。
# 在实际应用中,可以通过对比主从复制偏移量信息来监控主从复制健康状况。
slave0:ip=192.168.10.102,port=6379,state=online,offset=23866,lag=0
slave1:ip=192.168.10.103,port=6379,state=online,offset=23866,lag=0
# master启动时生成的40位16进制的随机字符串,用来标识master节点
master_replid:acc2aaa1f0bb0fd79d7d3302f16bddcbe4add423
master_replid2:0000000000000000000000000000000000000000
# master 命令(单位:字节长度)已写入的偏移量
master_repl_offset:23866
second_repl_offset:-1
# 0/1:关闭/开启复制积压缓冲区标志(2.8+),主要用于增量复制及丢失命令补救
repl_backlog_active:1
# 缓冲区最大长度,默认 1M
repl_backlog_size:1048576
# 缓冲区起始偏移量
repl_backlog_first_byte_offset:1
# 缓冲区已存储的数据长度
repl_backlog_histlen:23866
从节点
127.0.0.1:6379> info replication
# Replication
# 角色
role:slave
# 主节点详细信息
master_host:192.168.10.101
master_port:6379
# slave端可查看它与master之间同步状态,当复制断开后表示down
master_link_status:up
# 主库多少秒未发送数据到从库
master_last_io_seconds_ago:1
# 从服务器是否在与主服务器进行同步 0否/1是
master_sync_in_progress:0
# slave复制命令(单位:字节长度)偏移量
slave_repl_offset:24076
# 选举时,成为主节点的优先级,数字越大优先级越高,0 永远不会成为主节点
slave_priority:100
# 从库是否设置只读,0读写/1只读
slave_read_only:1
# 连接的slave实例个数
connected_slaves:0
# master启动时生成的40位16进制的随机字符串,用来标识master节点
master_replid:acc2aaa1f0bb0fd79d7d3302f16bddcbe4add423
# slave切换master之后,会生成了自己的master标识,之前的master节点的标识存到了master_replid2的位置
master_replid2:0000000000000000000000000000000000000000
# master 命令(单位:字节长度)已写入的偏移量
master_repl_offset:24076
# 主从切换时记录主节点的命令偏移量+1,为了避免全量复制
second_repl_offset:-1
# 0/1:关闭/开启复制积压缓冲区标志(2.8+),主要用于增量复制及丢失命令补救
repl_backlog_active:1
# 缓冲区最大长度,默认 1M
repl_backlog_size:1048576
# 缓冲区起始偏移量
repl_backlog_first_byte_offset:1
# 缓冲区已存储的数据长度
repl_backlog_histlen:24076

日志查看

tail -f -n 1000 /usr/local/redis/log/redis.log
# 准备就绪,接受客户端连接
* Ready to accept connections
# 102 从节点发起 SYNC 请求
* Replica 192.168.10.102:6379 asks for synchronization
# 全量
# 从节点发起全量复制请求
* Full resync requested by replica 192.168.10.102:6379
# 创建 repl_backlog 文件及生成 master_replid
* Replication backlog created, my new replication IDs are 'acc2aaa1f0bb0fd79d7d3302f16bddcbe4add423' and '0000000000000000000000000000000000000000'
# 通过 BGSAVE 指令将数据写入磁盘(RBD操作)
* Starting BGSAVE for SYNC with target: disk
# 开启一个子守护进程执行写入
* Background saving started by pid 1377
# 数据已写入磁盘
* DB saved on disk
# 有 4MB 数据已写入磁盘
* RDB: 4 MB of memory used by copy-on-write
# 保存结束
* Background saving terminated with success
# 从节点同步数据结束
* Synchronization with replica 192.168.10.102:6379 succeeded
# 103 从节点发起 SYNC 请求,执行同步数据操作
* Replica 192.168.10.103:6379 asks for synchronization
# 从节点发起全量复制请求
* Full resync requested by replica 192.168.10.103:6379

# 增量
# 当前一个客户端连接,执行了两个复制
1 clients connected (2 replicas), 1955144 bytes in use

复制流程

Redis的主从结构可以采用一主多从或者级联结构,Redis主从复制可以根据是否是全量分为全量同步和增量同步

主从刚刚连接的时候,进行全量同步;全同步结束后,进行增量同步。

  • 全量同步

    • Redis全量复制一般发生在Slave初始化阶段,这时Slave需要将Master上的所有数据都复制一份
    • 从服务器连接主服务器,发送SYNC命令;
    • 主服务器接收到SYNC命名后,开始执行BGSAVE命令生成RDB文件并使用缓冲区记录此后执行的所有写命令;
    • 主服务器BGSAVE执行完后,向所有从服务器发送快照文件,并在发送期间继续记录被执行的写命令;
    • 从服务器收到快照文件后丢弃所有旧数据,载入收到的快照;
    • 主服务器快照发送完毕后开始向从服务器发送缓冲区中的写命令;
    • 从服务器完成对快照的载入,开始接收命令请求,并执行来自主服务器缓冲区的写命令;

  • 增量同步

    • Redis增量复制是指Slave初始化后开始正常工作时主服务器发生的写操作同步到从服务器的过程。
    • 增量复制的过程主要是主服务器每执行一个写命令就会向从服务器发送相同的写命令,从服务器接收并执行收到的写命令。
  • 主从复制的异步特性

    • 主从复制对于主redis服务器来说是非阻塞的
      • 这意味着当从服务器在进行主从复制同步过程中,主redis仍然可以处理外界的访问请求;
    • 主从复制对于从redis服务器来说也是非阻塞的
      • 这意味着,即使从redis在进行主从复制过程中也可以接受外界的查询请求,只不过这时候从redis返回的是以前老的数据

Redis 复制如何处理 key 的过期

slave 不会让 key 过期,而是等待 master 让 key 过期。当一个 master 让一个 key 到期时,它会合成一个 DEL 命令并传输到所有的 slave。包括源码中也体现了非主库不删:

int expireIfNeeded(redisDb *db, robj *key) { 
 time_t when = getExpire(db,key); 
 
 if (when < 0) return 0; /* No expire for this key */ 
 
 /* Don't expire anything while loading. It will be done later. */ 
 if (server.loading) return 0; 
 
 /* If we are running in the context of a slave, return ASAP: 
 * the slave key expiration is controlled by the master that will 
 * send us synthesized DEL operations for expired keys. 
 * 
 * Still we try to return the right information to the caller, 
 * that is, 0 if we think the key should be still valid, 1 if 
 * we think the key is expired at this time. */ 
 if (server.masterhost != NULL) { 
 return time(NULL) > when; 
 } 
 
 /* Return when this key has not expired */ 
 if (time(NULL) <= when) return 0; 
 
 /* Delete the key */ 
 server.stat_expiredkeys++; 
 propagateExpire(db,key); 
 return dbDelete(db,key); 
}

无需磁盘参与的复制

正常情况下,一个全量重同步要求在磁盘上创建一个 RDB 文件,然后将它从磁盘加载进内存,然后 slave以此进行数据同步。

如果磁盘性能很低的话,这对 master 是一个压力很大的操作。Redis 2.8.18 是第一个支持无磁盘复制的版本。在此设置中,子进程直接发送 RDB 文件给 slave,无需使用磁盘作为中间储存介质。

# 默认是关闭的,使用的时候将 no 改为 yes 即可。
# 最终都会把RBD快照文件发送给丛节点,开启以后会不写入磁盘直接发送,关闭以后先写入磁盘再发送快照,默认关闭。
repl-diskless-sync no

三、故障解决方案

数据一致性

主从数据不一致

主从数据不一致大方向分为两种,主多从少和主少从多。

主多从少解决方案:部分重同步。可以通过命令 PSYNC master_run_id offset 执行。

主少从多解决方案:全量复制,覆盖。这种情况是因为从节点读写模式导致的,关闭从节点读写模式,或者删除从节点数据,重新从主节点全量复制。

数据脏读
脏数据产生的原因

读到过期数据

读到过期数据的原因是因为 Redis 的删除策略导致的,删除策略主要有以下几种:

  • **惰性删除:**master节点每次读取命令时都会检查键是否超时,如果超时则执行del命令删除键对象,之后异步把del命令slave节点,这样可以保证数据复制的一致性,slave节点是永远不会主动去删除超时数据的。
  • **定时删除:**Redis的master节点在内部定时任务,会循环采样一定数量的键,当发现采样的键过期时,会执行del命令,之后再同步个slave节点。
  • 主动删除:当前已用内存超过maxMemory限定时,触发主动清理策略。主动设置的前提是设置了maxMemory的值

注:如果数据大量超时,master节点采样速度跟不上过期的速度,而且master节点没有读取过期键的操作,那slave节点是无法收到del命令的,这时从节点上读取的数据已经是超时的了。

从节点可写

如果从节点是读写模式的话,可能误写入从节点的数据后期会成为脏数据。

解决方案

  • 忽略
  • 选择性强制读主,从节点简介变为了备份服务器(某个业务)。
  • 从节点只读
  • Redis3.2版本中已经解决了 Redis 删除策略导致的过期数据,在此版本中slave节点读取数据之前会检查键过期时间来决定是否返回数据的。

数据延迟

Redis复制数据的延迟,是由于复制的异步特性导致的,因此无法避免。但是延迟主要是取决于网络带宽和命令阻塞的情况而定,比如master节点刚写入数据,在slave节点上是可能读取不到数据的。

编写外部监控程序

在大量延迟的场景下,可以编写外部程序监听主从节点的复制偏移量,延迟较大时发出报警或通知,实现方式如下:

  • 对于具体延迟,监控程序可通过检查 info replication 的 offset 指标记录,从节点的偏移量可以查询主节点的offset指标,它们的差值就是主从延迟的字节量。
  • 如果字节量过高,监控程序触发客户端通知。
  • 客户端接收通知后,修改读命令路由到主节点或其他从节点上,当延迟恢复后,再通知客户端。

修改从节点参数配置

从节点的 slave-serve-stale-data 参数也与此有关,它控制这种情况下从节点的表现 当从库同主机失去连接或者复制正在进行,从机库有两种运行方式:

  1. 如果slave-serve-stale-data设置为yes(默认设置),从库会继续响应客户端的请求。
  2. 如果slave-serve-stale-data设置为no,除去INFO和SLAVOF命令之外的任何请求都会返回一个错误”SYNC with master in progress”。

数据安全性

当 master 关闭持久化时,复制的安全性

在使用 Redis 复制功能时的设置中,强烈建议在 master 和在 slave 中启用持久化。当不可能启用时,例如由于非常慢的磁盘性能而导致的延迟问题,应该禁用主节点自动重启功能

为了更好地理解为什么关闭了持久化并配置了自动重启的 master 是危险的,通过以下步骤来详细说明:

  1. 我们设置节点 A 为 master 并关闭它的持久化设置,节点 B 和 C 从 节点 A 复制数据。
  2. 节点 A 崩溃,但是他有一些自动重启的系统可以重启进程。但是由于持久化被关闭了,节点重启后其数据集合为空。
  3. 节点 B 和 节点 C 会从节点 A 复制数据,但是节点 A 的数据集是空的,因此复制的结果是它们会销毁自身之前的数据副本。

规避全量复制

第一次全量复制

首先,我们知道,redis复制有全量复制和部分复制两种,而全量复制的开销是很大的。那么我们来看看,如何尽量去规避全量复制。

当我们某一台slave第一次去挂到master上时,是不可避免要进行一次全量复制的,那么,我们如何去想办法降低开销呢?

解决方案:既然第一次不可以避免,那我们可以选在集群低峰的时间(凌晨)进行slave的挂载。

节点RunID不匹配

例如我们主节点重启(RunID发生变化),对于slave来说,它会保存之前master节点的RunID,如果它发现了此时master的RunID发生变化,就会采取一次全量复制。

解决办法:对于这类问题,我们只有是做一些故障转移的手段,例如master发生故障宕掉,我们选举一台slave晋升为master,减少全量复制的产生。(哨兵)

复制积压缓冲区不足

master生成RDB同步到slave,slave加载RDB这段时间里,master的所有写命令都会保存到一个复制缓冲队列里(如果主从直接网络抖动,进行部分复制也是走这个逻辑),待slave加载完RDB后,拿offset的值到这个队列里判断,如果在这个队列中,则把这个队列从offset到末尾全部同步过来,这个队列的默认值为1M。而如果发现offset不在这个队列,就会产生全量复制。

解决办法:增大复制缓冲区的配置 rel_backlog_size 默认1M,我们可以设置大一些,从而来加大我们offset的命中率。这个值,我们可以假设,一般我们网络故障时间一般是分钟级别,那我们可以根据我们当前的QPS来算一下每分钟可以写入多少字节,再乘以我们可能发生故障的分钟就可以得到我们这个理想的值。

规避复制风暴

什么是复制风暴?举例:我们master重启,其master下的所有slave检测到RunID发生变化,导致所有从节点向主节点做全量复制。尽管redis对这个问题做了优化,即只生成一份RDB文件,但需要多次传输,仍然开销很大。

单主节点复制风暴:主节点重启,多从节点全量复制

但节点复制风暴,一般是发生在主节点挂在多个从节点的场景下。当主节点重启恢复后,从节点发起全量复制流程,此时主节点会为从节点创建RDB快照,如果在快照创建完毕之前,有多个从节点尝试与主节点进行全量同步,那么其他的从节点将共享这份RDB快照。这方面Redis做了相关优化,有效的避免了创建多个快照。但是同时像多个从节点发送快照,可能会使主节点的网络带宽消耗严重,造成主节点延迟变大,极端情况会出现主从断开,导致复制失败。

解决方案:(1). 减少主节点挂载从节点的数量,或者采用树桩复制结构。(2). 当master发生故障宕掉,我们选举一台slave晋升为master,减少全量复制的产生。(哨兵)

单机复制风暴

由于 Redis 的单线程架构,通常会在一台物理机上部署多个Redis实例。如果这台机器出现故障或网络长时间中断,当他重启恢复后,会有大量从节点针对这台机器的主节点进行全量复制,会造成当前机器带宽耗尽。

解决方案:(1). 应当把主节点尽量分散在多台机器上,避免在单台机器上部署过多的主节点。(2). 当主节点所在机器故障后提供故障恢复转移机制,避免机器恢复后进行密集的全量复制。

相关文章:

  • nvidia驱动更新,centos下安装openwebui+ollama(非docker)
  • 面试基础---ConcurrentHashMap vs HashMap
  • 网络运维学习笔记(DeepSeek优化版)003网工初级(HCIA-Datacom与CCNA-EI)命令入门
  • 【paddle】详解 padde.autograd.backward
  • 人工智能发展全景与DeepSeek-R1
  • 设计模式Python版 备忘录模式
  • uni-app集成sqlite
  • Vue组件间通信的方式
  • 升级Office软件后,Windows 系统右键里没有新建Word、Excel、PowerPoint文件的解决办法
  • OA办公系统自动渗透测试过程
  • 树和二叉树
  • 线性回归 (Linear Regression)案例分析1
  • 数据处理(二)| 打磨数据,提升模型:全面解读图像数据质量评估
  • 在自己的数据上复现一下LlamaGen
  • 开发HarmonyOS NEXT版五子棋游戏实战
  • 【Linux】vim 设置
  • 深入理解 Linux 中的 last 和 lastb 命令
  • OpenGL 04--GLSL、数据类型、Uniform、着色器类
  • Unity XR-XR Interaction Toolkit开发使用方法(十一)组件介绍(XR Interactable)
  • 在单位,领导不说,但自己得懂的7个道理
  • 奢侈品网站建设方案/关键词林俊杰歌词
  • 湖南建设集团网站/2023年10月疫情还会严重吗
  • 建设网站的网站公司/钦州seo
  • 佛山提供网站设计方案公司/泰安seo排名
  • 网站建设创新/北京网站推广助理
  • 南宁网站制作价格/整站优化seo