Redis主从复制&Redis哨兵机制&Springboot整合哨兵

简介: Redis主从复制&Redis哨兵机制&Springboot整合哨兵

正文


一、Redis主从复制


              主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者为主节点(master),后者称为从节点(slave);数据的复制只能由主节点到从节点。主从复制保证了数据的备份,高可用(配合哨兵机制)、负载均衡(读写分离分担master压力)。


Redis主从数据同步有两种方式,(full resync)全量复制和增量复制(partial resync)


full resync(全量复制)


Redis通过psync命令进行全量复制的过程如下:


从节点判断无法进行部分复制,向主节点发送全量复制的请求;或从节点发送部分复制的请求,但主节点判断无法进行部分复制,就进行全量复制。

主节点收到全量复制的命令后,执行bgsave,在后台生成RDB文件,并使用一个缓冲区(称为复制缓冲区)记录从现在开始执行的所有写命令

主节点的bgsave执行完成后,将RDB文件发送给从节点;从节点首先清除自己的旧数据,然后载入接收的RDB文件,将数据库状态更新至主节点执行bgsave时的数据库状态

主节点将前述复制缓冲区中的所有写命令发送给从节点,从节点执行这些写命令,将数据库状态更新至主节点的最新状态

如果从节点开启了AOF,则会触发bgrewriteaof的执行,从而保证AOF文件更新至主节点的最新状态


partial resync(增量复制)


由于网络原因导致主从服务器断开连接,当主从重新连接之后,不需要全量复制,只需要进行增量复制。因为主从服务器都会维持一个offset(偏移量),当连接恢复之后,对比两者的偏移量,把不同的数据同步过来。


在命令传播阶段,主节点除了将写命令发送给从节点,还会发送一份给积压缓冲区,作为写命令的备份;除了存储写命令,积压缓冲区中还存储了其中的每个字节对应的复制偏移量(offset)。由于复制积压缓冲区定长且是先进先出(队列),所以它保存的是主节点最近执行的写命令;时间较早的写命令会被挤出缓冲区。


由于该缓冲区长度固定且有限,因此可以备份的写命令也有限,当主从节点offset的差距过大超过缓冲区长度时,将无法执行部分复制,只能执行全量复制。反过来说,为了提高网络中断时部分复制执行的概率,可以根据需要增大复制积压缓冲区的大小(通过配置repl-backlog-size 1M 默认是1M);


如果offset偏移量之后的数据,仍然都在复制积压缓冲区里,则执行部分复制;

如果offset偏移量之后的数据已不在复制积压缓冲区中(数据已被挤出),则执行全量复制。

每个Redis节点(无论主从),在启动时都会自动生成一个随机ID(每次启动都不一样),由40个随机的十六进制字符组成;runid用来唯一识别一个Redis节点。通过info Server命令,可以查看节点的runid。


主从节点初次复制时,主节点将自己的runid发送给从节点,从节点将这个runid保存起来;当断线重连时,从节点会将这个runid发送给主节点;主节点根据runid判断能否进行部分复制:


如果从节点保存的runid与主节点现在的runid相同,说明主从节点之前同步过,主节点会继续尝试使用部分复制(到底能不能部分复制还要看offset和复制积压缓冲区的情况);

如果从节点保存的runid与主节点现在的runid不同,说明从节点在断线前同步的Redis节点并不是当前的主节点,只能进行全量复制。


二、Redis主从复制配置


配置方式有两种,这种方式不推荐,如果从服务器过多,数据同步效率很差


555.jpg


采用树状结构


555.jpg


配置主从复制


由于我是在一台虚拟机上模拟所以需要修改响应的端口号为6379,6380,6381


修改响应的 pidfile /var/run/redis_6379.pid,pidfile /var/run/redis_6380.pid,pidfile /var/run/redis_6381.pid


在6380配置文件添加指向主节点


slaveof 192.168.139.154 6379
#密码
masterauth xiaojie


在6381上修改配置文件如下


slaveof 192.168.139.154 6380
masterauth xiaojie


启动redis实例


#由于是同一台虚拟机,启动时候需要指定端口,不然默认是6379
[root@bogon bin]# ./redis-cli -p 6379
[root@bogon bin]# ./redis-cli -p 6380
[root@bogon bin]# ./redis-cli -p 6381


输入指令


127.0.0.1:6379>  info replication


111.png222.png333.png

444.png


此时我们Redis主从复制已经搭建好了,在6379写入数据,其他两个节点能同步数据,从节点从2.6版本之后默认只能读不能写,这个可以配置replica-read-only yes(yes只能读不能写)。这个时候如果主节点宕机之后,从节点不能写,然后服务就不能用了,于是引入哨兵机制。


