💻 Hello World, 我是 予枫。
代码不止,折腾不息。作为一个正在升级打怪的 Java 后端练习生,我喜欢把踩过的坑和学到的招式记录下来。 保持空杯心态,让我们开始今天的技术分享。
Redis 作为高性能的键值数据库,单节点部署在高并发场景下会面临两大核心问题:一是读写压力集中在单个节点,性能瓶颈明显;二是单节点故障会导致服务不可用,数据丢失风险高。主从复制(Master-Slave Replication)是 Redis 解决这两个问题的基石 ------ 通过将主节点(Master)的数据同步到从节点(Slave),实现读写分离、数据备份和故障转移,是 Redis 高可用架构的核心组成部分。
本文将从底层逻辑出发,拆解主从复制的核心阶段与关键概念,分析实际应用中的常见问题,并给出 Java 项目中「主从读写分离」的完整实践方案。
一、Redis 主从复制的核心价值
在深入技术细节前,先明确主从复制的核心作用,理解其存在的意义:
- 读写分离:Master 处理写操作(SET、DEL、HSET 等),Slave 处理读操作(GET、HGET、SMEMBERS 等),分散单节点的读写压力,提升系统整体吞吐量;
- 数据备份:Slave 是 Master 的实时数据副本,即使 Master 故障,Slave 可作为数据恢复的核心来源;
- 故障恢复:配合哨兵(Sentinel)或集群(Cluster),Master 故障后可自动将 Slave 升级为新 Master,实现服务无感知切换;
- 负载均衡:多个 Slave 可分担读请求,比如报表查询、数据统计等耗资源的读操作可路由到 Slave,降低 Master 压力。
二、主从复制的三个核心阶段(底层逻辑)
主从复制的本质是「Slave 主动同步 Master 数据」的过程,核心分为三个阶段,每个阶段对应不同的核心逻辑和指令。
阶段 1:连接建立(Slave 主动发起,Master 被动响应)
这是复制的 "握手阶段",核心目标是建立主从之间的网络连接并完成身份认证,流程如下:
- Slave 配置触发 :Slave 通过配置
replicaof <master-ip> <master-port>(Redis5.0 + 推荐,替代旧的slaveof)指定 Master 地址,启动后自动触发复制流程; - TCP 连接建立:Slave 向 Master 发起 TCP Socket 连接,Master 接受连接后,会创建一个 "复制专用" 的客户端连接(与普通业务客户端区分);
- 身份认证 :若 Master 配置了
requirepass(密码),Slave 必须通过auth <password>命令认证,否则 Master 会拒绝复制; - 可用性检测 :Slave 发送
PING命令测试 Master 是否可用,Master 返回PONG表示正常,否则复制中断; - 同步请求发起 :Slave 发送
PSYNC ? -1命令(?表示未知 Master 的 RunID,-1表示首次同步),向 Master 请求数据同步。
阶段 2:全量同步(首次同步的核心)
当 Slave 首次连接 Master,或 Master 判断 Slave 的复制偏移量无效时,会触发全量同步------ 将 Master 当前的全部数据一次性发送给 Slave,这是最耗资源的阶段:
- Master 生成 RDB 快照 :Master 收到
PSYNC命令后,调用fork()创建子进程,执行BGSAVE生成 RDB 快照文件;此过程中,Master 的新写操作会被临时记录到「复制积压缓冲区」,避免数据丢失; - RDB 文件传输:Master 将生成的 RDB 文件通过 Socket 发送给 Slave;
- Slave 加载 RDB:Slave 接收 RDB 文件后,先清空本地原有数据(避免数据冲突),再加载 RDB 文件到内存,恢复 Master 的全量数据;
- 同步积压缓冲区 :Master 将
BGSAVE过程中产生的写操作(积压缓冲区中)发送给 Slave,Slave 执行这些操作,确保与 Master 数据完全一致。
核心痛点:全量同步的 RDB 文件传输会占用大量网络带宽和磁盘 IO,Master 数据量越大,同步耗时越长,需尽量避免频繁触发。
阶段 3:增量同步(日常同步的常态)
全量同步完成后,主从进入增量同步阶段 ------Master 将后续的每一个写操作实时同步给 Slave,保证数据一致性:
- Master 记录写操作:Master 每执行一个写命令,都会将命令写入「复制积压缓冲区」,同时更新自身的「复制偏移量」;
- 实时同步命令:Master 通过复制连接,将写命令实时发送给 Slave;
- Slave 执行并更新偏移量:Slave 接收命令后执行,同时更新自己的复制偏移量;
- 偏移量校验:Slave 每秒向 Master 汇报自身偏移量,Master 对比自身偏移量,确保主从数据一致;若偏移量不一致,Master 会触发补全逻辑。
三、核心概念解析(理解同步的关键)
要彻底掌握主从复制,必须理解以下三个核心概念:
1. PSYNC 命令:同步的 "核心指令"
Redis2.8 + 引入PSYNC替代旧的SYNC命令,解决了SYNC"无论场景都全量同步" 的低效问题。其格式为:
PSYNC <runid> <offset>
<runid>:Master 的唯一标识(每个 Redis 实例启动后生成 40 位 RunID),Slave 重连时携带该值,表明要同步的目标 Master;<offset>:Slave 当前的复制偏移量,Master 据此判断同步起始位置。
PSYNC 的三种执行场景:
- 场景 1:Slave 首次连接(
PSYNC ? -1)→ 触发全量同步(FULLRESYNC),Master 返回自身 RunID 和当前偏移量; - 场景 2:Slave 重连,RunID 匹配且偏移量在积压缓冲区范围内 → 触发增量同步(
CONTINUE),发送偏移量后的写命令; - 场景 3:Slave 重连,RunID 不匹配(Master 重启)或偏移量超出缓冲区范围 → 触发全量同步。
2. 复制偏移量(Replication Offset):数据一致性的 "标尺"
- 定义:64 位整数,记录 Master 发送给 Slave 的字节数,Master 和 Slave 各自维护独立的偏移量;
- 作用:Master 每发送 N 字节数据,自身偏移量 + N;Slave 每接收并执行 N 字节,自身偏移量 + N。通过对比主从偏移量,可直接判断数据是否一致(偏移量相等则一致);
- 校验机制:Slave 每秒发送
REPLCONF ACK <offset>命令汇报偏移量,Master 通过该信息感知 Slave 的同步状态。
3. 复制积压缓冲区(Replication Backlog Buffer):增量同步的 "容错空间"
- 定义:Master 端的环形缓冲区(固定大小,默认 1MB,可通过
repl-backlog-size配置),缓存最近发送给 Slave 的写命令; - 核心作用:Slave 断开重连时,Master 检查 Slave 的偏移量是否在缓冲区范围内:
- 若在范围内:从 Slave 偏移量位置开始,发送缓冲区中的命令(增量同步);
- 若超出范围:触发全量同步;
- 配置建议:根据 Slave 最大断连时间和写命令吞吐量调整,比如写频繁的场景可设为 10-50MB,减少全量同步概率。
四、主从架构的常见 "坑" 与解决方案
主从复制并非银弹,实际应用中会遇到各类问题,以下是最常见的 3 个 "坑" 及解决方案:
1. 脑裂(Split Brain):主从失联后的 "数据孤岛"
- 现象:Master 因网络分区与 Slave 断开,但自身仍运行;哨兵误判 Master 故障,将 Slave 升级为新 Master;网络恢复后,原 Master 重新加入集群,出现 "双 Master",导致数据写入到两个节点,最终数据不一致;
- 解决方案 :
- 配置 Master 的
min-replicas-to-write(最少可用 Slave 数)和min-replicas-max-lag(Slave 最大延迟):比如min-replicas-to-write 1+min-replicas-max-lag 10,表示当可用 Slave 数 <1 或延迟> 10 秒时,Master 拒绝写操作; - 合理设置哨兵故障判断时间(
down-after-milliseconds),避免误判; - 网络恢复后,强制原 Master 成为新 Master 的 Slave,同步新数据。
- 配置 Master 的
2. 数据不一致:主从同步的 "时差"
- 现象:Master 执行写操作后,Slave 未及时同步,此时读 Slave 会获取旧数据;
- 解决方案 :
- 强一致性读请求(如用户余额、订单状态)强制路由到 Master;
- 配置 Slave 的
slave-serve-stale-data no:Slave 与 Master 断开时拒绝读请求,避免返回旧数据; - 优化网络,减少主从间的同步延迟。
3. 全量同步频繁:性能杀手
- 现象:Slave 频繁触发全量同步,导致 Master/Slave 的 CPU、网络压力剧增;
- 解决方案 :
- 增大
repl-backlog-size,降低偏移量超出缓冲区的概率; - 保证 Master 稳定性,避免频繁重启(重启会导致 RunID 变化,触发全量同步);
- 监控 Slave 连接状态,及时处理断连问题。
- 增大
五、Java 项目实战:Redis 主从读写分离
接下来给出完整的 Java 项目实现方案,包括环境准备、依赖配置、代码实现和测试验证。
5.1 环境准备(主从集群搭建)
以单机多实例为例(生产环境建议多机部署):
- Master:127.0.0.1:6379
- Slave1:127.0.0.1:6380
- Slave2:127.0.0.1:6381
Slave 的redis.conf核心配置(Redis6.0+):
# 绑定IP(生产环境建议内网IP)
bind 127.0.0.1
# 修改端口
port 6380
# 指定Master
replicaof 127.0.0.1 6379
# Master密码认证(若有)
masterauth 123456
# 禁止Slave写操作(生产必开)
slave-read-only yes
# 同步失败时拒绝读旧数据
slave-serve-stale-data no
启动主从节点:
# 启动Master
redis-server /etc/redis/6379.conf
# 启动Slave1
redis-server /etc/redis/6380.conf
# 启动Slave2
redis-server /etc/redis/6381.conf
验证主从关系:在 Master 执行info replication,可看到 Slave 列表;在 Slave 执行该命令,可看到 Master 信息。
5.2 项目依赖(Maven)
使用 Spring Boot + Spring Data Redis(Lettuce 客户端,默认):
XML
<dependencies>
<!-- Spring Boot Redis Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.7.10</version> <!-- 适配JDK8 -->
</dependency>
<!-- 连接池(提升性能) -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- 测试依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
5.3 核心实现:读写分离配置
推荐使用 Lettuce 的自动读写分离配置(生产级),可实现读操作自动路由到 Slave,写操作路由到 Master,并支持从库负载均衡。
java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisStaticMasterReplicaConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.time.Duration;
import static org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration.ReadFrom;
/**
* Lettuce自动读写分离 + 从库负载均衡配置
*/
@Configuration
public class RedisMasterSlaveConfig {
@Bean
public LettuceConnectionFactory redisConnectionFactory() {
// 1. 配置主从节点
RedisStaticMasterReplicaConfiguration config = new RedisStaticMasterReplicaConfiguration("127.0.0.1", 6379);
// 添加多个Slave(支持负载均衡)
config.addNode("127.0.0.1", 6380);
config.addNode("127.0.0.1", 6381);
// Redis密码(主从需一致)
config.setPassword("123456");
// 2. Lettuce客户端配置
LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder()
// 连接超时
.connectTimeout(Duration.ofSeconds(5))
// 重试策略(应对网络抖动)
.retryAttempts(3)
.retryInterval(Duration.ofMillis(100))
// 读写分离策略:优先读Slave,Slave不可用时读Master
.readFrom(ReadFrom.REPLICA_PREFERRED)
.build();
// 3. 创建连接工厂
return new LettuceConnectionFactory(config, clientConfig);
}
/**
* 统一的RedisTemplate(自动路由读写操作)
*/
@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory());
// 序列化配置(避免乱码)
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
template.afterPropertiesSet();
return template;
}
}
关键说明:
ReadFrom策略选择:
MASTER:只读 Master(强一致性场景);REPLICA:只读 Slave(Slave 不可用时报错);REPLICA_PREFERRED:优先读 Slave,Slave 全不可用时读 Master(推荐);MASTER_PREFERRED:优先读 Master,Master 不可用时读 Slave。
5.4 业务层实现
封装 Redis 读写操作,业务层无需关心底层路由逻辑:
java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
/**
* Redis读写分离业务服务
*/
@Service
public class RedisService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 写操作(自动路由到Master)
*/
public void set(String key, Object value) {
redisTemplate.opsForValue().set(key, value);
}
/**
* 读操作(自动路由到Slave)
*/
public Object get(String key) {
return redisTemplate.opsForValue().get(key);
}
/**
* 强一致性读(强制路由到Master)
* 注:需单独配置Master的RedisTemplate,或使用Redis命令指定Master
*/
public Object getFromMaster(String key) {
// 若需强制读Master,可单独配置Master的RedisTemplate注入使用
return redisTemplate.opsForValue().get(key);
}
}
5.5 测试验证
java
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class RedisMasterSlaveTest {
@Autowired
private RedisService redisService;
@Test
public void testReadWriteSeparation() {
// 1. 写操作(Master)
String key = "user:1001";
redisService.set(key, "张三");
// 2. 读操作(Slave)
Object slaveResult = redisService.get(key);
System.out.println("从Slave读取:" + slaveResult); // 预期:张三
// 3. 强一致性读(Master)
Object masterResult = redisService.getFromMaster(key);
System.out.println("从Master读取:" + masterResult); // 预期:张三
}
}
5.6 生产环境最佳实践
- 监控主从同步状态:通过
info replication监控复制偏移量、Slave 延迟,及时发现同步异常; - 配合哨兵:主从 + 哨兵实现 Master 故障自动转移,提升高可用性;
- 连接池优化:合理设置 Lettuce 连接池大小(
spring.redis.lettuce.pool.max-active),避免连接耗尽; - 避免大 Key:大 Key 的全量同步会占用大量资源,建议拆分大 Key;
- 限流熔断:对 Slave 的读请求做限流,避免 Slave 被打垮。
总结
- Redis 主从复制的核心流程是「连接建立→全量同步→增量同步」,PSYNC 命令、复制偏移量、积压缓冲区是保障同步的关键;
- 主从架构的核心问题是脑裂、数据不一致和全量同步频繁,可通过配置优化和监控规避;
- Java 项目中实现主从读写分离,推荐使用 Lettuce 的
ReadFrom策略,可自动实现读操作路由到 Slave、写操作路由到 Master,简化开发。
主从复制是 Redis 高可用的基础,掌握其底层逻辑和实践技巧,能帮助你搭建更稳定、高性能的 Redis 架构。
🌟 关注【予枫】,获取更多技术干货
📅 身份:一名热爱技术的研二学生
🏷️ 标签:Java / 算法 / 个人成长
💬 Slogan:只写对自己和他人有用的文字。