try redis (五) -- master -slave 主从复制

本文深入探讨Redis的复制机制,包括其非阻塞性特性、安全性考量及如何在不同场景下进行复制与数据恢复。通过实验验证了Redis复制的可靠性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

参考官网原文:http://redis.io/topics/replication

 参考文章 :http://in.sdo.com/?p=1187


注意事项

Redis 的复制说白了就是通过配置 将slave  当做 masters 的复制。但是有有几个需要注意点:

1、Redis使用的是一部复制。从Redis 2.8开始,slaves 将定期确认从复制流中处理的数量

2、一个master 可以有多个slavers 

3、slaves可以接受其他slaves 的连接。

4、Redis的复制是不会阻塞master 的,也就是所谓的 non-blocking.这就意味着当一个或者多个slaves 在处理最初的同步时,master可以继续他的查询操作等。

5、为了有多个只读slaves,可以用俩做一些伸缩性的复制。

6、复制操作有可能也可以避免Redis将所有的数据集写入disk的损失。


master持久化变与复制的安全性

当 Redis 复制使用的时候,它是强烈建议master的持久化是开启的,当没法开启持久化比如需要延迟,Redis 需要配置成 禁止自动重启

为了更好的 理解为什么master 在自动重启的 情况下,关闭持久化配置是危险的。可能造成数据从master 和所有slaves 中擦除,我们看一个例子

1、我们启动nodeA 作为master,同时设置A 为持久化 turned down 。node B、C 从A 中复制数据

2、如果A 突然挂了,然后可以重启,但是由于没有开启持久化。重启后数据集为空

3、这个似乎 B 和 C将从 A 中复制空的数据集,这样原来的一些老的数据就被擦除了。

当 redis 系统为了提高可用率,会将master的持久化给关闭。但是任何时候,数据安全都是最重要的,复制功能在没有配置持久化,同时又自动重启的机器中是不可取的。



Reids 复制时怎么工作的

当启动一个slave ,连接后他会发送一个SYNC命令,在它第一次连接或者重新连接master的时候,这个都是正常的。

master 开始后台进行保存操作,同时开始开始缓存所有收到的改变数据集的命令。当后台保存成功,master将 保存在硬盘上的 数据集文件(dump.rdb) 读入到内存中 ,发送给slave。然后master将发送所有缓存命令,这好比是一个命令流,是redis协议本身实现的

Slaves 可以自动冲洗连接。当 master <-> salve 连接因为某些原因中断的时候。当master收到多个并发 slave 同步请求的时候,会有一个单独的线程专门来保存他们。


同时在网上找打一个 比较好的一个状态流程的解释


  1. Slave端在配置文件中添加了slave of指令,于是Slave启动时读取配置文件,初始状态为REDIS_REPL_CONNECT。
  2. Slave端在定时任务serverCron(Redis内部的定时器触发事件)中连接Master,发送sync命令,然后阻塞等待master发送回其内存快照文件(最新版的Redis已经不需要让Slave阻塞)。
  3. Master端收到sync命令简单判断是否有正在进行的内存快照子进程,没有则立即开始内存快照,有则等待其结束,当快照完成后会将该文件发送给Slave端。
  4. Slave端接收Master发来的内存快照文件,保存到本地,待接收完成后,清空内存表,重新读取Master发来的内存快照文件,重建整个内存表数据结构,并最终状态置位为 REDIS_REPL_CONNECTED状态,Slave状态机流转完成。
  5. Master端在发送快照文件过程中,接收的任何会改变数据集的命令都会暂时先保存在Slave网络连接的发送缓存队列里(list数据结构),待快照完成后,依次发给Slave,之后收到的命令相同处理,并将状态置位为 REDIS_REPL_ONLINE。

状态流程图如下:



redis.conf 关于  配置


port 6379

指定Redis监听端口,默认端口为6379

################################# REPLICATION ###################################

  maseter-slave 复制 需要注意几个点

1、redis 复制时一部实现的,但是你可以配置 master 在不能连接到一个给定数量的slaves 的时候,禁止写入

2、redis slaves 可以局部的再同步数据到master 如果master突然断掉。然后启动。

