Spring Boot实现Redis同数据源动态切换DB | Spring Cloud 31
一、前言
在某些业务场景下,需要多访问同一Redis
数据源下的不同DB
。
在
Redis
中默认提供了16
个数据库(序号0-15
),默认Redis
使用的是db 0
。
此章节基于spring-boot-starter-data-redis
模块,实现了Redis
同数据源动态切换DB
,具体功能如下:
- 突破一个项目只能连接
Redis
一个DB
的限制 - 提供多种操作切换
Redis DB
的方式(@Autowired
方式 和RedisSelectSupport
方式) - 提供完善的代码使用示例
二、项目结构
-
RedisSelect
:自定义注解,用于切换同一redis
数据源下的不同DB Index
-
RedisSelectSupport
:自定义切换DB
操作,用于线程间传递DB Index
值 -
RedisIndexSelectAspect
:自定义AOP
切面,对RedisSelect
注解方法进行拦截,调用RedisSelectSupport
,利用RedisSelectSupport
设置DB Index
值 -
RedisSelectTemplate
:对原RedisTemplate
进行封装,利用RedisSelectSupport
获取DB Index
值,在对Redis
操作前实现DB
的实际切换 -
RedisIndexSelectConfig
:实现RedisSelectTemplate
定义,修改Lettuce
连接池属性支持动态切换DB
下文对各类的功能描述不进行重复介绍,请详细了解本章节。
本示例源码位于项目的
redis/redis-multi-index
模块下。
三、动态切换Redis DB实现
3.1 所需依赖
redis/redis-multi-index/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-index</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: 6008
spring:
application:
name: @artifactId@
redis:
database: 1
host:
password:
port: 6379
timeout: 60000
lettuce:
pool:
max-active: -1
max-idle: -1
max-wait: -1
min-idle: -1
logging:
level:
org:
springframework:
boot:
autoconfigure:
logging: info
3.3 自定义注解类@RedisSelect
com/gm/multi/redis/config/select/annotation/RedisSelect.java
:
import java.lang.annotation.*;
/**
* 注解,用于切换同一redis数据源下的不同db index
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RedisSelect {
/**
* redis库 0 - 15 库
*
* @return
*/
int index() default 0;
}
3.4 自定义切换支持类RedisSelectSupport
com/gm/multi/redis/config/select/RedisSelectSupport.java
:
/**
* Redis 切换DB操作
*/
public class RedisSelectSupport {
/**
* 定义一个静态变量,用于线程间传递值
*/
private static final ThreadLocal<Integer> DB_SELECT_CONTEXT = new ThreadLocal<>();
public static void selectIndex(int db) {
DB_SELECT_CONTEXT.set(db);
}
public static Integer getSelectIndex() {
return DB_SELECT_CONTEXT.get();
}
}
3.5 自定义切面类RedisIndexSelectAspect
com/gm/multi/redis/config/select/aspect/RedisIndexSelectAspect.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.RedisSelectTemplate;
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.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
@Slf4j
@Aspect
@Component
public class RedisIndexSelectAspect {
@Value("${spring.redis.database:0}")
private int defaultIndex;
/**
* 创建RedisSelect对应的切面,来对标有注解的方法拦截
*
* @param point
* @return
* @throws Throwable
*/
@Around("@annotation(com.gm.multi.redis.config.select.annotation.RedisSelect)")
@ConditionalOnBean(RedisSelectTemplate.class)
public Object configRedis(ProceedingJoinPoint point) throws Throwable {
int index = defaultIndex;
try {
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
RedisSelect config = method.getAnnotation(RedisSelect.class);
if (config != null) {
index = config.index();
}
RedisSelectSupport.selectIndex(index);
return point.proceed();
} finally {
RedisSelectSupport.selectIndex(defaultIndex);
log.debug("redis index reset {} to {}", index, defaultIndex);
}
}
}
3.6 封装支持切换DB的RedisSelectTemplate
com/gm/multi/redis/config/select/RedisSelectTemplate.java
:.
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisTemplate;
public class RedisSelectTemplate<K, V> extends RedisTemplate<K, V> {
@Override
protected RedisConnection createRedisConnectionProxy(RedisConnection pm) {
return super.createRedisConnectionProxy(pm);
}
/**
* 在连接Redis之前做一些配置
*
* @param connection
* @param existingConnection
* @return
*/
@Override
protected RedisConnection preProcessConnection(RedisConnection connection, boolean existingConnection) {
Integer index = RedisSelectSupport.getSelectIndex();
if (index != null) {
//切换 redis db 到 其他的库
connection.select(index);
}
return super.preProcessConnection(connection, existingConnection);
}
}
3.7 自定义配置类RedisIndexSelectConfig
com/gm/multi/redis/config/select/index/RedisIndexSelectConfig.java
:
import com.gm.multi.redis.config.select.RedisSelectTemplate;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.serializer.GenericToStringSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Slf4j
@Configuration(proxyBeanMethods = false)
public class RedisIndexSelectConfig {
@Autowired(required = false)
private LettuceConnectionFactory factory;
@Bean
@ConditionalOnMissingBean
public RedisSelectTemplate<String, Object> redisIndexSelectTemplate() {
/**
* 使用默认注入的RedisConnectionFactory factory时,切换db时出现以下异常:
*
* java.lang.UnsupportedOperationException:Selecting a new database not supported due to shared connection.
* Use separate ConnectionFactorys to work with multiple databases
* 从默认RedisConnectionFactory factory注入,改为LettuceConnectionFactory factory,
* 并通过factory.setShareNativeConnection(false)关闭共享链接
*/
RedisSelectTemplate<String, Object> redisTemplate = new RedisSelectTemplate<>();
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(new GenericToStringSerializer(Object.class));
redisTemplate.setValueSerializer(new StringRedisSerializer());
// 关闭共享链接
factory.setShareNativeConnection(false);
redisTemplate.setConnectionFactory(factory);
redisTemplate.afterPropertiesSet();
log.info("实例化 SelectableRedisTemplate 对象完成");
return redisTemplate;
}
}
LettuceConnectionFactory
默认创建的LettuceConnection
共享同一个线程安全的本地连接处理非阻塞和非事物的操作。
将shareNativeConnection
设置为false
可以让LettuceConnectionFactory
每次创建一个专用连接。
四、测试业务搭建
4.1 多种操作切换Redis DB的方式
通过以下方式获取支持Redis
同数据源可切换DB
的操作类RedisSelectTemplate
:
@Autowired
private RedisSelectTemplate<String, Object> redisTemplate;
4.1.1 注解@RedisSelect方式
com/gm/multi/redis/controller/RedisSelectIndexontroller.java
:
@RequestMapping("/one")
@RedisSelect(index = 1) //选择db1库
public String selectOne() {
redisTemplate.opsForValue().set("one", "one_" System.currentTimeMillis());
String one = (String) redisTemplate.opsForValue().get("one");
return one;
}
@RequestMapping("/two")
@RedisSelect(index = 2) //选择db2库
public String selectTwo() {
redisTemplate.opsForValue().set("two", "two_" System.currentTimeMillis());
String two = (String) redisTemplate.opsForValue().get("two");
return two;
}
4.1.2 设置RedisSelectSupport方式
com/gm/multi/redis/controller/RedisSelectIndexontroller.java
:
/**
* 同一个方法中切换不同的redis库
*
* @return
*/
@RequestMapping("/three")
@RedisSelect(index = 2) //选择db2库
public String selectThree() {
redisTemplate.opsForValue().set("two", "two_" System.currentTimeMillis());
String two = (String) redisTemplate.opsForValue().get("two");
log.info(two);
RedisSelectSupport.selectIndex(3);//此处切换到db3库
redisTemplate.opsForValue().set("three", "three_" System.currentTimeMillis());
String three = (String) redisTemplate.opsForValue().get("three");
log.info(three);
return three;
}
4.2 多线程测试
com/gm/multi/redis/controller/RedisSelectIndexontroller.java
:
@Autowired
RedisMultiIndexService redisMultiIndexService;
@RequestMapping("/testMultiIndex")
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 {
redisMultiIndexService.testMultiIndex("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/RedisMultiIndexService.java
:
public interface RedisMultiIndexService {
void testMultiIndex(String suffix);
}
com/gm/multi/redis/service/impl/RedisMultiIndexServiceImpl.java
:
import com.gm.multi.redis.config.select.RedisSelectSupport;
import com.gm.multi.redis.config.select.RedisSelectTemplate;
import com.gm.multi.redis.service.RedisMultiIndexService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Slf4j
@Service
public class RedisMultiIndexServiceImpl implements RedisMultiIndexService {
@Autowired
private RedisSelectTemplate<String, Object> redisSelectTemplate;
public void testMultiIndex(String suffix) {
String defaultkv = "default-" suffix "-index";
String twokv = "two-" suffix "-index";
String threekv = "three-" suffix "-index";
String fourkv = "two-" suffix "-index";
//使用认连接
RedisSelectSupport.selectIndex(10);
redisSelectTemplate.opsForValue().set(defaultkv, defaultkv);
//使用 two 连接
RedisSelectSupport.selectIndex(11);
redisSelectTemplate.opsForValue().set(twokv, twokv);
//使用 three 连接
RedisSelectSupport.selectIndex(12);
redisSelectTemplate.opsForValue().set(threekv, threekv);
//使用 four 连接
RedisSelectSupport.selectIndex(13);
redisSelectTemplate.opsForValue().set(fourkv, fourkv);
//使用默认连接
RedisSelectSupport.selectIndex(10);
String value1 = String.valueOf(redisSelectTemplate.opsForValue().get(defaultkv));
//使用 two 连接
RedisSelectSupport.selectIndex(11);
String value2 = String.valueOf(redisSelectTemplate.opsForValue().get(twokv));
//使用 three 连接
RedisSelectSupport.selectIndex(12);
String value3 = String.valueOf(redisSelectTemplate.opsForValue().get(threekv));
//使用 four 连接
RedisSelectSupport.selectIndex(13);
String value4 = String.valueOf(redisSelectTemplate.opsForValue().get(fourkv));
log.info("suffix:{} default={} two={} three={} four={}", suffix, (defaultkv).equals(value1),
(twokv).equals(value2), (threekv).equals(value3), (fourkv).equals(value4));
}
}
4.3 测试效果
浏览器访问:http://127.0.0.1:6008/redis/index/one
浏览器访问:http://127.0.0.1:6008/redis/index/two
浏览器访问:http://127.0.0.1:6008/redis/index/three
浏览器访问:http://127.0.0.1:6008/redis/index/testMultiIndex
查看控制台输出暂未发现在切换
Redis DB
在多线程下,存在线程安全问题。
查看Redis
的client
信息:
五、源码
源码地址:https://gitee.com/gm19900510/springboot-cloud-example.git 中模块redis/redis-multi-index
。
这篇好文章是转载于:学新通技术网
- 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
- 本站站名: 学新通技术网
- 本文地址: /boutique/detail/tanhfhkaaj
-
photoshop保存的图片太大微信发不了怎么办
PHP中文网 06-15 -
《学习通》视频自动暂停处理方法
HelloWorld317 07-05 -
Android 11 保存文件到外部存储,并分享文件
Luke 10-12 -
word里面弄一个表格后上面的标题会跑到下面怎么办
PHP中文网 06-20 -
photoshop扩展功能面板显示灰色怎么办
PHP中文网 06-14 -
微信公众号没有声音提示怎么办
PHP中文网 03-31 -
excel下划线不显示怎么办
PHP中文网 06-23 -
excel打印预览压线压字怎么办
PHP中文网 06-22 -
TikTok加速器哪个好免费的TK加速器推荐
TK小达人 10-01 -
怎样阻止微信小程序自动打开
PHP中文网 06-13