基于配置文件 实现 redis 动态数据源和动态数据库的切换

MultiRedisConnectionFactory

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

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.ObjectUtils;

import java.util.Map;

/**
* @author xiaowu
*/
public class MultiRedisConnectionFactory
implements InitializingBean, DisposableBean, RedisConnectionFactory, ReactiveRedisConnectionFactory {
private final Map<String, LettuceConnectionFactory> connectionFactoryMap;

/**
* 当前redis的名字
*/
private static final ThreadLocal<String> CURRENT_REDIS_NAME = new ThreadLocal<>();

/**
* 当前redis的db数据库
*/
private static final ThreadLocal<Integer> CURRENT_REDIS_DB = new ThreadLocal<>();


public MultiRedisConnectionFactory(Map<String, LettuceConnectionFactory> connectionFactoryMap) {
this.connectionFactoryMap = connectionFactoryMap;
}

public void setCurrentRedis(String currentRedisName) {
if (!connectionFactoryMap.containsKey(currentRedisName)) {
throw new RuntimeException("invalid currentRedis: " + currentRedisName + ", it does not exists in configuration");
}
MultiRedisConnectionFactory.CURRENT_REDIS_NAME.set(currentRedisName);
}

/**
* 选择连接和数据库
*
* @param currentRedisName
* @param db
*/
public void setCurrentRedis(String currentRedisName, Integer db) {
if (!connectionFactoryMap.containsKey(currentRedisName)) {
throw new RuntimeException("invalid currentRedis: " + currentRedisName + ", it does not exists in configuration");
}
MultiRedisConnectionFactory.CURRENT_REDIS_NAME.set(currentRedisName);
MultiRedisConnectionFactory.CURRENT_REDIS_DB.set(db);
}


@Override
public void destroy() throws Exception {
connectionFactoryMap.values().forEach(LettuceConnectionFactory::destroy);
MultiRedisConnectionFactory.CURRENT_REDIS_NAME.remove();
MultiRedisConnectionFactory.CURRENT_REDIS_DB.remove();
}

@Override
public void afterPropertiesSet() throws Exception {
connectionFactoryMap.values().forEach(LettuceConnectionFactory::afterPropertiesSet);
}

private LettuceConnectionFactory currentLettuceConnectionFactory() {
String currentRedisName = MultiRedisConnectionFactory.CURRENT_REDIS_NAME.get();
if (!ObjectUtils.isEmpty(currentRedisName)) {
return connectionFactoryMap.get(currentRedisName);
}
return connectionFactoryMap.get(MultiRedisProperties.DEFAULT);
}

@Override
public ReactiveRedisConnection getReactiveConnection() {
return currentLettuceConnectionFactory().getReactiveConnection();
}

@Override
public ReactiveRedisClusterConnection getReactiveClusterConnection() {
return currentLettuceConnectionFactory().getReactiveClusterConnection();
}

@Override
public RedisConnection getConnection() {
// 切换数据库
Integer currentRedisDb = MultiRedisConnectionFactory.CURRENT_REDIS_DB.get();
if (!ObjectUtils.isEmpty(currentRedisDb)) {
LettuceConnectionFactory lettuceConnectionFactory = currentLettuceConnectionFactory();
lettuceConnectionFactory.setShareNativeConnection(false);
RedisConnection connection = lettuceConnectionFactory.getConnection();
connection.select(currentRedisDb);
return connection;
}
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);
}
}

MultiRedisProperties

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

import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

import java.util.Map;

/**
* @author xiaowu
*/
@Configuration
@ConfigurationProperties(prefix = "spring.redis")
public class MultiRedisProperties {
/**
* 默认连接必须配置,配置 key 为 release
*/
public static final String DEFAULT = "release";

private boolean enableMulti = false;


private Map<String, RedisProperties> multi;

public boolean isEnableMulti() {
return enableMulti;
}

public void setEnableMulti(boolean enableMulti) {
this.enableMulti = enableMulti;
}

public Map<String, RedisProperties> getMulti() {
return multi;
}

public void setMulti(Map<String, RedisProperties> multi) {
this.multi = multi;
}

public MultiRedisProperties() {
}
}

RedisCustomizedConfiguration

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

import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
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.RedisPassword;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;

import java.util.HashMap;
import java.util.Map;

/**
* @author xiaowu
*/
@ConditionalOnProperty(prefix = "spring.redis", value = "enable-multi", matchIfMissing = false)
@Configuration(proxyBeanMethods = false)
public class RedisCustomizedConfiguration {

/**
* @param multiRedisProperties
* @return
*/
@Bean
public MultiRedisConnectionFactory multiRedisConnectionFactory(MultiRedisProperties multiRedisProperties) {
Map<String, LettuceConnectionFactory> connectionFactoryMap = new HashMap<>();
Map<String, RedisProperties> multi = multiRedisProperties.getMulti();
multi.forEach((k, v) -> {
// 此处仅处理RedisStandaloneConfiguration
RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
redisStandaloneConfiguration.setDatabase(v.getDatabase());
redisStandaloneConfiguration.setHostName(v.getHost());
redisStandaloneConfiguration.setPort(v.getPort());
redisStandaloneConfiguration.setUsername(v.getUsername());
redisStandaloneConfiguration.setPassword(RedisPassword.of(v.getPassword()));
// @see org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory
LettuceConnectionFactory lettuceConnectionFactory = new LettuceConnectionFactory(redisStandaloneConfiguration);
connectionFactoryMap.put(k, lettuceConnectionFactory);
});
return new MultiRedisConnectionFactory(connectionFactoryMap);
}
}

RedisConfig

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
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.util.Objects;

/**
* @author xiaowu
*/
@Configuration
@EnableCaching
public class RedisConfig {

@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
GenericJackson2JsonRedisSerializerCustomized jackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializerCustomized();
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
template.setKeySerializer(stringRedisSerializer);
template.setValueSerializer(jackson2JsonRedisSerializer);
template.setHashKeySerializer(stringRedisSerializer);
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.setConnectionFactory(redisConnectionFactory);
template.afterPropertiesSet();
return template;
}

static class GenericJackson2JsonRedisSerializerCustomized extends GenericJackson2JsonRedisSerializer {
@Override
public byte[] serialize(Object source) throws SerializationException {
// 解决存储时带双引号的问题
if (Objects.nonNull(source)) {
if (source instanceof String || source instanceof Character) {
return source.toString().getBytes();
}
}
return super.serialize(source);
}

@Override
public <T> T deserialize(byte[] source, Class<T> type) throws SerializationException {
// 此处将查询得到的数据全部视为字符串处理
return (T) RedisSerializer.string().deserialize(source);
}
}

}

application.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
spring:
redis:
enable-multi: true
multi:
release:
host: 192.168.56.100
port: 6379
password: root
database: 13
production:
host: 192.168.56.100
port: 6378
password: root
database: 13

测试代码

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
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisTemplate;

@SpringBootTest
class MultiRedisSourceApplicationTests {
@Autowired
RedisTemplate<String, Object> redisTemplate;

@Autowired
MultiRedisConnectionFactory multiRedisConnectionFactory;

@Test
void contextLoads() {
// 走默认的数据源
redisTemplate.opsForValue().set("k1","v1");
// 走release数据源0 库
multiRedisConnectionFactory.setCurrentRedis("release",0);
redisTemplate.opsForValue().set("k1","v2");
// 走production数据源9 库
multiRedisConnectionFactory.setCurrentRedis("production",9);
redisTemplate.opsForValue().set("k1","v2");
}
}