3、复制时自动进行的。不需要用户调用。如果slavers 与master 无法连接。slaves 会尝试连接master.同时再同步slaves上的数据到master上

######################################################################################


slaveof <masterId> <masterPort>

如果配置了slaveof 属性,意味着当前节点是 slave。同时确定了master的ip 和 端口


slave-serve-stale-data yes

当 slaves 和 master 失去联系或者 复制数据工作仍然在进行。这个适合slave 会有两种选择
 当配置 yes(默认的) 意味着slave 会反馈 客户端的请求
 当配置 no 客户端会反馈一个error "SYNC with master in progress" ,如果master 无法连接上,则会报"MASTERDOWN Link with MASTER is down and slave-serve-stale-data is set to 'no'."


slave-read-only yes

用官方的话说,就是保护slave ,不让它暴露在不受信任的客户端上。一般用来当做保护层,尤其在果断的client的时候。

当配置yes(默认)的时候,意味着客户端没法给slave 节点写入数据,一写就会报错"READONLY You can't write against a read only slave."

当配置成 no 的时候,任何客户端都可以写入


 masterauth

当master服务设置了密码保护时,slave服务连接master的密码


# requirepass foobared

设置Redis连接密码,如果配置了连接密码,客户端在连接Redis时需要通过AUTH 命令提供密码,默认关闭


开始测试


感觉基本的属性就这些,下面感觉自己要开始做实验了。由于手头就两台机器,就准备搭建个三个节点的测试环境

masdter: A(10.13.145.36:6379)

slaves: B(10.13.145.35:7000)、C (10.13.145.36:7001)                               ---特别说明我的slaves放在一台机器上,起多个server服务模拟


A(10.13.145.36)就不用配置,全部使用默认配置即可

B的集群配置文件(redis_b.conf)如下:

...
daemonize yes   #后台启动
port 7000        #服务端口
slaveof 10.13.145.36 6379 #配置master A
slave-serve-stale-data yes
slave-read-only yes
...

启动 redis-server实例      ./redis-server redis_b.conf 


同理C 的配置如下:                                     

C的集群配置文件(redis_c.conf)如下:                                 --C的配置需要注意,我把slave-read-only 设置成 no 了

...
daemonize yes   #后台启动
port 7001        #服务端口
slaveof 10.13.145.36 6379 #配置master A
slave-serve-stale-data yes
slave-read-only no
...

启动 redis-server实例      ./redis-server redis_c.conf 


好。到此为止,所有的准备工作都 OK了。剩下的就是我们来测试 和验证下 Redis 的复制和替换功能。

有几种场景,我们都可以假设下,并且开始做实验。

一、A(master) 挂了。从B 、C 上是否可以取到数据

1在A(master) 中写入 key1 的数据

10.13.145.36:6379> SET key1 data1
OK
10.13.145.36:6379> get key1 
"data1"
2、这个时候,终止A 服务,然后开始从 B、C 上取值

从B上取值:

10.13.145.35:7000> get key1 
"data1"

从C 上取值:

10.13.145.36:7000> get key1 
"data1"
3、得出结论:master 挂了。还是可以从 B、C 上取到值


二、A(master) 挂了。从C写入数据data。看是否会复制到B。同时 A(master)又起来了。是否A上是否能查到data. 这时候 B上能否查到data

1、 停掉A,

2、在C中写入数据

10.13.145.35:7000> SET key2 data2
OK
10.13.145.35:7000> get key2
"data1"
3、启动A(master) 服务,从C 中读入数据 key2

10.13.145.35:7000> get key2
(nil)


三、A (master)一直ok ,然后B 挂了。给A 写入 data。然后B起来,看B上是否有data.

这个就不做测试了。slave每次启动发送 SYNC 命令,肯定会有数据


总结

  1. Redis的复制功能没有增量复制,每次重连都会把主库整个内存快照发给从库,所以需要避免向在线服务的压力较大的主库上增加从库。
  2. 启动时,resdis slaves 每秒都会ping master .确认复制流的进度.而master 每次都会记录每个slaver 最后 ping 的时间
  3. 自带的复制功能不好用。尤其是针对大的项目。现在开始去研究redis-cluster了。貌似3.0才支持。但是不是稳定版本。


