redis高可用之主从架构

高可用的必要:
所谓的高可用是指:redis服务数据少丢失、服务少中断。通过rdb或者aof方式对数据进行持久化,可以重放aof日志或者重新读入rdb文件可以达到数据少丢失。
服务少中断:可以增加副本冗余量,将一份数据同时保存在多个实例上。即使有一个实例出现了故障,需要过一段时间才能恢复,其他实例也可以对外提供服务,不会影响业务使用。
主从的原理:
redis为了主从数据的一致性,采用了读写分离的架构,master或slave都可以接受读请求,但是slave不能接受写请求(保障数据的一致性),所有的写请求都会在master执行后,同步给slave。
第一次同步流程如下:
redis高可用之主从架构_第1张图片
阶段一、从库和主库建立起连接,并告诉主库即将进行同步,主库确认回复后,主从库间就可以开始同步了。具体来说,从库给主库发送 psync 命令,表示要进行数据同步,主库根据这个命令的参数来启动复制。psync 命令包含了主库的 runID 和复制进度 offset 两个参数。runID,是每个 Redis 实例启动时都会自动生成的一个随机 ID,用来唯一标记这个实例。当从库和主库第一次复制时,因为不知道主库的 runID,所以将 runID 设为“?”。offset,此时设为 -1,表示第一次复制。主库收到 psync 命令后,会用 FULLRESYNC 响应命令带上两个参数:主库 runId与offset.
其中FULLRESYNC 响应表示第一次复制采用的全量复制,也就是说,主库会把当前所有的数据都复制给从库。
阶段二、主库将所有数据同步给从库。从库收到数据后,在本地完成数据加载(依靠主库的rdb文件,为了保障复制的正确性,会把slave之前的rdb文件给删除)。
阶段三、当主库完成 RDB 文件发送后,就会把此时 replication buffer 中的修改操作发给从库,从库再重新执行这些操作

如何搭建合理的主从架构:
从主从第一个同步数据的流程图得知一次全量复制中,对于主库来说,需要完成两个耗时的操作:
生成 RDB 文件和传输 RDB 文件。如果从库数量很多,而且都要和主库进行全量复制的话,就会导致主库忙于 fork 子进程生成 RDB 文件,进行数据全量同步。fork 这个操作会阻塞主线程处理正常请求,从而导致主库响应应用程序的请求速度变慢。此外,传输 RDB 文件也会占用主库的网络带宽,同样会给主库的资源使用带来压力。
可以通过 主 - 从 - 从 模式分担主库压力的方式。那么,一旦主从库完成了全量复制,它们之间就会一直维护一个网络连接,主库会通过这个连接将后续陆续收到的命令操作再同步给从库,这个过程也称为基于长连接的命令传播,可以避免频繁建立连接的开销。
主从架构遇上网络断开的redis如何处理?
网络断了之后,主从库会采用增量复制的方式继续同步。主库会把断连期间收到的写操作命令,写入 replication buffer,同时也会把这些操作命令也写入 repl_backlog_buffer 这个缓冲区。repl_backlog_buffer 是一个环形缓冲区,主库会记录自己写到的位置,从库则会记录自己已经读到的位置。对主库来说,对应的偏移量就是 master_repl_offset。主库接收的新写操作越多,这个值就会越大。从库已复制的偏移量 slave_repl_offset 也在不断增加。正常情况下,这两个偏移量基本相等。
当主从库的连接恢复之后,从库首先会给主库发送 psync 命令,并把自己当前的 slave_repl_offset 发给主库,主库会判断自己的 master_repl_offset 和 slave_repl_offset 之间的差距。在网络断连阶段,主库可能会收到新的写操作命令,所以,一般来说,master_repl_offset 会大于 slave_repl_offset。此时,主库只用把 master_repl_offset 和 slave_repl_offset 之间的命令操作同步给从库就行。
但是也有特别情况。。如果从库的读取速度比较慢,就有可能导致从库还未读取的操作被主库新写的操作覆盖了,这会导致主从库间的数据不一致。(因为 repl_backlog_buffer 是一个环形缓冲区,所以在缓冲区写满后,主库会继续写入,此时,就会覆盖掉之前写入的操作),可以调整 repl_backlog_size的值减少此种情况的产生。 repl_backlog_size=缓存*2
主从搭建实例:
主库搭建可以参考手记一的方式常规启动一个redis实例即可配置文件也一样,从库需要加的主要是这两个参数:


