Spring Boot实现Redis多数据源动态切换 | Spring Cloud 32

news/2024/5/18 23:43:35/文章来源:https://blog.csdn.net/ctwy291314/article/details/129816237

一、前言

在前面我们通过以下章节对Redis使用有了基础的了解:

Spring Boot实现Redis同数据源动态切换DB | Spring Cloud 31

此章节基于spring-boot-starter-data-redis模块,实现了Redis多数据源动态切换,具体功能如下:

  • 突破一个项目只能一个连接Redis数据源的限制
  • 提供多种操作切换Redis数据源的方式(@Autowired方式 和RedisSelectSupport方式)
  • 提供完善的代码使用示例

二、项目结构

在这里插入图片描述

  • RedisSelect:自定义注解,用于标注要切换的Redis数据源

  • RedisSelectSupport:自定义多数据源切换支持,用于线程间传递多数据源标识

  • MultiRedisProperties:获取配置文件中Redis多数据源属性定义

  • RedisInstanceSelectAspect:自定义AOP切面,对RedisSelect注解方法进行拦截,调用RedisSelectSupport,利用RedisSelectSupport设置数据源标识

  • MultiRedisLettuceConnectionFactory:自定义Redis 数据源的接口,利用RedisSelectSupport获取Redis数据源标识,实现多数据源切换

  • RedisInstanceConfig:实现MultiRedisLettuceConnectionFactory定义,读取MultiRedisProperties属性实现各数据源的LettuceConnectionFactory,将多数据源组装为Map

下文对各类的功能描述不进行重复介绍,请详细了解本章节。

本示例源码位于项目的redis/redis-multi-instance模块下。

三、动态切换Redis多数据源实现

3.1 所需依赖

redis/redis-multi-instance/pom.xml

<?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"><parent><artifactId>redis</artifactId><groupId>com.gm</groupId><version>0.0.1-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>redis-multi-instance</artifactId><properties><maven.compiler.source>17</maven.compiler.source><maven.compiler.target>17</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.83</version></dependency><dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId></dependency></dependencies></project>

3.2 配置文件

src/main/resources/application.yml

server:port: 6009spring:application:name: @artifactId@redis:database: 1timeout: 60000lettuce:pool:max-active: -1max-idle: -1max-wait: -1min-idle: -1enable-multi: truemulti:default:database: 1host: password: port: 6379timeout: 60000lettuce:pool:max-active: -1max-idle: -1max-wait: -1min-idle: -1enable-multi: truetwo:database: 1host: port: 6379password: timeout: 60000lettuce:pool:max-active: -1max-idle: -1max-wait: -1min-idle: -1logging:level:org:springframework:boot:autoconfigure:logging: info

3.3 自定义注解类@RedisSelect

com/gm/multi/redis/config/select/RedisSelectSupport.java

import java.lang.annotation.*;/*** 注解,用于切换不同数据源的redis*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RedisSelect {/*** redis库   0 - 15  库** @return*/String instance() default "default";
}

3.4 多数据源属性读取MultiRedisProperties

com/gm/multi/redis/config/select/instance/MultiRedisProperties.java

import lombok.Data;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.Map;@Data
@Component
@ConfigurationProperties(prefix = "spring.redis")
public class MultiRedisProperties {/*** 默认连接必须配置,配置 key 为 default*/public static final String DEFAULT = "default";private boolean enableMulti = false;private Map<String, RedisProperties> multi;
}

3.5 自定义切面类RedisInstanceSelectAspect

com/gm/multi/redis/config/select/aspect/RedisInstanceSelectAspect.java

import com.gm.multi.redis.config.select.RedisSelectSupport;
import com.gm.multi.redis.config.select.annotation.RedisSelect;
import com.gm.multi.redis.config.select.instance.MultiRedisLettuceConnectionFactory;
import com.gm.multi.redis.config.select.instance.MultiRedisProperties;
import lombok.extern.slf4j.Slf4j;
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.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;@Slf4j
@Aspect
@Component
public class RedisInstanceSelectAspect {private final String defaultInstance = MultiRedisProperties.DEFAULT;/*** 创建RedisSelect对应的切面,来对标有注解的方法拦截** @param point* @return* @throws Throwable*/@Around("@annotation(com.gm.multi.redis.config.select.annotation.RedisSelect)")@ConditionalOnBean(MultiRedisLettuceConnectionFactory.class)public Object configRedis(ProceedingJoinPoint point) throws Throwable {String instance = defaultInstance;try {MethodSignature signature = (MethodSignature) point.getSignature();Method method = signature.getMethod();RedisSelect config = method.getAnnotation(RedisSelect.class);if (config != null) {instance = config.instance();}RedisSelectSupport.selectInstance(instance);return point.proceed();} finally {RedisSelectSupport.selectInstance(defaultInstance);log.debug("redis instance reset {} to {}", instance, defaultInstance);}}
}

