Redis篇-19--运维篇1-主从复制(主从复制,读写分离,配置实现,实战案例)

news2024/12/20 13:25:33

1、概述

Redis的主从复制(Master-Slave Replication)是一种数据冗余机制,它允许将一台Redis服务器的数据复制到其他Redis服务器。在主从复制中,有一台主服务器(Master)和一个或多个从服务器(Slave)。主服务器负责写操作,而从服务器可以用于读操作,从而实现读写分离,减轻主服务器的负载压力。

示例图:
在这里插入图片描述

2、主从复制中的基本概念

- 主服务器(Master):负责处理写操作(如SET、DEL等),并将这些操作同步给从服务器。
- 从服务器(Slave):只读服务器,默认情况下不允许写操作(可以通过配置修改)。从服务器会定期从主服务器获取最新的数据更新,并保持与主服务器的数据一致。
- 数据流向:数据的复制是单向的,只能由主服务器到从服务器。从服务器不能将数据写回主服务器。

3、主从复制的作用

- 数据冗余:通过主从复制,可以在多台服务器上保存相同的数据副本,提供热备份,防止因单点故障导致数据丢失。
- 故障恢复:当主服务器出现问题时,可以从服务器可以接管服务,确保系统的高可用性。
- 负载均衡:通过读写分离,主服务器处理写操作,从服务器处理读操作,分担主服务器的负载,提升系统的并发处理能力。
- 高可用性基础:主从复制是Redis集群和哨兵(Sentinel)系统的基础,支持自动故障转移和高可用性部署。

4、主从复制的工作原理

Redis的主从复制分为两个主要阶段:
初次同步(全量同步)和命令传播(增量同步)。

(1)、初次同步(Full Resynchronization)

初次同步发生条件:

- 第一次建立主从关系:当从服务器首次连接到主服务器时,需要进行全量同步,以确保从服务器拥有与主服务器完全一致的数据集。
- 部分同步失败:如果从服务器与主服务器之间的连接中断时间过长,导致无法通过部分同步恢复数据一致性,也会触发全量同步。

全量同步(初始同步)的过程:

1、从服务器发起请求:从服务器通过配置文件的slaveof(或replicaof)检查自身需要同步的主节点信息。之后通过PSYNC命令携带replid(唯一身份标识符),offset(处理数据的偏移量)参数向主服务器发起数据同步请求。
2、主服务器确认请求:主服务器会校验从服务器的replid是否存在,不存在即表示第一次同步。主服务器会返回replid和offset和从服务器建立连接。
3、主服务器生成RDB文件:同时,主服务器会在后台执行BGSAVE操作,生成一个RDB快照文件。同时,主服务器会开启一个缓冲区,记录从BGSAVE期间的所有写命令。
4、传输RDB文件:BGSAVE完成后,主服务器将生成的RDB文件发送给从服务器。
5、加载RDB文件:从服务器接收到RDB文件后,会清空本地数据,加载RDB文件,重建与主服务器一致的数据集。
6、重放命令:从服务器加载完RDB文件后,主服务器会将缓冲区中的写命令发送给从服务器,确保数据完全一致。

全量过程原理示例如下:
在这里插入图片描述

简单总结:

首次同步是从服务发起,之后RDB文件同步,缓冲区写日志同步,都是主服务主动发起给从服务器的。

(2)、命令传播(Partial Resynchronization)

初次同步完成后,主服务器和从服务器之间会进入命令传播阶段。在这个阶段,主服务器会将所有的写命令实时发送给从服务器,确保两者的数据保持一致。

命令传播(增量同步)的过程:

1、主服务器记录命令:每当主服务器执行写命令时,它会将这些命令记录到一个缓冲区中。
2、从服务器请求命令:从服务器会定期向主服务器发送psync命令,告知主服务器它已经处理到的命令的偏移量offset。
3、主服务器发送命令:主服务器根据从服务器的偏移量offset,将未处理的命令发送给从服务器。
4、从服务器执行命令:从服务器接收到命令后,会立即执行这些命令,确保与主服务器的数据保持一致。
在这里插入图片描述

简单总结:

命令传播是从服务发起(携带自己身份和偏移量),主服务根据偏移量获取缓冲区写日志记录,发送给从服务器,这个过程是从服务主动发起数据请求的。

(3)、部分同步(Partial Resynchronization)

Redis从2.8版本开始引入了部分同步机制,以减少全量同步的频率。部分同步允许从服务器在与主服务器断开连接后,重新连接时只获取断开期间丢失的命令,而不是重新进行全量同步。其目的是为了优化全量同步的性能问题,减少全量同步的频率。