replicaof  同步的数据的实例(可以是master or slave) 端口
masterauth 主库密码

然后启动相应的redis实例即可。
可以通过redis-cli -h ip -p 9379 -a password info查看主从的信息。
redis高可用之主从架构_第2张图片

springboot实现主从读写分离:
1、引入依赖:

        <!--redis依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <!-- redis集群使用 -->
        <dependency>
            <!-- springboot的parents依赖中有对版本号的控制,此处不需要添加版本号相关信息 -->
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
        </dependency>

2、编写配置实例:
主库的配置

@Configuration
public class RedisMasterConfig {

    //Redis连接池配置
    @Bean
    public JedisPoolConfig jedisPoolConfig() {
        JedisPoolConfig masterConfig = new JedisPoolConfig();
        masterConfig.setMaxTotal(1000);
        masterConfig.setMaxIdle(1000);
        masterConfig.setMaxWaitMillis(3000);
        return masterConfig;
    }

    //redis主连接工厂
    @Bean("masterRedisFactory")
    @Primary
    public JedisConnectionFactory masterJedisConnectionFactory(JedisPoolConfig jedisPoolConfig) {
        JedisConnectionFactory masterFactory = new JedisConnectionFactory();
        masterFactory.setPort(6379);
        masterFactory.setHostName("192.168.23.129");
        masterFactory.setPoolConfig(jedisPoolConfig);
        masterFactory.setTimeout(3000);
        masterFactory.setPassword("root");
        return masterFactory;
    }

    //构造redis主对应的Template
    @Bean("masterRedisTemplate")
    @Primary
    public RedisTemplate masterRedisTemplate(@Qualifier("masterRedisFactory") JedisConnectionFactory jedisConnectionFactory) {
        RedisTemplate masterRedisTemplate = new RedisTemplate();
        masterRedisTemplate.setConnectionFactory(jedisConnectionFactory);
        masterRedisTemplate.setKeySerializer(new StringRedisSerializer());
        masterRedisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
        return masterRedisTemplate;
    }
}

从库配置:

@Configuration
public class RedisSlaveConfig {

    @Bean("slaveRedisFactory")
    public JedisConnectionFactory slaveJedisConnectionFactory(JedisPoolConfig jedisPoolConfig) {
        JedisConnectionFactory slaveFactory = new JedisConnectionFactory();
        slaveFactory.setPort(6379);
        slaveFactory.setHostName("192.168.23.133");
        slaveFactory.setPoolConfig(jedisPoolConfig);
        slaveFactory.setTimeout(3000);
        return slaveFactory;
    }

    @Bean("slaveRedisTemplate")
    public RedisTemplate slaveRedisTemplate(@Qualifier("slaveRedisFactory") JedisConnectionFactory jedisConnectionFactory) {
        RedisTemplate slaveRedisTemplate = new RedisTemplate();
        slaveRedisTemplate.setConnectionFactory(jedisConnectionFactory);
        slaveRedisTemplate.setKeySerializer(new StringRedisSerializer());
        slaveRedisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
        return slaveRedisTemplate;
    }
}

主从架构的读写分离实例:

    @Autowired
    @Qualifier("masterRedisTemplate")
    private RedisTemplate<String, String> masterRedis;

    @Autowired
    @Qualifier("slaveRedisTemplate")
    private RedisTemplate<String, String> slaveRedis;

    @Test
    void writeRedis() {
        for (int i = 0; i < 10000; i++) {
            masterRedis.opsForValue().set(String.valueOf(i), String.valueOf(i));
        }
    }

    @Test
    void readRedis() {
        for (int i = 0; i < 10000; i++) {
            masterRedis.opsForValue().get(String.valueOf(i));
        }
    }

执行写入方法后查看redis实例如下:
redis高可用之主从架构_第3张图片
主从架构完。

你可能感兴趣的:(中间件)