三、哨兵机制原理


Redis 的 Sentinel 系统用于管理多个 Redis 服务器(instance), 该系统执行以下三个任务:


监控(Monitoring): Sentinel 会不断地检查你的主服务器和从服务器是否运作正常。

提醒(Notification): 当被监控的某个 Redis 服务器出现问题时, Sentinel 可以通过 API 向管理员或者其他应用程序发送通知。

自动故障迁移(Automatic failover): 当一个主服务器不能正常工作时, Sentinel 会开始一次自动故障迁移操作, 它会将失效主服务器的其中一个从服务器升级为新的主服务器, 并让失效主服务器的其他从服务器改为复制新的主服务器; 当客户端试图连接失效的主服务器时, 集群也会向客户端返回新主服务器的地址, 使得集群可以使用新主服务器代替失效服务器。

Redis Sentinel 是一个分布式系统, 你可以在一个架构中运行多个 Sentinel 进程(progress), 这些进程使用流言协议(gossip protocols)来接收关于主服务器是否下线的信息, 并使用投票协议(agreement protocols)来决定是否执行自动故障迁移, 以及选择哪个从服务器作为新的主服务器。


每个 Sentinel 都需要定期执行的任务


每个 Sentinel 以每秒钟一次的频率向它所知的主服务器、从服务器以及其他 Sentinel 实例发送一个 PING 命令。

如果一个实例(instance)距离最后一次有效回复 PING 命令的时间超过 down-after-milliseconds 选项所指定的值, 那么这个实例会被 Sentinel 标记为主观下线(subjectively down,简称 SDOWN )。 一个有效回复可以是: +PONG 、 -LOADING 或者 -MASTERDOWN 。

如果一个主服务器被标记为主观下线, 那么正在监视这个主服务器的所有 Sentinel 要以每秒一次的频率确认主服务器的确进入了主观下线状态。

如果一个主服务器被标记为主观下线, 并且有足够数量的 Sentinel (至少要达到配置文件指定的数量)在指定的时间范围内同意这一判断, 那么这个主服务器被标记为客观下线(objectively down, 简称 ODOWN)。

在一般情况下, 每个 Sentinel 会以每 10 秒一次的频率向它已知的所有主服务器和从服务器发送 INFO 命令。 当一个主服务器被 Sentinel 标记为客观下线时, Sentinel 向下线主服务器的所有从服务器发送 INFO 命令的频率会从 10 秒一次改为每秒一次。

当没有足够数量的 Sentinel 同意主服务器已经下线, 主服务器的客观下线状态就会被移除。 当主服务器重新向 Sentinel 的 PING 命令返回有效回复时, 主服务器的主观下线状态就会被移除。


自动发现 Sentinel 和从服务器


一个 Sentinel 可以与其他多个 Sentinel 进行连接, 各个 Sentinel 之间可以互相检查对方的可用性, 并进行信息交换。


你无须为运行的每个 Sentinel 分别设置其他 Sentinel 的地址, 因为 Sentinel 可以通过发布与订阅功能来自动发现正在监视相同主服务器的其他 Sentinel , 这一功能是通过向频道 sentinel:hello 发送信息来实现的。


与此类似, 你也不必手动列出主服务器属下的所有从服务器, 因为 Sentinel 可以通过(info)询问主服务器来获得所有从服务器的信息。


每个 Sentinel 会以每两秒一次的频率, 通过发布与订阅功能, 向被它监视的所有主服务器和从服务器的 sentinel:hello 频道发送一条信息, 信息中包含了 Sentinel 的 IP 地址、端口号和运行 ID (runid)。

每个 Sentinel 都订阅了被它监视的所有主服务器和从服务器的 sentinel:hello 频道, 查找之前未出现过的 sentinel (looking for unknown sentinels)。 当一个 Sentinel 发现一个新的 Sentinel 时, 它会将新的 Sentinel 添加到一个列表中, 这个列表保存了 Sentinel 已知的, 监视同一个主服务器的所有其他 Sentinel 。

Sentinel 发送的信息中还包括完整的主服务器当前配置(configuration)。 如果一个 Sentinel 包含的主服务器配置比另一个 Sentinel 发送的配置要旧, 那么这个 Sentinel 会立即升级到新配置上。

在将一个新 Sentinel 添加到监视主服务器的列表上面之前, Sentinel 会先检查列表中是否已经包含了和要添加的 Sentinel 拥有相同运行 ID 或者相同地址(包括 IP 地址和端口号)的 Sentinel , 如果是的话, Sentinel 会先移除列表中已有的那些拥有相同运行 ID 或者相同地址的 Sentinel(自己发布的消息) , 然后再添加新 Sentinel 。