部分同步的过程:

1、从服务器保留复制偏移量offset:从服务器会记录最后一次成功同步的命令偏移量(offset)以及主服务器的replid(唯一标识符)。
2、从服务器发起部分同步请求:当从服务器重新连接到主服务器时,它会通过PSYNC 命令请求部分同步。
3、主服务器检查缓冲区:主服务器会检查它的复制积压缓冲区(Replication Backlog),判断是否包含从服务器请求的命令。
4、发送命令:如果缓冲区中包含从服务器请求的命令,主服务器会将这些命令发送给从服务器,完成部分同步;否则,主服务器会触发全量同步。

5、复制积压缓冲区(Replication Backlog)

复制积压缓冲区是一个固定大小的循环缓冲区,主服务器会将所有写命令记录到这个缓冲区中。它主要用于支持部分同步机制,确保从服务器在断开连接后能够快速恢复数据一致性。

  • 默认大小:1MB(可以通过repl-backlog-size参数调整)。
  • 作用:当从服务器与主服务器短暂断开连接时,主服务器可以通过复制积压缓冲区将断开期间的命令发送给从服务器,避免频繁的全量同步。

6、主从复制的优缺点

优点:

  • 数据冗余:提供热备份,防止数据丢失。
  • 高可用性:支持故障恢复和自动故障转移(结合 Redis Sentinel 使用)。
  • 负载均衡:通过读写分离,减轻主服务器的负载,提升系统性能。
  • 简单易用:配置简单,易于维护。

缺点:

  • 数据一致性问题:由于主从复制是异步的,从服务器可能会存在一定的延迟,导致主从数据不完全一致。
  • 单点故障:如果主服务器发生故障且没有及时切换到从服务器,可能会导致服务中断。
  • 网络带宽消耗:主服务器需要将写命令实时发送给从服务器,可能会占用较多的网络带宽。

7、主从复制的优化建议

1、启用持久化:为了防止主服务器崩溃后数据丢失,建议为主服务器启用持久化( RDB或AOF),并定期备份数据。
2、合理配置复制积压缓冲区:根据业务需求调整复制积压缓冲区的大小,确保从服务器在断开连接后能够快速恢复。
3、监控主从延迟:定期监控主从服务器之间的延迟,确保从服务器能够及时同步主服务器的数据。
4、使用哨兵系统:结合Redis Sentinel系统,实现自动故障检测和主从切换,提升系统的高可用性。
5、限制从服务器的数量:过多的从服务器可能会增加主服务器的负担,建议根据实际需求合理配置从服务器的数量。

8、配置示例

1、主节点配置

第一步:配置ip和port
在这里插入图片描述
第二步:指定配置文件启动redis服务:
启动命令如:

redis-server.exe redis.windows.conf

在这里插入图片描述

2、从节点配置

第一步:配置ip和port
在这里插入图片描述
第二步:配置密码
如果主服务器设置了密码,从服务需要配置主服务器的密码

masterauth master_password

在这里插入图片描述
第三步:配置从节点所属主节点的信息
注意:5.0版本后的redis使用replicaof替代了slaveof。配置和功能是相同的。
在这里插入图片描述
第四步:指定配置文件启动从服务redis
如:redis-server.exe redis.windows.conf
启动从节点后,可以在日志中看到主从复制的相关日志,如下图:
在这里插入图片描述

3、验证主从复制

如下图:左边为主节点redis-cli,右边为从节点redis-cli。
主节点设置aaa1的key值,从节点没有设置该key。
在从节点redis-cli中,直接查看aaa1的key信息,可以正确访问。
说明主从同步关系配置是正确且正常工作的。
在这里插入图片描述

9、代码实现

1、导入依赖

说明下:springboot自带了LettuceConnectionFactory连接工厂,无需再次引入依赖。

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

2、配置类,注入工厂

这里直接在配置类中写死了配置,如果真实开发建议把配置放到配置文件中。
需要注入主节点连接工厂(用于写操作)和从节点连接工厂数组(用于读操作)

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;

@Configuration
public class RedisConfig {

    // 主服务器连接工厂
    @Bean(name = "masterConnectionFactory")
    public LettuceConnectionFactory masterConnectionFactory() {
        RedisStandaloneConfiguration config = new RedisStandaloneConfiguration("127.0.0.1", 6379);
//     config.setPassword("your_master_password");  // 如果主服务器启用了密码保护
        return new LettuceConnectionFactory(config);
    }

