入门
概要
为什么用
- 避免 redis-server 单点故障
- 单节点 QPS 有限
场景
- 读写分离场景,规避redis单机瓶颈
- master:写
- slave(replica):读
- 故障切换,master出问题后还有slave节点可以使用
使用
搭建
主 Redis Server 以普通模式启动;从服务器可以选择多种方式启动
命令行
#连接需要实现从节点的redis,执行下面的命令
slaveof [ip] [port]
配置文件
#配置文件中增加
slaveof [ip] [port]
#从服务器是否只读(默认yes)
slave-read-only yes
# 最新的配置方式
replicaof [ip][port]
退出
SlaveOf no one
#或
ReplcaOf no one
主从启动成功
状态检查
info replication
进阶
主从复制的流程
- 从服务器通过 psync(partial synchornization)命令发送服务器已有的同步进度
- 同步源 ID、同步进度 offset
- 源ID:用于确定当前数据是否是当前 master
- 同步进度:用于增量同步使用
- 同步源 ID、同步进度 offset
- master 收到请求, 同步源为当前 master,则根据偏移量增量同步
- 增量同步:类似于 AOF
- 同步源非当前master,则进入全量同步:
- 全量同步:类似于RDB。
- master生成rdb,传输到slave,加载到slave内存
- 全量同步:类似于RDB。
核心概念
- Redis默认使用异步复制,slave 和 master 之间异步地确认处理的数据量
- 一个 master 可以拥有多个 slave
- slave 可以接受其他 slave 的连接,slave 可以有下级 sub slave
- 主从同步过程在 master 侧是非阻塞的
- slave 初次同步需要删除旧数据,加载新数据,会阻塞到来的连接请求
✨应用场景
- 读写分离:主从复制可以用来支持读写分离
- 数据安全:slave 服务器设定为只读。
- 例如
- 主服务器:用户服务(可以修改用户信息)
- 从服务器:订单服务,支付服务
- 例如
- 避免 master 持久化造成的开销
- master 关闭持久化
- slave 配置为不定期保存或是启用AOF。
- 注意:重新启动的master程序将从一个空数据集开始,如果一个 slave 试图与它同步,那么这个 slave 也会被清空。
注意事项
读写分离场景
- 数据复制延时导致读到过期数据或者读不到数据
- 网络原因、slave 阻塞
- 从节点故障(多个 client 如何迁移)
- 最好选用支持 Slave 自动切换的客户端
增量复制情况
有可能造成复制风暴
- 第一次建立主从关系或者 runid 不匹配会导致全量复制
- 故障转移的时候也会出现全量复制
复制风暴
- master故障重启
- 如果 slave 节点较多,所有 slave 都要复制。对服务器的性能,网络的压力都有很大影响。
- 如果一个机器部署了多个 master
- 每个 slave 的同步压力都会到 mater
写能力有限
- 只有一个 master,还是会有很高的写压力
master无持久化
master 关闭了持久化,由 slave 来持久化的情况
- master 无持久化,slave 开启持久化来保留数据的场景,建议不要配置 redis 自动重启。
- redis 自动重启,master 启动后,无备份数据,可能导致集群数据丢失的情况。
slave不会使key过期
- slave 不会让 key 过期,而是等待 master 让 key 过期
- 在 Lua 脚本执行期间,不执行任何 key 过期操作
实例演示
主服务器故障恢复
master 故障后,自动重启会导致 slave 数据丢失
1. 使从服务器变为主服务器
- 找到非故障的从服务器
- 执行 replicaof no one
- 故障的服务器恢复,作为 replica 连接
- replicaof [host] [port]
- 将db文件在主服务器恢复
读写分离
监控服务器命令执行状态
使用代码连接
这里使用 Lettuce 客户端连接
- Configuration
@Configuration
@Profile("replication-rw") // 主从 - 读写分离模式
class ReplicationRWRedisAppConfig {
@Bean
public LettuceConnectionFactory redisConnectionFactory() {
System.out.println("使用读写分离版本");
LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder()
//优先读 replica
.readFrom(ReadFrom.SLAVE_PREFERRED)
.build();
RedisStandaloneConfiguration serverConfig = new RedisStandaloneConfiguration("192.168.100.242", 6379);
return new LettuceConnectionFactory(serverConfig, clientConfig);
}
}
- Test
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
@ActiveProfiles("replication-rw") // 激活主从集群-读写分离的配置
public class ReplicationRWTests {
@Autowired
ReplicationExampleService replicationExampleService;
@Test
public void setTest() {
replicationExampleService.setByCache("tony", "xxxx");
String result = replicationExampleService.getByCache("tony");
System.out.println("从缓存中读取到数据:" + result);
}
}