1. 主从复制介绍
什么是主从复制
为什么要使用主从复制
- redis-server 单点故障。
- 单节点 QPS 有限。
主从复制的应用场景
- 读写分离场景,规避 redis 单机瓶颈。
- 故障切换,master 出问题后还有 slave 节点可以使用。
2. 搭建主从复制
主 Redis Server 以普通模式启动,主要是启动从服务器的方式。
第一种方式:命令行
# 连接需要实现从节点的 redis,执行下面的命令
slaveof [ip] [port]
第二种方式:redis.conf 配置文件
# 配置文件中增加 slaveof [ip] [port] # 从服务器是否只读(默认 yes) slave-read-only yes
slaveof [ip] [port]
也可以使用replicaof [ip] [port]
,此为文化差异,国外感觉 slave 有一种奴隶的感觉,想要使用 replication 代替 slave。
退出主从集群的方式
slaveof no one
可以通过如下命令查看 Docker 容器的 IP
docker inspect <容器ID>
3. 检查主从复制
4. 主从复制流程
- 从服务器通过
psync
命令发送服务器已有的同步进度(同步源 ID、同步进度 offset),psync:partial synchronzation。 - master 收到请求,同步源为当前 master,则根据偏移量增量同步。
- 同步源非当前 master,则进入全量同步:master 生成 rdb,传输到 slave,加载到 slave 内存。
5. 主从复制核心知识
- Redis 默认使用异步复制,slave 和 master 之间异步地确认处理的数据量。
- 一个 master 可以拥有多个 slave。
- slave 可以接受其他 slave 的连接。slave 可以有下级 sub slave。
- 主从同步过程在 master 侧是非阻塞的。
- slave 初次同步需要删除旧数据,加载新数据,会阻塞到来的连接请求。
6. 主从复制应用场景
- 主从复制可以用来支持读写分离。
- slave 服务器设定为只读,可以用在数据安全的场景下。
- 可以使用主从复制来避免 master 持久化造成的开销。master 关闭持久化,slave 配置为不定期保存或是启用 AOF。(注意:重新启动的 master 程序将从一个空数据集开始,如果一个 slave 试图与它同步,那么这个 slave 也会被清空。)
7. 主从复制的注意事项
- 读写分离场景
- 数据复制延时导致读到过期数据或者读不到数据(网络原因、slave 阻塞)。
- 从节点故障(多个 client 如何迁移)。
- 全量复制情况下
- 第一次建立主从关系或者 runid 不匹配会导致全量复制。
- 故障转移的时候也会出现全量复制。
- 复制风暴
- master 故障重启,如果 slave 节点较多,所有 slave 都要复制,对服务器的性能,网络的压力都有很大影响。
- 如果一个机器部署了多个 master,主从复制压力也会很大。
- 写能力有限
- 主从复制还是只有一台 master,提供的写服务器能力有限。
- master 故障情况下
- 如果 master 无持久化,slave 开启持久化来保留数据的场景,建议不要配置 redis 自动重启。
- 启动 redis 自动重启,master 启动后,无备份数据,可能导致集群数据丢失的情况。
- 带有效期的 key
- slave 不会让 key 过期,而是等待 master 让 key 过期。
- 在 Lua 脚本执行期间,不执行任何 key 过期操作。
8. Java 实现简单的主从复制
1. 前置条件
- redis — 主服务器 127.0.0.1:6379
- redis-2 — 从服务器 127.0.0.1:6380
2. 配置类 ReplicationRedisAppConfig
@Configuration
@Profile("replication") // 主从模式
public class ReplicationRedisAppConfig {
@Bean
public LettuceConnectionFactory redisConnectionFactory() {
// master: 127.0.0.1:6379 slave: 127.0.0.1:6380
// 默认 slave 只能进行读取,不能写入
// 如果你的应用程序需要往 redis 写数据,建议连接 master
return new LettuceConnectionFactory(new RedisStandaloneConfiguration("127.0.0.1", 6379));
}
}
3. 业务类 ReplicationExampleService
@Service
public class ReplicationExampleService {
@Resource
private StringRedisTemplate template;
public void setByCache(String userId, String userInfo) {
template.opsForValue().set(userId, userInfo);
}
public String getByCache(String userId) {
return template.opsForValue().get(userId);
}
}
4. 测试类 ReplicationTests
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
@ActiveProfiles("replication") // 激活主从复制的配置
public class ReplicationTests {
@Resource
private ReplicationExampleService replicationExampleService;
@Test
public void setTest() {
replicationExampleService.setByCache("zp", "hahahhahah");
}
}
5. 通过 redis monitor 命令查看两台服务器的信息
- 主服务器 redis — 127.0.0.1:6379
- 从服务器 redis-2 — 127.0.0.1:6380
9. Java 实现主从复制读写分离
1. 前置条件
- redis — 主服务器 127.0.0.1:6379
- redis-2 — 从服务器 127.0.0.1:6380
2. 配置类 ReplicationRWRedisAppConfig
@Configuration
@Profile("replication-rw") // 主从 - 读写分离
public class ReplicationRWRedisAppConfig {
@Bean
public LettuceConnectionFactory redisConnectionFactory() {
System.out.println("使用读写分离版本");
LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder()
.readFrom(ReadFrom.SLAVE_PREFERRED).build();
RedisStandaloneConfiguration serverConfig = new RedisStandaloneConfiguration("127.0.0.1", 6379);
return new LettuceConnectionFactory(serverConfig, clientConfig);
}
}
3. 业务类 ReplicationExampleService
@Service
public class ReplicationExampleService {
@Resource
private StringRedisTemplate template;
public void setByCache(String userId, String userInfo) {
template.opsForValue().set(userId, userInfo);
}
public String getByCache(String userId) {
return template.opsForValue().get(userId);
}
}
4. 测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
@ActiveProfiles("replication-rw")
public class ReplicationRWTests {
@Resource
private ReplicationExampleService replicationExampleService;
@Test
public void setTest() {
replicationExampleService.setByCache("zp", "xxxxxx");
String result = replicationExampleService.getByCache("zp");
System.out.println("从缓存中读取到数据:" + result);
}
}
5. 控制台打印
6. 通过 redis monitor 命令查看两台服务器的信息
- 因为此处使用 win10 的两台 docker 服务,会出现电脑无法 ping 通 docker ip 的情况,就暂时不做截图。
- 结果应该是:
- 主服务器 redis:可以看到一条
set zp xxxxxx
命令,源地址是程序地址,此处为 set 数据。 - 从服务器 redis-2:可以看到一条
set zp xxxxxx
命令,源地址是主服务器地址,此命令为主从同步。还有一条get zp
,此命令为提供读服务的命令。
- 主服务器 redis:可以看到一条