    @Bean(name = "slaveConnectionFactories")
    public RedisConnectionFactory[] slaveConnectionFactories() {
        RedisStandaloneConfiguration slave1Config = new RedisStandaloneConfiguration("127.0.0.1", 6380);
//        slave1Config.setPassword("your_master_password");  // 如果服务器启用了密码保护
        RedisStandaloneConfiguration slave2Config = new RedisStandaloneConfiguration("127.0.0.1", 6381);
//        slave2Config.setPassword("your_master_password");  // 如果服务器启用了密码保护
        return new RedisConnectionFactory[]{
                new LettuceConnectionFactory(slave1Config),
                new LettuceConnectionFactory(slave2Config)
        };
    }
}

3、自定义注解

用于标注redis操作的方法上,指明该方法走写连接还是读连接。

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ReadWrite {
    boolean read() default true;  // 默认为读操作
}

4、定义AOP,实现注解类的功能

AOP用于监听Redis操作方法上的自定义注解内容,将读写的标识保存到线程的ThreadLocal中。之后可以根据这里的标识切换读写连接工厂,从而实现读写分离的效果。

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class ReadWriteAspect {
    // 线程局部变量,用于存储当前操作是否为读操作
    private static final ThreadLocal<Boolean> READ_OPERATION = new ThreadLocal<>();

    @Around("@annotation(ReadWrite)")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        // 获取方法上的 @ReadWrite注解
        ReadWrite readWrite = ((MethodSignature) joinPoint.getSignature()).getMethod().getAnnotation(ReadWrite.class);
        try {
            // 设置线程局部变量,指示当前操作是读还是写
            setReadOperation(readWrite.read());
            // 执行方法
            return joinPoint.proceed();
        } finally {
            // 清除线程局部变量
            setReadOperation(null);     // 清除ThreadLocal的值
        }
    }

    // 设置线程局部变量,指示当前操作是读还是写
    public static void setReadOperation(Boolean read) {
        READ_OPERATION.set(read);     // 保存到ThreadLocal中,用于之后切换数据工厂来源
    }

    // 获取线程局部变量,判断当前操作是否为读操作
    public static Boolean isReadOperation() {
        return READ_OPERATION.get();
    }
}

5、读写连接切换实现类

实现RedisConnectionFactory类,并标明@Primary方法,相当于告诉spring容器,在所有容器中的redis连接工厂里,以当前工厂为主。
即:redisTemplate默认使用这个工厂创建redis连接实例,这里我们复写getConnection方法,用于读写连接的自动切换(实现:则是通过ThreadLocal中保存的读写标识)。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Primary;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisClusterConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisSentinelConnection;
import org.springframework.stereotype.Component;
import java.util.Random;

@Component
@Primary  // 标记为默认的连接工厂
public class ReadWriteSplittingConnectionFactory implements RedisConnectionFactory {

    @Autowired
    @Qualifier("masterConnectionFactory")
    private RedisConnectionFactory masterConnectionFactory;  // 主服务器连接工厂
    @Autowired
    private RedisConnectionFactory[] slaveConnectionFactories;  // 从服务器连接工厂列表
    private final Random random = new Random();

    @Override
    public org.springframework.data.redis.connection.RedisConnection getConnection() {
        if (isReadOperation()) {    // 根据线程局部变量ThreadLocal中判断是读还是写
            // 在从服务器数组中随机选择一个连接
            int index = random.nextInt(slaveConnectionFactories.length);
            return slaveConnectionFactories[index].getConnection();
        } else {
            // 写操作使用主服务器连接
            return masterConnectionFactory.getConnection();
        }
    }

    // 判断当前操作是否为读操作
    private boolean isReadOperation() {
        // 获取线程局部变量,指示当前操作是读还是写
        Boolean isRead = ReadWriteAspect.isReadOperation();
        return isRead != null && isRead;
    }

    @Override
    public boolean getConvertPipelineAndTxResults() {
        return masterConnectionFactory.getConvertPipelineAndTxResults();
    }

    @Override
    public DataAccessException translateExceptionIfPossible(RuntimeException ex) {
        return masterConnectionFactory.translateExceptionIfPossible(ex);
    }

    @Override
    public RedisClusterConnection getClusterConnection() {
        return null;
    }

    @Override
    public RedisSentinelConnection getSentinelConnection() {
        return null;
    }
}

6、定义redis的工具类,使用注解

编写Redis公共方法,使用自定义注解标识读写操作。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

@Service
public class RedisService {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    // 写操作
    @ReadWrite(read = false)     // 相当于指定了写的连接工厂创建连接
    public void set(String key, String value) {
        redisTemplate.opsForValue().set(key, value);
    }