3.6 自定义数据源MultiRedisLettuceConnectionFactory

com/gm/multi/redis/config/select/instance/MultiRedisLettuceConnectionFactory.java

import com.gm.multi.redis.config.select.RedisSelectSupport;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.*;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.util.StringUtils;import java.util.Map;/*** 自定义数据源,实现多数据源切换,利用RedisSelectSupport获取Redis数据源标识,实现多数据源切换*/
public class MultiRedisLettuceConnectionFactoryimplements InitializingBean, DisposableBean, RedisConnectionFactory, ReactiveRedisConnectionFactory {public Map<String, LettuceConnectionFactory> getConnectionFactoryMap() {return connectionFactoryMap;}private final Map<String, LettuceConnectionFactory> connectionFactoryMap;public MultiRedisLettuceConnectionFactory(Map<String, LettuceConnectionFactory> connectionFactoryMap) {this.connectionFactoryMap = connectionFactoryMap;}@Overridepublic void destroy() {connectionFactoryMap.values().forEach(LettuceConnectionFactory::destroy);}@Overridepublic void afterPropertiesSet() {connectionFactoryMap.values().forEach(LettuceConnectionFactory::afterPropertiesSet);}private LettuceConnectionFactory currentLettuceConnectionFactory() {String instance = RedisSelectSupport.getSelectInstance();if (StringUtils.hasLength(instance)) {LettuceConnectionFactory factory = connectionFactoryMap.get(instance);return factory;}return connectionFactoryMap.get(MultiRedisProperties.DEFAULT);}@Overridepublic ReactiveRedisConnection getReactiveConnection() {return currentLettuceConnectionFactory().getReactiveConnection();}@Overridepublic ReactiveRedisClusterConnection getReactiveClusterConnection() {return currentLettuceConnectionFactory().getReactiveClusterConnection();}@Overridepublic RedisConnection getConnection() {return currentLettuceConnectionFactory().getConnection();}@Overridepublic RedisClusterConnection getClusterConnection() {return currentLettuceConnectionFactory().getClusterConnection();}@Overridepublic boolean getConvertPipelineAndTxResults() {return currentLettuceConnectionFactory().getConvertPipelineAndTxResults();}@Overridepublic RedisSentinelConnection getSentinelConnection() {return currentLettuceConnectionFactory().getSentinelConnection();}@Overridepublic DataAccessException translateExceptionIfPossible(RuntimeException ex) {return currentLettuceConnectionFactory().translateExceptionIfPossible(ex);}
}

3.7 自定义配置类RRedisInstanceConfig

com/gm/multi/redis/config/select/instance/RedisInstanceConfig.java

import io.lettuce.core.resource.ClientResources;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.data.redis.LettuceClientConfigurationBuilderCustomizer;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.RedisConfiguration;
import org.springframework.data.redis.connection.RedisSentinelConfiguration;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import java.util.HashMap;
import java.util.Map;@ConditionalOnProperty(prefix = "spring.redis", value = "enable-multi", matchIfMissing = false)
@Configuration(proxyBeanMethods = false)
public class RedisInstanceConfig {@Beanpublic MultiRedisLettuceConnectionFactory multiRedisLettuceConnectionFactory(ObjectProvider<LettuceClientConfigurationBuilderCustomizer> builderCustomizers, ClientResources clientResources, MultiRedisProperties multiRedisProperties, ObjectProvider<RedisSentinelConfiguration> sentinelConfigurationProvider, ObjectProvider<RedisClusterConfiguration> clusterConfigurationProvider) {//读取配置Map<String, LettuceConnectionFactory> connectionFactoryMap = new HashMap<>();Map<String, RedisProperties> multi = multiRedisProperties.getMulti();multi.forEach((k, v) -> {LettuceConnectionFactory lettuceConnectionFactory = lettuceConnectionFactory(v);connectionFactoryMap.put(k, lettuceConnectionFactory);});return new MultiRedisLettuceConnectionFactory(connectionFactoryMap);}public LettuceConnectionFactory lettuceConnectionFactory(RedisProperties redisProperties) {RedisStandaloneConfiguration redisConfiguration = new RedisStandaloneConfiguration(redisProperties.getHost(), redisProperties.getPort());// 设置选用的数据库号码redisConfiguration.setDatabase(redisProperties.getDatabase());// 设置 redis 数据库密码redisConfiguration.setPassword(redisProperties.getPassword());// 根据配置和客户端配置创建连接LettuceConnectionFactory factory = new LettuceConnectionFactory((RedisConfiguration) redisConfiguration);return factory;}
}

四、测试业务搭建

4.1 多种操作切换Redis数据源的方式

支持原生的 RedisTemplate 或者 ReactiveRedisTemplate

4.1.1 注解@RedisSelect方式

com/gm/multi/redis/controller/RedisInstanceSelectController.java