Sentinel 选主规则


在失效主服务器属下的从服务器当中, 那些被标记为主观下线、已断线、或者最后一次回复PING 命令的时间大于五秒钟的从服务器都会被淘汰。

在失效主服务器属下的从服务器当中, 那些与失效主服务器连接断开的时长超过 down-after 选项指定的时长十倍的从服务器都会被淘汰。

在经历了以上两轮淘汰之后剩下来的从服务器中, 我们选出复制偏移量(replication offset)最大的那个从服务器作为新的主服务器; 如果复制偏移量不可用, 或者从服务器的复制偏移量相同, 那么带有最小运行 ID(runid) 的那个从服务器成为新的主服务器。


四、哨兵机制配置


复制redis解压文件中的sentinel.conf文件到安装目录的bin下面


[root@bogon bin]# cp /usr/local/redis-6.2.5/sentinel.conf /usr/local/redis6379/bin/
[root@bogon bin]# cp /usr/local/redis-6.2.5/sentinel.conf /usr/local/redis6380/bin/
[root@bogon bin]# cp /usr/local/redis-6.2.5/sentinel.conf /usr/local/redis6381/bin/
[root@bogon bin]# 


修改sentinel.conf


#后台启动
daemonize yes
#修改端口
port 26379-26381 
#指定pid
 pidfile /var/run/redis-sentinel-6379.pid
#指定监听的主节点 2是至少有2个哨兵认为宕机的个数
sentinel monitor mymaster 192.168.139.154 6379 2
#指定主节点密码
sentinel auth-pass mymaster xiaojie
#指定哨兵的密码
requirepass xiaojie
#打开这个注释
protected-mode no


启动哨兵

./redis-sentinel sentinel.conf


然后手动宕机6379服务节点。


启动6379的节点再检查info replication


111.png

222.png

333.png


 此时6380节点被选举为master,6379原来的master成为了slave。


五、哨兵机制整合SpringBoot


来 上才艺,不对 !上代码


555.jpg


pom文件


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.xiaojie</groupId>
    <artifactId>springboot-redis</artifactId>
    <version>1.0-SNAPSHOT</version>
    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>
    <parent>
        <artifactId>spring-boot-starter-parent</artifactId>
        <groupId>org.springframework.boot</groupId>
        <version>2.4.2</version>
        <relativePath/>
    </parent>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.4</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.12</version>
        </dependency>
<!--序列化-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.47</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.10.4</version>
        </dependency>
        <!--redis-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <version>2.2.7.RELEASE</version>
        </dependency>
        <!--lombok-->
        <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.20</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
</project>


配置文件


spring:
  jackson:
    time-zone: GMT+8
    date-format: yyyy-MM-dd HH:mm:ss
  datasource:
    name: iot-home
    url: jdbc:mysql://127.0.0.1:3306/my_test?useUnicode=true&characterEncoding=UTF-8&rewriteBatchedStatements=true&allowMultiQueries=true&serverTimezone=Asia/Shanghai
    username: root
    password: root
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
  redis:
    password: xiaojie #这个密码一定要加,然后才能保证能连接到redis服务器,如果没有配置密码则不需要
    connect-timeout: 5000
    database: 0
    sentinel:
      master: mymaster
      nodes: 192.168.6.137:26379,192.168.6.137:26380,192.168.6.137:26381
      password: xiaojie #这个密码是哨兵的密码,如果在sentinel.conf中没有配置requirepass则不需要


核心代码


package com.xiaojie.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.net.UnknownHostException;
/*
 *
 * @param null
 * @redis配置
 * @author xiaojie
 * @date 2021/9/8 
 * @return
 */
@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
            throws UnknownHostException {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        StringRedisSerializer stringRedisSerializer=new StringRedisSerializer();
        redisTemplate.setKeySerializer(stringRedisSerializer);
        redisTemplate.setHashKeySerializer(stringRedisSerializer);
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer=new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper objectMapper=new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance ,
                ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
}


   public User getUserByName(String name) {
        JSONObject obj= (JSONObject) redisUtil.get(USERKEY + ":" + name);
        if (null==obj){
            System.out.println("缓存中没有该值,查询数据库");
            User resultUser = userMapper.selectByName(name);
            if (null!= resultUser) {
                redisUtil.set(USERKEY+":"+resultUser.getName(), JSONObject.toJSON(resultUser),30);
                return resultUser;
            }
        }
        User user = JSONObject.toJavaObject(obj,User.class);
        return user;
    }


