天津中小企业网站制作,珠海做网站的,wordpress 旅游主题,中国空间站简笔画1、概述
Redis的主从复制#xff08;Master-Slave Replication#xff09;是一种数据冗余机制#xff0c;它允许将一台Redis服务器的数据复制到其他Redis服务器。在主从复制中#xff0c;有一台主服务器#xff08;Master#xff09;和一个或多个从服务器#xff08;Sl…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.conf2、从节点配置
第一步配置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连接工厂无需再次引入依赖。
dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-data-redis/artifactId
/dependency2、配置类注入工厂
这里直接在配置类中写死了配置如果真实开发建议把配置放到配置文件中。 需要注入主节点连接工厂用于写操作和从节点连接工厂数组用于读操作
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 ThreadLocalBoolean 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 {AutowiredQualifier(masterConnectionFactory)private RedisConnectionFactory masterConnectionFactory; // 主服务器连接工厂Autowiredprivate RedisConnectionFactory[] slaveConnectionFactories; // 从服务器连接工厂列表private final Random random new Random();Overridepublic 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;}Overridepublic boolean getConvertPipelineAndTxResults() {return masterConnectionFactory.getConvertPipelineAndTxResults();}Overridepublic DataAccessException translateExceptionIfPossible(RuntimeException ex) {return masterConnectionFactory.translateExceptionIfPossible(ex);}Overridepublic RedisClusterConnection getClusterConnection() {return null;}Overridepublic 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 {Autowiredprivate RedisTemplateString, 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 {Autowiredprivate 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方法如下可以看到走的逻辑是从节点中任意一个节点的连接。 如上的验证即可说明已经达到了读写分离的效果。
学海无涯苦作舟