    @RequestMapping("/one")@RedisSelect()         //选择默认库public String selectOne() {redisTemplate.opsForValue().set("one", "one_" + System.currentTimeMillis());String one = redisTemplate.opsForValue().get("one");return one;}@RequestMapping("/two")@RedisSelect(instance = "two")         //选择two库public String selectTwo() {redisTemplate.opsForValue().set("two", "two_" + System.currentTimeMillis());String two = redisTemplate.opsForValue().get("two");return two;}

4.1.2 设置RedisSelectSupport方式

    /*** 同一个方法中切换不同的redis数据源** @return*/@RequestMapping("/three")@RedisSelectpublic String selectThree() {String one = redisTemplate.opsForValue().get("one");log.info(one);RedisSelectSupport.selectInstance("two");//此处切换到two库String two = redisTemplate.opsForValue().get("two");log.info(two);return two;}

4.2 多线程测试

com/gm/multi/redis/controller/RedisInstanceSelectController.java

    @AutowiredRedisMultiInstanceService redisMultiInstanceService;@RequestMapping("/testMultiInstance")public String testMultiIndex() {Thread thread[] = new Thread[500];AtomicBoolean result = new AtomicBoolean(true);for (int i = 0; i < thread.length; i++) {int finalI = i;thread[i] = new Thread(() -> {try {redisMultiInstanceService.testMultiInstance("Thread-" + finalI);} catch (Exception e) {e.printStackTrace();result.set(false);}});thread[i].setName("Thread-" + i);}for (int i = 0; i < thread.length; i++) {thread[i].start();}return "";}

com/gm/multi/redis/service/RedisMultiInstanceService.java

public interface RedisMultiInstanceService {void testMultiInstance(String suffix);
}

com/gm/multi/redis/service/impl/RedisMultiInstanceServiceImpl.java

import com.gm.multi.redis.config.select.RedisSelectSupport;
import com.gm.multi.redis.service.RedisMultiInstanceService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;@Slf4j
@Service
public class RedisMultiInstanceServiceImpl implements RedisMultiInstanceService {@Autowiredprivate StringRedisTemplate redisTemplate;public void testMultiInstance(String suffix) {String defaultkv = "default-" + suffix + "-instance";String twokv = "two-" + suffix + "-instance";//使用默认连接redisTemplate.opsForValue().set(defaultkv, defaultkv);//使用 two 连接RedisSelectSupport.selectInstance("two");redisTemplate.opsForValue().set(twokv, twokv);//使用默认连接RedisSelectSupport.selectInstance("default");String value1 = String.valueOf(redisTemplate.opsForValue().get(defaultkv));//使用 two 连接RedisSelectSupport.selectInstance("two");String value2 = String.valueOf(redisTemplate.opsForValue().get(twokv));log.info("suffix:{}     default={}    two={}", suffix, (defaultkv).equals(value1), (twokv).equals(value2));}
}

4.3 测试效果

浏览器访问:http://127.0.0.1:6009/instance/index/one

在这里插入图片描述

浏览器访问:http://127.0.0.1:6009/instance/index/two
在这里插入图片描述
浏览器访问:http://127.0.0.1:6009/redis/instance/testMultiInstance

  • default数据源

在这里插入图片描述

  • two数据源

在这里插入图片描述

查看控制台输出暂未发现在切换Redis数据源在多线程下,存在线程安全问题。

查看Redisclient信息:

  • default数据源

在这里插入图片描述

  • two数据源

在这里插入图片描述

五、源码

源码地址:https://gitee.com/gm19900510/springboot-cloud-example.git 中模块redis/redis-multi-instance

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

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

3D点云分割系列4:PointSIFT:SIFT算法在点云处理中的应用,从PointNet++的球查询扩展到8向查询

PointSIFT 《PointSIFT: A SIFT-like Network Module for 3D Point Cloud Semantic Segmentation》2018年发布在arXiv上。 1 引言 PointNet和PointNet成功实现point-base的3D点云分割。基于此&#xff0c;更多point-base的方法提出&#xff0c;这些方法普遍是对PointNet和Poi…

文件操作编程

这世间&#xff0c;青山灼灼&#xff0c;星光杳杳&#xff0c;秋风渐渐&#xff0c;晚风慢慢 文件操作编程文件操作编程1&#xff09;熟悉Linux的C编程环境编译优化2&#xff09;文件基本操作编程使用Linux系统调用编写一个完成文件拷贝的C程序。比较拷贝得到的文件与源文件的大…

最长****子序列

&#xff08;在研读大佬写的博客后&#xff0c;打算记录下自己学习过程&#xff09; 通过最长上升子序列的拓展了解到&#xff0c;其实这是一个系列的问题主要代表有&#xff1a; 1 最长上升子序列 2 最长不上升子序列 3 最长下降子序列 4 最长不下降子序列 就以最长上升子…

【创作赢红包】python学习——【第七弹】

前言 上一篇文章 python学习——【第六弹】中介绍了 python中的字典操作&#xff0c;这篇文章接着学习python中的可变序列 集合 集合 1&#xff1a; 集合是python语言提供的内置数据结构&#xff0c;具有无序性&#xff08;集合中的元素无法通过索引下标访问&#xff0c;并且…

leetcode 删除链表的倒数第 n 个结点(3.)

题目 给你一个链表&#xff0c;删除链表的倒数第 n 个结点&#xff0c;并且返回链表的头结点。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5], n 2 输出&#xff1a;[1,2,3,5] 示例 2&#xff1a; 输入&#xff1a;head [1], n 1 输出&#xff1a;[] 示例 3&…

反转字符串II(力扣刷题)

给定一个字符串 s 和一个整数 k&#xff0c;从字符串开头算起&#xff0c;每计数至 2k 个字符&#xff0c;就反转这 2k 字符中的前 k 个字符。 如果剩余字符少于 k 个&#xff0c;则将剩余字符全部反转。 如果剩余字符小于 2k 但大于或等于 k 个&#xff0c;则反转前 k 个字符&…

图片怎么转PDF文件格式?推荐这五个免费无损转换方法!

如何将图片转换为PDF&#xff1f;图片格式文件经常用于每个人的日常生活中&#xff0c;但有时候。我们会将多张图片转换为一份PDF文件进行单个文件传输&#xff0c;但很多人不知道如何将图片转换为PDF格式。 今天&#xff0c;我将与大家分享五种简单免费的无损转换方法&#x…

【CVPR小目标检测】- ISNet红外小目标检测

ISNet&#xff1a;红外小目标探测的形状问题 ——从分割的角度完成小目标红外检测红外图像&#xff1a; 红外小目标使用红外热成像技术&#xff0c;使得红外目标检测能够全天候工作&#xff0c;可视距离远&#xff0c;抗干扰能力强。当像素距离较远时&#xff0c;目标所占比例…

记录如何把postman变成为中文版

首先点击下方这个链接&#xff0c;进入gitee&#xff0c;在里面下载一个插件 Releases hlmd/Postman-cn GitHub 进来是这个样子&#xff1a; 看一下自己的postman是什么版本的&#xff0c;然后在gitee下载对应的APP包 应该怎么查看我的postman版本号呢&#xff1f; ~~看下…

代理模式:JDK动态代理和静态代理回顾

背景&#xff1a;Spring主要有两大思想&#xff1a;IoC、AOP。对于IoC依赖注入不多说了&#xff0c;对于Spring的核心AOP来说&#xff0c;我们需要了解其底层的实现原理&#xff1a;java的动态代理机制。 本篇随笔就是对java的动态机制进行一个回顾。 代理模式的理解 类型&…

三甲医院手术麻醉系统源码, C# .net 桌面软件 C/S版源码

手术麻醉管理系统源码&#xff0c;手麻系统源码&#xff0c;大型医院手术麻醉系统源码 相关技术&#xff1a;C#语言 前端框架&#xff1a;Winform 后端框架&#xff1a;WCF 数 据库&#xff1a;sqlserver VS2019 文末获取联系 手术麻醉系统是面向医生实际工作情况开发的专…

Excel宏(VBA)密码破解

最近在研究一个Excel宏&#xff0c;想查看VBA代码但是有密码&#xff0c;于是想着能不能移除密码。网上查找一番资料后进行了尝试。 一&#xff0c;准备工具 ExcelHex Editor Neo 二&#xff0c;开始实践 首先将.xlsm后缀名的文件改为.zip文件 然后双击zip文件(不用解压文件…

字节跳动CVPR 2023论文精选来啦(内含一批图像生成新研究)

计算机视觉领域三大顶会之一的 CVPR 今年已经开奖啦。 今年的 CVPR 将于六月在加拿大温哥华举办&#xff0c;和往年一样&#xff0c;字节跳动技术团队的同学们收获了不少中选论文&#xff0c;覆盖文本生成图像、语义分割、目标检测、自监督学习等多个领域&#xff0c;其中不少…

AUTOSAR SecOC的CAN FD应用

20多年来&#xff0c;CAN一直是并且仍然是车辆中的主导通信系统。 随着车载功能日益复杂&#xff0c;传统CAN已无法满足对有效数据速率日益增长的需求。 因此&#xff0c;引入了CAN FD—它允许高达64字节的有效载荷以实现2 Mbit/s 和5 Mbit/s的数据速率。为了将这一主要优势用于…

【CocosCreator入门】CocosCreator组件 | Mask(遮罩)组件

Cocos Creator 是一款流行的游戏开发引擎&#xff0c;具有丰富的组件和工具&#xff0c;其中Mask组件可用于创建如圆形、矩形和任意形状的遮罩效果&#xff0c;以限制节点显示的范围。这对于创建具有复杂布局的UI元素非常有用&#xff0c;例如只显示图片的一部分或控制文本显示…

C++相关面试题总结一——内存、关键字、STL、指针、排序、Lambda

面试题总结 基础 C是在C语言的基础上开发的一种面向对象编程语言&#xff0c;应用广泛。C支持多种编程范式&#xff1a;面向对象编程、泛型编程和过程化编程。其编程领域众广&#xff0c;常用于系统开发&#xff0c;引擎开发等应用领域&#xff0c;是最受广大程序员受用的最强…

JavaSE——方法的使用

目录 一、方法的概念及使用 1、什么是方法(method) 2、方法定义 3、方法调用的执行过程 4、实参和形参的关系 二、方法重载 1、为什么需要方法重载 2、方法重载概念 3、方法签名 三、递归 1、递归的概念 2、递归执行过程分析 3、递归练习 一、方法的概念及使用 1、…

【进阶C语言】内存函数(详解)

前言 上一期讲的函数都是和字符串相关的&#xff0c;但是我们在操作数据的时候&#xff0c;不仅仅是操作字符串的数据&#xff0c;还得需要内存函数的应用 内存函数的应用1. memcpy1.1 memcpy的介绍1.2 memcpy的使用1.3 模拟实现memcpy库函数1.4 我想在1&#xff0c;2后面打印1…

web学习---Vue---笔记(1)

该笔记是记录尚硅谷的Vue学习视频的笔记&#xff0c;视频地址为&#xff1a;学习视频地址 初始Vue Vue组件化的特点 组件化声明式编码虚拟DOMDiff算法&#xff0c;尽量复用DOM节点 H5的组件&#xff0c;是把某一个模块封装&#xff0c;里面写HTML\CSS\JS等&#xff0c;算是一…

关于软件发布等一系列注意事项

我们以VS for Qt 开发为案例 1、软件图标的使用&#xff1a; this->setWindowIcon(QIcon("写入路径"));注意这里的路径&#xff0c;一般需要你先添加图片到资源文件中 那么如何将图片添加到资源文件中呢&#xff1f; 1、打开qrc文件 2、添加前缀&#xff0c;添…