测试:宕机主节点,然后还能继续写入缓存,搭建完成。


完整代码 :spring-boot: Springboot整合redis、消息中间件等相关代码


参考:


深入学习Redis(3):主从复制 - 编程迷思 - 博客园

REDIS sentinel-old -- Redis中国用户组(CRUG)

相关文章
|
12月前
|
NoSQL Java 网络安全
SpringBoot启动时连接Redis报错:ERR This instance has cluster support disabled - 如何解决?
通过以上步骤一般可以解决由于配置不匹配造成的连接错误。在调试问题时,一定要确保服务端和客户端的Redis配置保持同步一致。这能够确保SpringBoot应用顺利连接到正确配置的Redis服务,无论是单机模式还是集群模式。
824 5
|
NoSQL Java 调度
分布式锁与分布式锁使用 Redis 和 Spring Boot 进行调度锁(不带 ShedLock)
分布式锁是分布式系统中用于同步多节点访问共享资源的机制,防止并发操作带来的冲突。本文介绍了基于Spring Boot和Redis实现分布式锁的技术方案,涵盖锁的获取与释放、Redis配置、服务调度及多实例运行等内容,通过Docker Compose搭建环境,验证了锁的有效性与互斥特性。
1003 0
分布式锁与分布式锁使用 Redis 和 Spring Boot 进行调度锁(不带 ShedLock)
|
存储 监控 NoSQL
Redis高可用架构全解析:从主从复制到集群方案
Redis高可用确保服务持续稳定,避免单点故障导致数据丢失或业务中断。通过主从复制实现数据冗余,哨兵模式支持自动故障转移,Cluster集群则提供分布式数据分片与水平扩展,三者层层递进,保障读写分离、容灾切换与大规模数据存储,构建高性能、高可靠的Redis架构体系。
|
监控 NoSQL Redis
Redis哨兵机制
Redis哨兵机制用于解决主从复制中主节点故障无法自动恢复的问题。通过独立进程监控主节点状态,在主节点宕机时,多个哨兵协作选举新主节点并完成故障转移,实现高可用性。哨兵机制具备监控、故障转移和通知功能,支持自动切换主从关系,但无法提升存储容量和完全避免数据丢失。
|
存储 负载均衡 NoSQL
Redis主从复制
在分布式系统中,为解决单点故障和提升性能,常采用Redis主从复制架构。通过将数据复制到多个从节点,实现读写分离、负载均衡及高可用性,同时支持多种拓扑结构以适应不同场景需求。
|
负载均衡 NoSQL Redis
【赵渝强老师】Redis的主从复制集群
Redis主从复制是指将一台Redis服务器的数据复制到其他Redis服务器,实现数据热备份、故障恢复、负载均衡及高可用架构的基础。主节点负责写操作,从节点同步数据并可提供读服务,提升并发处理能力。
482 5
|
缓存 NoSQL 算法
Redis数据库的键值过期和删除机制
我们需要注意的是,虽然Redis提供了这么多高级的缓存机制,但在使用过程中,必须理解应用的特性,选择合适的缓存策略,才能最大化Redis的性能。因此,在设计和实施应用程序时,理解应用的数据访问模式,以及这些模式如何与Redis的缓存机制相互作用,尤为重要。
449 24
|
机器学习/深度学习 数据采集 人机交互
springboot+redis互联网医院智能导诊系统源码,基于医疗大模型、知识图谱、人机交互方式实现
智能导诊系统基于医疗大模型、知识图谱与人机交互技术,解决患者“知症不知病”“挂错号”等问题。通过多模态交互(语音、文字、图片等)收集病情信息,结合医学知识图谱和深度推理,实现精准的科室推荐和分级诊疗引导。系统支持基于规则模板和数据模型两种开发原理:前者依赖人工设定症状-科室规则,后者通过机器学习或深度学习分析问诊数据。其特点包括快速病情收集、智能病症关联推理、最佳就医推荐、分级导流以及与院内平台联动,提升患者就诊效率和服务体验。技术架构采用 SpringBoot+Redis+MyBatis Plus+MySQL+RocketMQ,确保高效稳定运行。
1037 0
基于springboot+thymeleaf+Redis仿知乎网站问答项目源码
基于springboot+thymeleaf+Redis仿知乎网站问答项目源码
488 36
|
缓存 NoSQL Redis
Redis原理—3.复制、哨兵和集群
详细介绍了Redis的复制原理、哨兵原理和集群原理。

热门文章

最新文章