1. 主从复制介绍


什么是主从复制

image.png

为什么要使用主从复制

  • redis-server 单点故障。
  • 单节点 QPS 有限。

主从复制的应用场景

  • 读写分离场景,规避 redis 单机瓶颈。
  • 故障切换,master 出问题后还有 slave 节点可以使用。

2. 搭建主从复制


主 Redis Server 以普通模式启动,主要是启动从服务器的方式。

  1. 第一种方式:命令行

    1. # 连接需要实现从节点的 redis,执行下面的命令
    2. slaveof [ip] [port]
  2. 第二种方式: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. 检查主从复制


image.png

4. 主从复制流程


image.png

  1. 从服务器通过 psync 命令发送服务器已有的同步进度(同步源 ID、同步进度 offset),psync:partial synchronzation。
  2. master 收到请求,同步源为当前 master,则根据偏移量增量同步。
  3. 同步源非当前 master,则进入全量同步:master 生成 rdb,传输到 slave,加载到 slave 内存。

5. 主从复制核心知识


image.png

  • Redis 默认使用异步复制,slave 和 master 之间异步地确认处理的数据量。
  • 一个 master 可以拥有多个 slave。
  • slave 可以接受其他 slave 的连接。slave 可以有下级 sub slave。
  • 主从同步过程在 master 侧是非阻塞的。
  • slave 初次同步需要删除旧数据,加载新数据,会阻塞到来的连接请求。

6. 主从复制应用场景


image.png

  • 主从复制可以用来支持读写分离。
  • 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

image.png

  • 从服务器 redis-2 — 127.0.0.1:6380

image.png

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. 控制台打印

image.png

6. 通过 redis monitor 命令查看两台服务器的信息

  • 因为此处使用 win10 的两台 docker 服务,会出现电脑无法 ping 通 docker ip 的情况,就暂时不做截图。
  • 结果应该是:
    • 主服务器 redis:可以看到一条 set zp xxxxxx 命令,源地址是程序地址,此处为 set 数据。
    • 从服务器 redis-2:可以看到一条 set zp xxxxxx 命令,源地址是主服务器地址,此命令为主从同步。还有一条 get zp,此命令为提供读服务的命令。