<think>我们正在讨论Redis主从复制模式下Slave节点如何处理读请求,以及客户端如何路由读请求到Slave节点。根据引用[1]和[5],主从复制模式中,Master节点负责写操作,Slave节点负责读操作,实现读写分离。但是,Redis本身并不提供自动的路由机制,需要客户端或中间件来实现。具体分析:1.Slave节点处理读请求:Slave节点启动后会连接到Master节点同步数据,之后Master的数据变更会异步复制到Slave。因此,Slave节点上的数据可能不是最新的(存在延迟,见引用[2])。客户端可以直接连接Slave节点执行读命令。2.客户端路由实现方式:a.手动配置:在客户端代码中,显式指定写操作发送到Master节点,读操作发送到Slave节点。例如,在Java中使用Jedis,可以创建两个连接池,分别指向MasterSlave,然后在业务代码中根据操作类型选择连接。b.代理中间件:使用如Twemproxy、Codis等代理,由代理层自动将读请求路由到Slave节点,写请求路由到Master节点。这样客户端只需连接代理,无需关心后端拓扑。c.支持读写分离的客户端库:一些高级的Redis客户端库(如Lettuce)内置了读写分离功能,通过配置主从节点地址,客户端库会自动路由请求。3.注意事项:-数据一致性:由于主从复制是异步的,Slave节点可能存在数据延迟。对于一致性要求高的场景,可能需要强制从Master读(引用[2]提到可以通过外部监控程序检测延迟,或者使用WAIT命令等待同步)。-负载均衡:如果有多个Slave节点,客户端或代理需要实现负载均衡(如轮询、随机等)来分配读请求。4.示例配置(引用[4]提到复制缓冲区配置,这会影响复制性能和延迟,但和客户端路由无关):在客户端实现读写分离的伪代码示例:```java//伪代码示例:使用两个连接池JedisPoolmasterPool=newJedisPool("master_ip",6379);JedisPoolslavePool=newJedisPool("slave_ip",6379);//写操作使用mastertry(Jedismaster=masterPool.getResource()){master.set("key","value");}//读操作使用slavetry(Jedisslave=slavePool.getResource()){Stringvalue=slave.get("key");}```或者使用Lettuce(支持读写分离):```javaRedisClientclient=RedisClient.create();StatefulRedisMasterSlaveConnection<String,String>connection=MasterSlave.connect(client,newUtf8StringCodec(),Arrays.asList(newRedisURI("master_ip",6379,30,TimeUnit.SECONDS),newRedisURI("slave1_ip",6379,30,TimeUnit.SECONDS),newRedisURI("slave2_ip",6379,30,TimeUnit.SECONDS)));connection.setReadFrom(ReadFrom.SLAVE);//设置读策略:优先从Slave读//使用连接时,写操作自动发往Master,读操作发往SlaveRedisCommands<String,String>commands=connection.sync();commands.set("key","value");//写操作发往MasterStringvalue=commands.get("key");//读操作发往Slave(根据策略选择一个)```总结:-Slave节点直接处理读请求,但数据可能不是最新。-客户端路由实现方式包括:手动配置、代理中间件、智能客户端库。-需要根据数据一致性要求和系统复杂度选择合适的方案。相关问题:1.Redis主从复制数据延迟如何监控和解决?2.如何配置RedisSentinel以实现自动故障转移和读写分离?3.RedisCluster模式下如何实现读写分离?</think>### Redis 主从复制模式:Slave 读请求处理与客户端路由实现 #### 一、Slave 节点处理读请求的机制 1. **读写分离架构** - **Master**:**独占写权限**(所有写操作必须发送到 Master- **Slave**:**只读副本**(接收客户端读请求,数据通过异步复制从 Master 同步)[^1][^5] - **数据一致性**:异步复制导致 Slave 数据可能**延迟**(取决于网络带宽和命令阻塞)[^2] 2. **Slave 读请求处理流程** ```mermaid sequenceDiagram Client->>Slave: 发送读请求 (GET key) Slave-->>Slave: 检查本地数据 alt 数据存在且最新 Slave->>Client: 返回数据 else 数据过期或不存在 Slave->>Master: 请求最新数据 Master->>Slave: 发送增量数据 Slave->>Client: 返回更新后数据 end ``` 3. **关键特性** - **无写权限**:Slave 拒绝所有写命令(返回 `READONLY` 错误) - **复制延迟容忍**:业务需能接受**最终一致性**(如商品展示页) - **负载分担**:多个 Slave 可**水平扩展读吞吐量**(理论上限 10万+/秒)[^3] #### 二、客户端路由实现方式 ##### 方式1:手动配置路由(基础方案) ```java // Java 伪代码示例 JedisPool masterPool = new JedisPool("master_ip", 6379); // 写连接池 JedisPool slavePool = new JedisPool("slave_ip", 6379); // 读连接池 public void handleRequest(String key, String value) { if (isWriteOperation()) { try (Jedis master = masterPool.getResource()) { master.set(key, value); // 写操作路由到Master } } else { try (Jedis slave = slavePool.getResource()) { String result = slave.get(key); // 读操作路由到Slave } } } ``` **缺点**:需业务代码显式区分读写,故障转移需手动干预[^4] ##### 方式2:代理中间件(推荐生产使用) - **常用工具**:Twemproxy、Predixy、AWS ElastiCache Proxy - **路由逻辑**: ```mermaid graph LR Client-->Proxy Proxy-->|Write| Master Proxy-->|Read| Slave1 Proxy-->|Read| Slave2 ``` - **配置示例**(Predixy): ```yaml # predixy.conf ProxyServer: Bind: 0.0.0.0:6379 ClusterServerPool: Master: 192.168.1.10:6379 # Master节点 Slaves: - 192.168.1.11:6379 # Slave节点1 - 192.168.1.12:6379 # Slave节点2 ReadStrategy: RoundRobin # 读负载均衡策略 ``` ##### 方式3:智能客户端库(现代方案) - **支持库**:Lettuce(Java)、redis-py-cluster(Python)、go-redis(Go) - **Lettuce 示例**: ```java List<RedisURI> nodes = Arrays.asList( RedisURI.create("redis://master:6379"), RedisURI.create("redis://slave1:6379"), RedisURI.create("redis://slave2:6379") ); RedisClient client = RedisClient.create(); StatefulRedisMasterSlaveConnection<String, String> connection = MasterSlave.connect(client, StringCodec.UTF8, nodes); connection.setReadFrom(ReadFrom.SLAVE); // 设置读策略:优先Slave RedisCommands<String, String> commands = connection.sync(); commands.set("key", "value"); // 自动路由到Master String value = commands.get("key"); // 自动路由到Slave ``` **优势**:自动感知拓扑变化,支持读写分离策略配置[^5] #### 三、关键优化策略 1. **降低复制延迟** - 增大复制缓冲区:`repl-backlog-size 256mb`(默认1MB)[^4] - 网络优化:主从节点同机房部署,万兆网络 - 监控延迟:使用 `INFO replication` 查看 `slave_repl_offset` 2. **读负载均衡** ```mermaid pie title 读请求分配策略 “轮询(RoundRobin)” : 45 “权重(Weighted)” : 30 “就近路由(Geo)” : 25 ``` 3. **一致性保障** - **强制读Master**:对一致性要求高的操作,客户端显式指定读Master - **WAIT命令**:`WAIT <numreplicas> <timeout>` 等待N个副本同步(牺牲性能)[^2] #### 四、适用场景与限制 | 场景 | 适用性 | 说明 | |---------------------|--------|--------------------------| | 读密集型应用 | ★★★★★ | 电商商品列表、资讯类网站 | | 写后立即读 | ★★☆☆☆ | 需配合WAIT或强制读Master | | 跨地域部署 | ★★★☆☆ | 需考虑网络延迟 | | 强一致性要求 | ★☆☆☆☆ | 建议改用Redis Cluster | > **最佳实践**: > 1. 读请求占比 >80% 时使用主从复制 > 2. 使用代理或智能客户端减少代码侵入 > 3. 监控 `master_link_down_since_seconds` 和 `lag` 指标
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值