    // 读操作
    @ReadWrite(read = true)   // 相当于指定了读的连接工厂创建连接
    public String get(String key) {
        return (String) redisTemplate.opsForValue().get(key);
    }
}

7、测试类

编写接口,调用Redis的读写方法,验证

import com.zw.base.BaseController;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping(value = "redis", method = {RequestMethod.POST, RequestMethod.GET})
public class RedisController extends BaseController {

    @Autowired
    private RedisService redisService;

    @RequestMapping("/set")
    public String setTest() {
        redisService.set("aaa3", "zhangsan3");
        return null;
    }

    @RequestMapping("/get")
    public String getTest() {
        String aaa2 = redisService.get("aaa2");
        System.out.println("aaa2:" + aaa2);
        return aaa2;
    }

}

8、验证结果

测试类中的set和get方法都正常使用。
在这里插入图片描述
但是这并不能直接看出读写到底走的那一个redis服务。这里可以断点看一下,当调用set方法时,可以看到走的逻辑是主节点的连接。
在这里插入图片描述
当调用get方法,如下可以看到,走的逻辑是从节点中任意一个节点的连接。
在这里插入图片描述
如上的验证即可说明,已经达到了读写分离的效果。

学海无涯苦作舟!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2262730.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【ORACLE】一个允许关键字作为别名所引起的语法歧义场景

前言 最近在看SQL语法解析器&#xff0c;发现了antlr4提供的PlSql语法树存在一个BUG&#xff0c;然后我顺着这个BUG&#xff0c;构造了一条SQL&#xff0c;在ORACLE执行&#xff0c;如下 然后神奇的事情出现了&#xff0c;这个查询竟然没有返回行&#xff01;t1表左关联t2&…

【前端】Jquery拍照,通过PHP将base64编码数据转换成PNG格式,并保存图像到本地

目录 一、需求 二、开发语言 三、效果 四、业务逻辑&#xff1a; 五、web端调用摄像头 六、示例代码 1、前端 2、后端 一、需求 web端使用jquery调用摄像头拍照&#xff0c;并使用PHP把base64编码转换成png格式图片&#xff0c;下载到本地。 由于js不能指定图片存储的…

本地摄像头视频流在html中打开

1.准备ffmpeg 和(rtsp-simple-server srs搭建流媒体服务器)视频服务器. 2.解压视频流服务器修改配置文件mediamtx.yml ,hlsAlwaysRemux: yes 3.双击运行服务器。 4&#xff0c;安装ffmpeg ,添加到环境变量。 5.查询本机设备列表 ffmpeg -list_devices true -f dshow -i d…

机器情绪及抑郁症识别算法(六)

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢&#xff0c;在这里我会分享我的知识和经验。&am…

34. Three.js案例-创建球体与模糊阴影

34. Three.js案例-创建球体与模糊阴影 实现效果 知识点 WebGLRenderer WebGLRenderer 是 Three.js 中用于渲染 3D 场景的核心类。它负责将场景中的对象绘制到画布上。 构造器 new THREE.WebGLRenderer(parameters)参数类型描述parametersObject可选参数对象&#xff0c;包…

服务器数据恢复—RAIDZ离线硬盘数超过热备盘数导致阵列崩溃的数据恢复案例

服务器存储数据恢复环境&#xff1a; ZFS Storage 7320存储阵列中有32块硬盘。32块硬盘分为4组&#xff0c;每组8块硬盘&#xff0c;共组建了3组RAIDZ&#xff0c;每组raid都配置了热备盘。 服务器存储故障&#xff1a; 服务器存储运行过程中突然崩溃&#xff0c;排除人为误操…

108. 将有序数组转换为二叉搜索树(java)

题目描述&#xff1a; 给你一个整数数组 nums &#xff0c;其中元素已经按 升序 排列&#xff0c;请你将其转换为一棵 平衡 二叉搜索树。 示例 1&#xff1a; 输入&#xff1a;nums [-10,-3,0,5,9] 输出&#xff1a;[0,-3,9,-10,null,5] 解释&#xff1a;[0,-10,5,null,-3,…

电子应用设计方案-60:智能床垫系统方案设计

智能床垫系统方案设计 一、引言 智能床垫作为智能家居的一部分&#xff0c;旨在为用户提供更舒适的睡眠体验和健康监测功能。本方案将详细描述智能床垫系统的设计理念、功能模块及技术实现。 二、系统概述 1. 系统目标 - 实时监测睡眠状态&#xff0c;包括心率、呼吸、体动等…

YOLOv8目标检测(六)_封装API接口

