1. 主从复制介绍
1.1 什么是主从复制?
1.2 为什么要主从复制?
- redis-server 单节点故障
- 单节点QPS有限
1.3 主从复制应用场景分析
- 读写分离场景,规避redis单机瓶颈
- 故障切换,master出问题后还有slave节点可以使用。
2. 搭建主从复制
主Redis Server以普通模式启动,主要是启动从服务器的方式
2.1 第一种方式:命令行
#连接需要实现从节点的redis,执行下面的命令
slaveof [ip] [port]
2.2 第二种方式:redis.conf配置文件
# 配置文件中增加
slavepof [ip] [port]
# 从服务器是否只读(默认yes)
slave-read-only yes
2.3 退出主从集群的方式
slaveof no one
注意: 因为文化差异的原因,新的版本可能会将slaveof
变为replicaof
,但是其实是一样的。
3. 检查主从复制
-
master
网上的例子
实际运行的例子
192.168.1.7:6379> info replication
# Replication
role:master
connected_slaves:0
master_replid:4fa1246eb96e7ad381cd21ab0f1f5a267d1a1cec
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:210
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:210
-
slave
网上的例子
实际运行的例子
192.168.1.7:6378> info replication
# Replication
role:slave
master_host:192.168.1.7
master_port:6379
master_link_status:down
master_last_io_seconds_ago:-1
master_sync_in_progress:0
slave_repl_offset:1
master_link_down_since_seconds:1662020523
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:101535bfbefc8c9858ff46bad42b8439c87295eb
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
(3.74s)
注意: 如果从服务器尝试复制数据时,提示以下错误,那么可能是缺少了文件的读取权限,需要使用sudo 或者 切换到 root用户启动!
Opening the temp file needed for MASTER <-> REPLICA synchronization: Permission denied
4. 主从复制流程
- 从服务器通过psync命令发送服务器已有的同步进度(同步源ID、同步进度offset)
- 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. 主从复制的注意事项
-
读写分离场景
1.数据复制延时导致读到过期数据或者读不到数据(网络原因、slave阻塞)
2.从节点故障(多个client如何迁移) -
全量复制情况下
1.第一次建立主从关系或者runid不匹配会导致全量复制
2.故障转移的时候也会出现全量复制 -
复制风暴
1.master故障重启,如果slave节点较多,所有slave都要复制,对服务器的性能,网络的压力都有很大影响。
2.如果一个机器部署了多个master -
写能力有限
1.主从复制还是只有一台master,提供的写服务能力有限 -
matser故障情况下
1.如果是master无持久化,slave开启持久化来保存数据的场景,建议不要配置redis自动重启。
2.启动redis自动重启,master启动后,无备份数据,可能导致集群数据丢失的情况 -
带有效期的key
1.slave不会让key过期,而是等待master让key过期
2.在Lua脚本执行期间,不执行任何key过期操作
8. 例子
首先记得先把lettuce依赖加入pom
<dependency>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
<version>${lettuce.version}</version>
</dependency>
1. 非读写分离方式
创建ReplicationRedisAppConfig.java
package cn.lazyfennnec.cache.redis;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
@Configuration
@Profile("replication") // 主从模式
class ReplicationRedisAppConfig {
@Bean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
StringRedisTemplate stringRedisTemplate = new StringRedisTemplate(redisConnectionFactory);
return stringRedisTemplate;
}
@Bean
public LettuceConnectionFactory redisConnectionFactory() {
// 假设master:192.168.100.241 slave:192.168.100.242
// 默认slave只能进行读取,不能写入
// 如果你的应用程序需要往redis写数据,建议连接master
// 其实本地的redis都是在同一个服务器上的,分别为
// 1. master 192.168.1.7 6379
// 2. slave 192.168.1.7 6378
return new LettuceConnectionFactory(new RedisStandaloneConfiguration("192.168.1.7", 6379));
}
}
2. 读写分离的方式
创建ReplicationRWRedisAppConfig.java
package cn.lazyfennec.cache.redis;
import io.lettuce.core.ReadFrom;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
@Configuration
@Profile("replication-rw") // 主从 - 读写分离模式
class ReplicationRWRedisAppConfig {
@Bean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
StringRedisTemplate stringRedisTemplate = new StringRedisTemplate(redisConnectionFactory);
return stringRedisTemplate;
}
@Bean
public LettuceConnectionFactory redisConnectionFactory() {
System.out.println("使用读写分离版本");
LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder()
.readFrom(ReadFrom.SLAVE_PREFERRED)
.build();
// 此处
RedisStandaloneConfiguration serverConfig = new RedisStandaloneConfiguration("192.168.1.7", 6378);
return new LettuceConnectionFactory(serverConfig, clientConfig);
}
}
3. Service实现类
package cn.lazyfennec.cache.redis;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
@Service
public class ReplicationExampleService {
@Autowired
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. 测试代码
- 非读写分离方式
package cn.lazyfennec.cache.redis;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
@ActiveProfiles("replication") // 激活主从复制的配置
public class ReplicationTests {
@Autowired
ReplicationExampleService replicationExampleService;
@Test
public void setTest() {
replicationExampleService.setByCache("neco", "hahhhhh");
}
}
- 读写分离的方式
package cn.lazyfennec.cache.redis;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
@ActiveProfiles("replication-rw") // 激活主从集群-读写分离的配置
public class ReplicationRWTests {
@Autowired
ReplicationExampleService replicationExampleService;
@Test
public void setTest() {
replicationExampleService.setByCache("neco", "xxxx");
String result = replicationExampleService.getByCache("neco");
System.out.println("从缓存中读取到数据:" + result);
}
}
如果觉得有收获,欢迎点赞和评论,更多知识,请点击关注查看我的主页信息哦~
网友评论