- ubuntu12.04环境下使用kvm ioctl接口实现最简单的虚拟机
- Ubuntu 通过无线网络安装Ubuntu Server启动系统后连接无线网络的方法
- 在Ubuntu上搭建网桥的方法
- ubuntu 虚拟机上网方式及相关配置详解
CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.
这篇CFSDN的博客文章spring-data-redis 动态切换数据源的方法由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.
最近遇到了一个麻烦的需求,我们需要一个微服务应用同时访问两个不同的 Redis 集群。一般我们不会这么使用 Redis,但是这两个 Redis 本来是不同业务集群,现在需要一个微服务同时访问.
其实我们在实际业务开发的时候,可能还会遇到类似的场景。例如 Redis 读写分离,这个也是 spring-data-redis 没有提供的功能,底层连接池例如 Lettuce 或者 Jedis 都提供了获取只读连接的 API,但是缺陷有两个:
因此,我们需要在 spring-data-redis 的基础上实现一个动态切换 Redis 连接的机制.
spring-data-redis 的配置类为:org.springframework.boot.autoconfigure.data.redis.RedisProperties,可以配置单个 Redis 实例或者 Redis 集群的连接配置。根据这些配置,会生成统一的 Redis 连接工厂 RedisConnectionFactory 。
spring-data-redis 核心接口与背后的连接相关抽象关系为:
通过这个图,我们可以知道,我们实现一个可以动态返回不同 Redis 连接的 RedisConnectionFactory 即可,并且根据 spring-data-redis 的自动装载源码可以知道,框架内的所有 RedisConnectionFactory 是 @ConditionalOnMissingBean 的,即我们可以使用我们自己实现的 RedisConnectionFactory 进行替换.
项目地址:https://github.com/JoJoTec/spring-boot-starter-redis-related 。
我们可以给 RedisProperties 配置外层封装一个多 Redis 连接的配置,即MultiRedisProperties:
1
2
3
4
5
6
7
8
9
10
11
12
|
@Data
@NoArgsConstructor
@ConfigurationProperties
(prefix =
"spring.redis"
)
public
class
MultiRedisProperties {
/**
* 默认连接必须配置,配置 key 为 default
*/
public
static
final
String DEFAULT =
"default"
;
private
boolean
enableMulti =
false
;
private
Map<String, RedisProperties> multi;
}
|
这个配置是在原有配置基础上的,也就是用户可以使用原有配置,也可以使用这种多 Redis 配置,就是需要配置 spring.redis.enable-multi=true。multi 这个 Map 中放入的 key 是数据源名称,用户可以在使用 RedisTemplate 或者 ReactiveRedisTemplate 之前,通过这个数据源名称指定用哪个 Redis.
接下来我们来实现 MultiRedisLettuceConnectionFactory,即可以动态切换 Redis 连接的 RedisConnectionFactory,我们的项目采用的 Redis 客户端是 Lettuce:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
|
public
class
MultiRedisLettuceConnectionFactory
implements
InitializingBean, DisposableBean, RedisConnectionFactory, ReactiveRedisConnectionFactory {
private
final
Map<String, LettuceConnectionFactory> connectionFactoryMap;
private
static
final
ThreadLocal<String> currentRedis =
new
ThreadLocal<>();
public
MultiRedisLettuceConnectionFactory(Map<String, LettuceConnectionFactory> connectionFactoryMap) {
this
.connectionFactoryMap = connectionFactoryMap;
}
public
void
setCurrentRedis(String currentRedis) {
if
(!connectionFactoryMap.containsKey(currentRedis)) {
throw
new
RedisRelatedException(
"invalid currentRedis: "
+ currentRedis +
", it does not exists in configuration"
);
}
MultiRedisLettuceConnectionFactory.currentRedis.set(currentRedis);
}
@Override
public
void
destroy()
throws
Exception {
connectionFactoryMap.values().forEach(LettuceConnectionFactory::destroy);
}
@Override
public
void
afterPropertiesSet()
throws
Exception {
connectionFactoryMap.values().forEach(LettuceConnectionFactory::afterPropertiesSet);
}
private
LettuceConnectionFactory currentLettuceConnectionFactory() {
String currentRedis = MultiRedisLettuceConnectionFactory.currentRedis.get();
if
(StringUtils.isNotBlank(currentRedis)) {
MultiRedisLettuceConnectionFactory.currentRedis.remove();
return
connectionFactoryMap.get(currentRedis);
}
return
connectionFactoryMap.get(MultiRedisProperties.DEFAULT);
}
@Override
public
ReactiveRedisConnection getReactiveConnection() {
return
currentLettuceConnectionFactory().getReactiveConnection();
}
@Override
public
ReactiveRedisClusterConnection getReactiveClusterConnection() {
return
currentLettuceConnectionFactory().getReactiveClusterConnection();
}
@Override
public
RedisConnection getConnection() {
return
currentLettuceConnectionFactory().getConnection();
}
@Override
public
RedisClusterConnection getClusterConnection() {
return
currentLettuceConnectionFactory().getClusterConnection();
}
@Override
public
boolean
getConvertPipelineAndTxResults() {
return
currentLettuceConnectionFactory().getConvertPipelineAndTxResults();
}
@Override
public
RedisSentinelConnection getSentinelConnection() {
return
currentLettuceConnectionFactory().getSentinelConnection();
}
@Override
public
DataAccessException translateExceptionIfPossible(RuntimeException ex) {
return
currentLettuceConnectionFactory().translateExceptionIfPossible(ex);
}
}
|
逻辑非常简单,就是提供了设置 Redis 数据源的接口,并且放入了 ThreadLocal 中,并且仅对当前一次有效,读取后就清空.
然后,将 MultiRedisLettuceConnectionFactory 作为 Bean 注册到我们的 ApplicationContext 中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
@ConditionalOnProperty
(prefix =
"spring.redis"
, value =
"enable-multi"
, matchIfMissing =
false
)
@Configuration
(proxyBeanMethods =
false
)
public
class
RedisCustomizedConfiguration {
/**
* @param builderCustomizers
* @param clientResources
* @param multiRedisProperties
* @return
* @see org.springframework.boot.autoconfigure.data.redis.LettuceConnectionConfiguration
*/
@Bean
public
MultiRedisLettuceConnectionFactory multiRedisLettuceConnectionFactory(
ObjectProvider<LettuceClientConfigurationBuilderCustomizer> builderCustomizers,
ClientResources clientResources,
MultiRedisProperties multiRedisProperties,
ObjectProvider<RedisSentinelConfiguration> sentinelConfigurationProvider,
ObjectProvider<RedisClusterConfiguration> clusterConfigurationProvider
) {
//读取配置
Map<String, LettuceConnectionFactory> connectionFactoryMap = Maps.newHashMap();
Map<String, RedisProperties> multi = multiRedisProperties.getMulti();
multi.forEach((k, v) -> {
//这个其实就是框架中原有的源码使用 RedisProperties 的方式,我们其实就是在 RedisProperties 外面包装了一层而已
LettuceConnectionConfiguration lettuceConnectionConfiguration =
new
LettuceConnectionConfiguration(
v,
sentinelConfigurationProvider,
clusterConfigurationProvider
);
LettuceConnectionFactory lettuceConnectionFactory = lettuceConnectionConfiguration.redisConnectionFactory(builderCustomizers, clientResources);
connectionFactoryMap.put(k, lettuceConnectionFactory);
});
return
new
MultiRedisLettuceConnectionFactory(connectionFactoryMap);
}
}
|
我们来测试下,使用 embedded-redis 来启动本地 redis,从而实现单元测试。我们启动两个 Redis,在两个 Redis 中放入不同的 Key,验证是否存在,并且测试同步接口,多线程调用同步接口,和多次异步接口无等待订阅从而测试有效性。:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
|
import
com.github.jojotech.spring.boot.starter.redis.related.lettuce.MultiRedisLettuceConnectionFactory;
import
org.junit.jupiter.api.AfterAll;
import
org.junit.jupiter.api.Assertions;
import
org.junit.jupiter.api.BeforeAll;
import
org.junit.jupiter.api.Test;
import
org.junit.jupiter.api.extension.ExtendWith;
import
org.redisson.api.RedissonClient;
import
org.springframework.beans.factory.annotation.Autowired;
import
org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import
org.springframework.boot.test.context.SpringBootTest;
import
org.springframework.context.annotation.Bean;
import
org.springframework.context.annotation.Configuration;
import
org.springframework.data.redis.core.ReactiveStringRedisTemplate;
import
org.springframework.data.redis.core.StringRedisTemplate;
import
org.springframework.test.context.junit.jupiter.SpringExtension;
import
reactor.core.publisher.Mono;
import
redis.embedded.RedisServer;
import
java.util.concurrent.TimeUnit;
import
java.util.concurrent.atomic.AtomicBoolean;
@ExtendWith
(SpringExtension.
class
)
@SpringBootTest
(properties = {
"spring.redis.enable-multi=true"
,
"spring.redis.multi.default.host=127.0.0.1"
,
"spring.redis.multi.default.port=6379"
,
"spring.redis.multi.test.host=127.0.0.1"
,
"spring.redis.multi.test.port=6380"
,
})
public
class
MultiRedisTest {
//启动两个 redis
private
static
RedisServer redisServer;
private
static
RedisServer redisServer2;
@BeforeAll
public
static
void
setUp()
throws
Exception {
System.out.println(
"start redis"
);
redisServer = RedisServer.builder().port(
6379
).setting(
"maxheap 200m"
).build();
redisServer2 = RedisServer.builder().port(
6380
).setting(
"maxheap 200m"
).build();
redisServer.start();
redisServer2.start();
System.out.println(
"redis started"
);
}
@AfterAll
public
static
void
tearDown()
throws
Exception {
System.out.println(
"stop redis"
);
redisServer.stop();
redisServer2.stop();
System.out.println(
"redis stopped"
);
}
@EnableAutoConfiguration
@Configuration
public
static
class
App {
}
@Autowired
private
StringRedisTemplate redisTemplate;
@Autowired
private
ReactiveStringRedisTemplate reactiveRedisTemplate;
@Autowired
private
MultiRedisLettuceConnectionFactory multiRedisLettuceConnectionFactory;
private
void
testMulti(String suffix) {
//使用默认连接,设置 "testDefault" + suffix, "testDefault" 键值对
redisTemplate.opsForValue().set(
"testDefault"
+ suffix,
"testDefault"
);
//使用 test 连接,设置 "testSecond" + suffix, "testDefault" 键值对
multiRedisLettuceConnectionFactory.setCurrentRedis(
"test"
);
redisTemplate.opsForValue().set(
"testSecond"
+ suffix,
"testSecond"
);
//使用默认连接,验证 "testDefault" + suffix 存在,"testSecond" + suffix 不存在
Assertions.assertTrue(redisTemplate.hasKey(
"testDefault"
+ suffix));
Assertions.assertFalse(redisTemplate.hasKey(
"testSecond"
+ suffix));
//使用 test 连接,验证 "testDefault" + suffix 不存在,"testSecond" + suffix 存在
multiRedisLettuceConnectionFactory.setCurrentRedis(
"test"
);
Assertions.assertFalse(redisTemplate.hasKey(
"testDefault"
+ suffix));
multiRedisLettuceConnectionFactory.setCurrentRedis(
"test"
);
Assertions.assertTrue(redisTemplate.hasKey(
"testSecond"
+ suffix));
}
//单次验证
@Test
public
void
testMultiBlock() {
testMulti(
""
);
}
//多线程验证
@Test
public
void
testMultiBlockMultiThread()
throws
InterruptedException {
Thread thread[] =
new
Thread[
50
];
AtomicBoolean result =
new
AtomicBoolean(
true
);
for
(
int
i =
0
; i < thread.length; i++) {
int
finalI = i;
thread[i] =
new
Thread(() -> {
try
{
testMulti(
""
+ finalI);
}
catch
(Exception e) {
e.printStackTrace();
result.set(
false
);
}
});
}
for
(
int
i =
0
; i < thread.length; i++) {
thread[i].start();
}
for
(
int
i =
0
; i < thread.length; i++) {
thread[i].join();
}
Assertions.assertTrue(result.get());
}
//reactive 接口验证
private
Mono<Boolean> reactiveMulti(String suffix) {
return
reactiveRedisTemplate.opsForValue().set(
"testReactiveDefault"
+ suffix,
"testReactiveDefault"
)
.flatMap(b -> {
multiRedisLettuceConnectionFactory.setCurrentRedis(
"test"
);
return
reactiveRedisTemplate.opsForValue().set(
"testReactiveSecond"
+ suffix,
"testReactiveSecond"
);
}).flatMap(b -> {
return
reactiveRedisTemplate.hasKey(
"testReactiveDefault"
+ suffix);
}).map(b -> {
Assertions.assertTrue(b);
System.out.println(Thread.currentThread().getName());
return
b;
}).flatMap(b -> {
return
reactiveRedisTemplate.hasKey(
"testReactiveSecond"
+ suffix);
}).map(b -> {
Assertions.assertFalse(b);
System.out.println(Thread.currentThread().getName());
return
b;
}).flatMap(b -> {
multiRedisLettuceConnectionFactory.setCurrentRedis(
"test"
);
return
reactiveRedisTemplate.hasKey(
"testReactiveDefault"
+ suffix);
}).map(b -> {
Assertions.assertFalse(b);
System.out.println(Thread.currentThread().getName());
return
b;
}).flatMap(b -> {
multiRedisLettuceConnectionFactory.setCurrentRedis(
"test"
);
return
reactiveRedisTemplate.hasKey(
"testReactiveSecond"
+ suffix);
}).map(b -> {
Assertions.assertTrue(b);
return
b;
});
}
//多次调用 reactive 验证,并且 subscribe,这本身就是多线程的
@Test
public
void
testMultiReactive()
throws
InterruptedException {
for
(
int
i =
0
; i <
10000
; i++) {
reactiveMulti(
""
+ i).subscribe(System.out::println);
}
TimeUnit.SECONDS.sleep(
10
);
}
}
|
运行测试,通过.
到此这篇关于spring-data-redis 动态切换数据源的文章就介绍到这了,更多相关spring-data-redis 动态切换数据源内容请搜索我以前的文章或继续浏览下面的相关文章希望大家以后多多支持我! 。
原文链接:https://www.cnblogs.com/zhxdick/p/15208681.html 。
最后此篇关于spring-data-redis 动态切换数据源的方法的文章就讲到这里了,如果你想了解更多关于spring-data-redis 动态切换数据源的方法的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我正在尝试创建一个简单的小部件,它只有一个切换按钮,但我的 AVD 模拟器上不断出现错误,提示“加载小部件有问题”。 似乎是因为我在小部件布局中添加了开关或切换按钮。 为了测试它,我创建了一个新的空
我正在使用 GLFW 进行键盘输入,但处理速度太快,因此我的 bool 开关在一次按下时被更改了 10 次,因为输入是每一帧处理的。我只需要按一次空格键即可切换状态。我当前的代码如下: if (glf
我希望完成一个相当简单的任务(我希望!) 我有两个 div 标签和一个 anchor 标签,像这样: forgot password? 我希望使用 anchor 标记在两个 div 标记之间切换,
我已经尝试了几种不同的方法,但似乎无法弄清楚如何将 span 的类从“die2”切换到“die3”以及将 div 的显示样式从“block”切换到“none”。有人有任何解决方案吗? (基本上当页面加
我正在尝试制作一个交换小部件,该小部件显示两个不同的文本。激活时,它下面显示一个TextField,顶部是不可见的,而禁用时它上面显示一个Text,而底部是不可见。但是它没有在屏幕上显示任何内容,只是
我有一个简单的 Angular 应用程序,它使用两个模板和 Controller 。放置两个按钮来切换 View 。它们调用在控件内定义的函数,该函数使用 window.location='' 来切换
我想要一个 div 切换它的类(切换)onclick,然后再次恢复到原来的类 onclick 我的代码是: function myfunc() { //the code over here
我确信这是一个常见问题,我已经尝试了该网站上的许多线程来尝试解决我的问题,但我似乎无法使其正常工作。基本上我有一个子菜单,当父菜单悬停在其上时需要显示该子菜单,但是如果您在加载完成之前将鼠标从菜单项上
我制作了一个 JavaScript 函数来隐藏单击按钮时的链接及其在该函数中的工作 function toggle() { var ele = document.getElement
我正在使用我在 JS fiddle 上找到的这个脚本:http://jsfiddle.net/Q4PUw/2/ 当我点击切换链接时,它会切换框并显示它,但是,它会跳回页面顶部,然后我必须再次向下滚动才
我正在 GoDaddy 上的共享服务器 IP 上构建 Web 应用程序。该应用程序与验证请求服务器 IP 的房地产 API 进行对话。问题是在 GoDaddy 上,我们的 IP 被列为 X,但它实际上
我在 jquery 中有一个简单的脚本,可以在 时切换 div(显示和隐藏)。被点击(我正在使用 Bootstrap )。 HTML: Advanced search This is t
我有两个 NSWindows,其中都有一个 NSPanel。我想在按下按钮时切换窗口。如何才能做到这一点?我不再需要旧窗口,所以我只想显示新窗口。 最佳答案 要聚焦第二个窗口,只需调用: [windo
我尝试在单击切换时将选项添加到选择菜单,但如果再次单击(取消选择),则可以将其删除。到目前为止,我可以在单击时向选择菜单添加单个值,但无法将其删除(切换添加切换删除) 这是我的代码: HTML
我正在尝试隐藏所属行。例如,如果您单击“子标题 1”,则将仅隐藏项目 1、项目 2 和项目 3 行。 示例: title Sub Title 1
似乎无法让它为我工作,任何人都可以为我提供帮助吗? http://codepen.io/anon/pen/kABjC 这应该根据点击打开和关闭文本部分,它采用 ID #,它只是一个数字(1,2,3,4
我正在从一个文件复制到另一个文件,并且我可以看到 Excel 在源文件和目标文件之间切换(如闪烁)。我希望宏从源复制并粘贴到目标,而不在文件之间切换(我不想闪烁)。 这里我得到了我的 Excel VB
我正在尝试制作一个带切换功能的 Accordion ,现在看起来效果很好。作为 javascript 的新手,我希望得到一些帮助,那就是它的组合方式。 http://jsfiddle.net/z3wW
我正在尝试制作一个小脚本,其中屏幕将每 100 毫秒随机更改一次背景颜色,您可以通过按一个按钮来打开和关闭它。我可以让它开始,但我不能让它停止。 这是切换的主要代码: var on = -1; fun
我确信这里应该已经涵盖了这一点,但我一直无法找到专门涉及此问题的问题。 我在一个页面中有 2 个 div,就像这样...... ...content... ...content...
我是一名优秀的程序员,十分优秀!