YOLOv8目标检测(一)_检测流程梳理&#xff1a;YOLOv8目标检测(一)_检测流程梳理_yolo检测流程-CSDN博客 YOLOv8目标检测(二)_准备数据集&#xff1a;YOLOv8目标检测(二)_准备数据集_yolov8 数据集准备-CSDN博客 YOLOv8目标检测(三)_训练模型&#xff1a;YOLOv8目标检测(三)_训…

CSDN数据大屏可视化【开源】

项目简介 本次基于版本3 开源 版本3开源地址&#xff1a;https://github.com/nangongchengfeng/CsdnBlogBoard.git 版本1开源地址&#xff1a;https://github.com/nangongchengfeng/CSDash.git 这是一个基于 Python 的 CSDN 博客数据可视化看板项目&#xff0c;通过爬虫采…

Moretl安全日志采集工具

永久免费: 至Gitee下载 使用教程: Moretl使用说明 使用咨询: 用途 定时全量或增量采集工控机,电脑文件或日志. 优势 开箱即用: 解压直接运行.不需额外下载.管理设备: 后台统一管理客户端.无人值守: 客户端自启动,自更新.稳定安全: 架构简单,兼容性好,通过授权控制访问. 架…

无人机航测系统技术特点!

一、无人机航测系统的设计逻辑 无人机航测系统的设计逻辑主要围绕实现高效、准确、安全的航空摄影测量展开。其设计目标是通过无人机搭载相机和传感器&#xff0c;利用先进的飞行控制系统和数据处理技术&#xff0c;实现对地表信息的全方位、高精度获取。 需求分析&#xff1…

Java学习笔记(13)——面向对象编程

面向对象基础 目录 面向对象基础 方法重载 练习&#xff1a; 继承 继承树 protected super 阻止继承 向上转型 向下转型 区分继承和组合 练习 小结&#xff1a; 方法重载 如果有一系列方法&#xff0c;功能类似&#xff0c;只是参数有所不同&#xff0c;就可以把…

Running CMake (运行 CMake)

Running CMake {运行 CMake} 1. CLion - Create a new CMake project2. Running CMake (运行 CMake)2.1. Building a project (构建项目)2.2. Picking a compiler (指定编译器)2.3. Verbose and partial builds (详细和部分的构建)2.4. Options (选项)2.4.1. Standard options …

穷举vs暴搜vs深搜vs回溯vs剪枝专题一>子集

题目&#xff1a; 两个方法本质就是决策树的画法不同 方法一解析&#xff1a; 代码&#xff1a; class Solution {private List<List<Integer>> ret;//返回结果private List<Integer> path;//记录路径&#xff0c;注意返回现场public List<List<Int…

MTU 使用使用解释

MTU (Maximum Transmission Unit&#xff0c;最大传输单元) 指的是网络链路层 (例如以太网) 能够传输的最大数据帧大小&#xff0c;以字节为单位。理解 MTU 对网络性能和可靠性至关重要&#xff0c;因为它直接影响数据包的分片 (Fragmentation) 和重组。本文档将详细解释 MTU 的…

uniapp v-tabs修改了几项功能,根据自己需求自己改

根据自己的需求都可以改 这里写自定义目录标题 1.数组中的名字过长&#xff0c;导致滑动异常2.change 事件拿不到当前点击的数据&#xff0c;通过index在原数组中查找得到所需要的id 各种字段麻烦3.添加指定下标下新加红点显示样式 1.数组中的名字过长&#xff0c;导致滑动异常…

iOS - 超好用的隐私清单修复脚本(持续更新)

文章目录 前言开发环境项目地址下载安装隐私访问报告隐私清单模板最后 前言 在早些时候&#xff0c;提交应用到App Store审核&#xff0c;大家应该都收到过类似这样的邮件&#xff1a; Although submission for App Store review was successful, you may want to correct th…

c语言-----数组

基本概念 数组是C语言中一种用于存储多个相同类型数据的数据结构。这些数据在内存中是连续存储的&#xff0c;可以通过索引&#xff08;下标&#xff09;来访问数组中的各个元素。数组的索引从0开始&#xff0c;这是C语言的规定。例如&#xff0c;一个有n个元素的数组&#xff…

社区版 IDEA 开发webapp 配置tomcat

1.安装tomcat 参考Tomcat配置_tomcat怎么配置成功-CSDN博客 2.构建webapp项目结构 新建一个普通项目 然后添加webapp的目录结构&#xff1a; main目录下新建 webapp 文件夹 webapp文件夹下新建WEB_INF文件夹 *WEB_INF目录下新建web.xml wenapp文件夹下再新建index.html …