Redis动态切换db数据源(db)最佳实践
refer to : https://blog.csdn.net/ron03129596/article/details/108847907
方法一:确定的2个数据源
配置文件的配置信息
redis:
user:
enable: true
host: 218.94.128.34
port: 16379
database: 7
password: long123456@HhRedis
# 15000毫秒
timeout: 15000
lettuce:
pool:
min-idle: 0
max-idle: 8
max-active: 8
max-wait: -1
iot:
enable: true
host: 218.94.128.34
port: 16379
database: 3
password: long123456@HhRedis
# 15000毫秒
timeout: 15000
lettuce:
pool:
min-idle: 0
max-idle: 8
max-active: 8
max-wait: -1
分别对应2个实体属性类:RedisIotDbProperties 和 RedisUserDbProperties
RedisIotDbProperties如下:
@ConfigurationProperties(value = "redis.iot")
public class RedisIotDbProperties {
private int database = 0;
/**
* Connection URL. Overrides host, port, and password. User is ignored. Example:
* redis://user:password@example.com:6379
*/
private String url;
/**
* Redis server host.
*/
private String host = "localhost";
/**
* Login username of the redis server.
*/
private String username;
/**
* Login password of the redis server.
*/
private String password;
/**
* Redis server port.
*/
private int port = 6379;
/**
* Whether to enable SSL support.
*/
private boolean ssl;
/**
* Read timeout.
*/
private Duration timeout;
/**
* Connection timeout.
*/
private Duration connectTimeout;
/**
* Client name to be set on connections with CLIENT SETNAME.
*/
private String clientName;
/**
* Type of client to use. By default, auto-detected according to the classpath.
*/
private RedisProperties.ClientType clientType;
private RedisProperties.Sentinel sentinel;
private RedisProperties.Cluster cluster;
private final RedisProperties.Jedis jedis = new RedisProperties.Jedis();
private final RedisProperties.Lettuce lettuce = new RedisProperties.Lettuce();
// get/set...
RedisUserDbProperties如下:
@ConfigurationProperties(value = "redis.user")
public class RedisUserDbProperties {
private int database = 0;
/**
* Connection URL. Overrides host, port, and password. User is ignored. Example:
* redis://user:password@example.com:6379
*/
private String url;
/**
* Redis server host.
*/
private String host = "localhost";
/**
* Login username of the redis server.
*/
private String username;
/**
* Login password of the redis server.
*/
private String password;
/**
* Redis server port.
*/
private int port = 6379;
/**
* Whether to enable SSL support.
*/
private boolean ssl;
/**
* Read timeout.
*/
private Duration timeout;
/**
* Connection timeout.
*/
private Duration connectTimeout;
/**
* Client name to be set on connections with CLIENT SETNAME.
*/
private String clientName;
/**
* Type of client to use. By default, auto-detected according to the classpath.
*/
private RedisProperties.ClientType clientType;
private RedisProperties.Sentinel sentinel;
private RedisProperties.Cluster cluster;
private final RedisProperties.Jedis jedis = new RedisProperties.Jedis();
private final RedisProperties.Lettuce lettuce = new RedisProperties.Lettuce();
// get/set....
分别对应2个Configuration配置类IotRedisConfiguration 和 UserRedisConfiguration
@EnableConfigurationProperties(value = {RedisIotDbProperties.class})
@ConditionalOnProperty(name = "redis.iot.enable" ,havingValue = "true", matchIfMissing = false)
@Configuration
public class IotRedisConfiguration {
@Primary
@Bean("iotRedisConnectionFactory")
public LettuceConnectionFactory userRedisConnectionFactory(ObjectProvider<RedisIotDbProperties> redisIotDbPropertiesProvider) {
RedisIotDbProperties redisIotDbProperties = redisIotDbPropertiesProvider.getIfAvailable();
RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
config.setHostName(redisIotDbProperties.getHost());
config.setPort(redisIotDbProperties.getPort());
config.setUsername(redisIotDbProperties.getUsername());
config.setPassword(RedisPassword.of(redisIotDbProperties.getPassword()));
config.setDatabase(redisIotDbProperties.getDatabase());
// 获取LettuceClientConfiguration
LettuceClientConfiguration lettuceClientConfiguration = lettuceClientConfiguration(redisIotDbProperties);
// 构建LettuceConnectionFactory
return new LettuceConnectionFactory(config, lettuceClientConfiguration);
}
public LettuceClientConfiguration lettuceClientConfiguration(RedisIotDbProperties redisIotDbProperties) {
RedisProperties.Pool pool = redisIotDbProperties.getLettuce().getPool();
GenericObjectPoolConfig<?> config = new GenericObjectPoolConfig<>();
config.setMaxTotal(pool.getMaxActive());
config.setMaxIdle(pool.getMaxIdle());
config.setMinIdle(pool.getMinIdle());
if (pool.getTimeBetweenEvictionRuns() != null) {
config.setTimeBetweenEvictionRuns(pool.getTimeBetweenEvictionRuns());
}
if (pool.getMaxWait() != null) {
config.setMaxWait(pool.getMaxWait());
}
//
LettucePoolingClientConfiguration.LettucePoolingClientConfigurationBuilder builder = LettucePoolingClientConfiguration.builder().poolConfig(config);
if (redisIotDbProperties.isSsl()) {
builder.useSsl();
}
if (redisIotDbProperties.getTimeout() != null) {
builder.commandTimeout(redisIotDbProperties.getTimeout());
}
if (redisIotDbProperties.getLettuce() != null) {
RedisProperties.Lettuce lettuce = redisIotDbProperties.getLettuce();
if (lettuce.getShutdownTimeout() != null && !lettuce.getShutdownTimeout().isZero()) {
builder.shutdownTimeout(redisIotDbProperties.getLettuce().getShutdownTimeout());
}
}
if (StringUtils.hasText(redisIotDbProperties.getClientName())) {
builder.clientName(redisIotDbProperties.getClientName());
}
return builder.build();
}
@Bean("iotRedisTemplate")
public RedisTemplate<Object, Object> redisTemplate(@Qualifier("iotRedisConnectionFactory") RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
// 指定序列化
GenericFastJsonRedisSerializer genericFastJsonRedisSerializer = new GenericFastJsonRedisSerializer();
// keySerializer & valueSerializer
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(genericFastJsonRedisSerializer);
// hash keySerializer & valueSerializer
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(genericFastJsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
@EnableConfigurationProperties(value = {RedisUserDbProperties.class})
@ConditionalOnProperty(name = "redis.user.enable" ,havingValue = "true", matchIfMissing = false)
@Configuration
public class UserRedisConfiguration {
@Bean("userRedisConnectionFactory")
public LettuceConnectionFactory userRedisConnectionFactory(ObjectProvider<RedisUserDbProperties> redisUserDbPropertiesProvider) {
RedisUserDbProperties redisUserDbProperties = redisUserDbPropertiesProvider.getIfAvailable();
RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
config.setHostName(redisUserDbProperties.getHost());
config.setPort(redisUserDbProperties.getPort());
config.setUsername(redisUserDbProperties.getUsername());
config.setPassword(RedisPassword.of(redisUserDbProperties.getPassword()));
config.setDatabase(redisUserDbProperties.getDatabase());
// 获取LettuceClientConfiguration
LettuceClientConfiguration lettuceClientConfiguration = lettuceClientConfiguration(redisUserDbProperties);
// 构建LettuceConnectionFactory
return new LettuceConnectionFactory(config, lettuceClientConfiguration);
}
public LettuceClientConfiguration lettuceClientConfiguration(RedisUserDbProperties redisUserDbProperties) {
RedisProperties.Pool pool = redisUserDbProperties.getLettuce().getPool();
GenericObjectPoolConfig<?> config = new GenericObjectPoolConfig<>();
config.setMaxTotal(pool.getMaxActive());
config.setMaxIdle(pool.getMaxIdle());
config.setMinIdle(pool.getMinIdle());
if (pool.getTimeBetweenEvictionRuns() != null) {
config.setTimeBetweenEvictionRuns(pool.getTimeBetweenEvictionRuns());
}
if (pool.getMaxWait() != null) {
config.setMaxWait(pool.getMaxWait());
}
//
LettucePoolingClientConfiguration.LettucePoolingClientConfigurationBuilder builder = LettucePoolingClientConfiguration.builder().poolConfig(config);
if (redisUserDbProperties.isSsl()) {
builder.useSsl();
}
if (redisUserDbProperties.getTimeout() != null) {
builder.commandTimeout(redisUserDbProperties.getTimeout());
}
if (redisUserDbProperties.getLettuce() != null) {
RedisProperties.Lettuce lettuce = redisUserDbProperties.getLettuce();
if (lettuce.getShutdownTimeout() != null && !lettuce.getShutdownTimeout().isZero()) {
builder.shutdownTimeout(redisUserDbProperties.getLettuce().getShutdownTimeout());
}
}
if (StringUtils.hasText(redisUserDbProperties.getClientName())) {
builder.clientName(redisUserDbProperties.getClientName());
}
return builder.build();
}
@Bean("userRedisTemplate")
public RedisTemplate<Object, Object> redisTemplate(@Qualifier("userRedisConnectionFactory") RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
// 指定序列化
GenericFastJsonRedisSerializer genericFastJsonRedisSerializer = new GenericFastJsonRedisSerializer();
// keySerializer & valueSerializer
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(genericFastJsonRedisSerializer);
// hash keySerializer & valueSerializer
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(genericFastJsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
对应的工具类RedisCacheManager,如下:
@Component
public class RedisCacheManager {
@Autowired
@Qualifier("userRedisTemplate")
private RedisTemplate userRedisTemplate;
@Autowired
@Qualifier("iotRedisTemplate")
private RedisTemplate iotRedisTemplate;
private RedisCacheHelper userRedisCacheHelper;
private RedisCacheHelper iotRedisCacheHelper;
@PostConstruct
public void initHelper() {
userRedisCacheHelper = new RedisCacheHelper(userRedisTemplate);
iotRedisCacheHelper = new RedisCacheHelper(iotRedisTemplate);
}
public RedisCacheHelper getUserRedisCacheHelper() {
return userRedisCacheHelper;
}
public RedisCacheHelper getIotRedisCacheHelper() {
return iotRedisCacheHelper;
}
}
方法二:数据源个数不确定:3个乃至更多
利用ImportBeanDefinitionRegistrar,注册多个redis数据源对应的LettuceConnectionFactory 和 redisTemplate
实现在一个java项目中,能够连接多个redis,并且动态切换。
实现如下:
1.application.yaml 配置文件中的配置信息如下:
redis:
dynamic:
endpoints:
# xuyi
- company-id: 07b0c05f-935d-43f6-9e64-3813919e8aa1
host: 218.94.128.34
port: 16379
database: 3
password: long123456@HhRedis
timeout: 15000
lettuce:
pool:
min-idle: 0
max-idle: 8
max-active: 8
max-wait: -1
# hxhb
- company-id: 76d843a4-94ba-47dc-9334-e9fefbfdd1a9
host: 218.94.128.34
port: 16379
database: 4
password: long123456@HhRedis
timeout: 15000
lettuce:
pool:
min-idle: 0
max-idle: 8
max-active: 8
max-wait: -1
# mq
- company-id: ca06fe31-b226-4358-a568-f40a655ef418
host: 218.94.128.34
port: 16379
database: 5
password: long123456@HhRedis
timeout: 15000
lettuce:
pool:
min-idle: 0
max-idle: 8
max-active: 8
max-wait: -1
2.指定一个java类,绑定配置文件中的配置信息
@ConfigurationProperties(value = "redis.dynamic")
public class RedisDynamicProperties {
private List<RedisEndpointProps> endpoints;
public List<RedisEndpointProps> getEndpoints() {
return endpoints;
}
public void setEndpoints(List<RedisEndpointProps> endpoints) {
this.endpoints = endpoints;
}
}
public class RedisEndpointProps {
private String companyId;
/**
* Login username of the redis server.
*/
private String username;
private String host;
private int port;
private int database;
private String password;
private Duration timeout;
private final RedisProperties.Lettuce lettuce = new RedisProperties.Lettuce();
private boolean ssl;
private String clientName;
// get/set.......
}
3.创建一个ImportBeanDefinitionRegistrar的实现类---RedisDynamicBeanDefinitionRegistrar
public class RedisDynamicBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar, EnvironmentAware {
private static final boolean COMMONS_POOL2_AVAILABLE = ClassUtils.isPresent("org.apache.commons.pool2.ObjectPool",
RedisDynamicBeanDefinitionRegistrar.class.getClassLoader());
private Environment environment;
private static Map<String, Object> registerBean = new ConcurrentHashMap<>();
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
RedisDynamicProperties redisDynamicProperties = new RedisDynamicProperties();
bindProperties(redisDynamicProperties);
List<RedisEndpointProps> endpoints = redisDynamicProperties.getEndpoints();
for (RedisEndpointProps endpoint : endpoints) {
String companyId = endpoint.getCompanyId();
// 创建letture连接客户端的配置,包括一些超时时间、最大多少个连接数等
LettuceClientConfiguration lettuceClientConfiguration = lettuceClientConfiguration(endpoint);
// 创建单例配置,包括连接redis server的ip/port/password/database等
RedisStandaloneConfiguration standaloneConfig = getStandaloneConfig(endpoint);
// 这里,创建一个LettuceConnectionFactory,然后存储到registerBean这个Map中,目的是,防止重复创建
Supplier<LettuceConnectionFactory> lettuceConnectionFactorySupplier = () -> {
LettuceConnectionFactory factory = (LettuceConnectionFactory) registerBean.get("LettuceConnectionFactory" + companyId);
if (factory != null) {
return factory;
}
factory = new LettuceConnectionFactory(standaloneConfig, lettuceClientConfiguration);
registerBean.put("LettuceConnectionFactory" + companyId,factory);
return factory;
};
// 这里,是利用BeanDefinitionBuilder的genericBeanDefinition方法,提供class和instance,得到BeanDefinitionBuilder
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(LettuceConnectionFactory.class, lettuceConnectionFactorySupplier);
// 利用BeanDefinitionBuilder,得到BeanDefinition,然后将该BeanDefinition注册到registry中
registry.registerBeanDefinition("lettuceConnectionFactory" + companyId, builder.getRawBeanDefinition());
//
LettuceConnectionFactory lettuceConnectionFactory = lettuceConnectionFactorySupplier.get();
// 上面是往registry中注册了LettuceConnectionFactory的BeanDefinition,
//这里,是往registry中,注册RedisTemplate的BeanDefinition
registerRedisTemplate(registry, companyId, lettuceConnectionFactory);
}
}
各方法如下:
private void registerRedisTemplate(BeanDefinitionRegistry registry, String companyId, LettuceConnectionFactory lettuceConnectionFactory) {
Map<String, Object> propertyValues = buildPropertyValues(lettuceConnectionFactory);
BeanDefinitionBuilder redisTemplateBeanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(RedisTemplate.class);
redisTemplateBeanDefinitionBuilder.setAutowireMode(AutowireCapableBeanFactory.AUTOWIRE_BY_NAME);
doRegister(registry, RedisConstant.REDIS_TEMPLATE_STR.concat(companyId), redisTemplateBeanDefinitionBuilder,propertyValues,new Object[]{});
}
public void doRegister(BeanDefinitionRegistry registry,
String beanName,
BeanDefinitionBuilder beanDefinitionBuilder,
Map<String, Object> propertyValues,
Object... constructorArgs) {
for (Object constructorArg : constructorArgs) {
beanDefinitionBuilder.addConstructorArgValue(constructorArg);
}
if (MapUtils.isNotEmpty(propertyValues)) {
propertyValues.forEach(beanDefinitionBuilder::addPropertyValue);
}
registry.registerBeanDefinition(beanName, beanDefinitionBuilder.getBeanDefinition());
}
private RedisStandaloneConfiguration getStandaloneConfig(RedisEndpointProps endpoint) {
RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
config.setHostName(endpoint.getHost());
config.setPort(endpoint.getPort());
config.setUsername(endpoint.getUsername());
config.setPassword(RedisPassword.of(endpoint.getPassword()));
config.setDatabase(endpoint.getDatabase());
return config;
}
private void bindProperties(RedisDynamicProperties redisDynamicProperties) {
Binder binder = Binder.get(environment);
ResolvableType type = ResolvableType.forClass(RedisDynamicProperties.class);
Bindable<?> target = Bindable.of(type).withExistingValue(redisDynamicProperties);
binder.bind("redis.dynamic", target);
}
private Map<String, Object> buildPropertyValues(LettuceConnectionFactory lettuceConnectionFactory) {
Map<String, Object> propertyValues = Maps.newHashMap();
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
GenericFastJsonRedisSerializer genericFastJsonRedisSerializer = new GenericFastJsonRedisSerializer();
propertyValues.put("connectionFactory", lettuceConnectionFactory);
propertyValues.put("keySerializer", stringRedisSerializer);
propertyValues.put("valueSerializer", genericFastJsonRedisSerializer);
propertyValues.put("hashKeySerializer", stringRedisSerializer);
propertyValues.put("hashValueSerializer", genericFastJsonRedisSerializer);
return propertyValues;
}
public LettuceClientConfiguration lettuceClientConfiguration(RedisEndpointProps endpoint) {
RedisProperties.Lettuce lettuce = endpoint.getLettuce();
RedisProperties.Pool pool = lettuce.getPool();
GenericObjectPoolConfig<?> config = new GenericObjectPoolConfig<>();
config.setMaxTotal(pool.getMaxActive());
config.setMaxIdle(pool.getMaxIdle());
config.setMinIdle(pool.getMinIdle());
if (pool.getTimeBetweenEvictionRuns() != null) {
config.setTimeBetweenEvictionRuns(pool.getTimeBetweenEvictionRuns());
}
if (pool.getMaxWait() != null) {
config.setMaxWait(pool.getMaxWait());
}
//
LettucePoolingClientConfiguration.LettucePoolingClientConfigurationBuilder builder = LettucePoolingClientConfiguration.builder().poolConfig(config);
ClientOptions clientOptions = createClientOptions();
DefaultClientResources clientResources = DefaultClientResources.builder().build();
builder.clientOptions(clientOptions);
builder.clientResources(clientResources);
if (endpoint.isSsl()) {
builder.useSsl();
}
if (endpoint.getTimeout() != null) {
builder.commandTimeout(endpoint.getTimeout());
}
if (lettuce != null) {
if (lettuce.getShutdownTimeout() != null && !lettuce.getShutdownTimeout().isZero()) {
builder.shutdownTimeout(lettuce.getShutdownTimeout());
}
}
if (StringUtils.hasText(endpoint.getClientName())) {
builder.clientName(endpoint.getClientName());
}
return builder.build();
}
private ClientOptions createClientOptions() {
ClientOptions.Builder builder = ClientOptions.builder();
ClientOptions clientOptions = builder.timeoutOptions(TimeoutOptions.enabled()).build();
return clientOptions;
}
4.在@Configuration注解的配置类上使用@Import导入实现类;
@EnableConfigurationProperties(value = {RedisDynamicProperties.class})
@Import(value = {RedisDynamicBeanDefinitionRegistrar.class})
@Configuration
public class RedisDynamicConfiguration {
}
这里,涉及到了一个,有意思的方法:
// org.springframework.beans.factory.support.BeanDefinitionBuilder#genericBeanDefinition
Create a new BeanDefinitionBuilder used to construct a GenericBeanDefinition.
Params:
beanClass – the Class of the bean that the definition is being created for
instanceSupplier – a callback for creating an instance of the bean
public static <T> BeanDefinitionBuilder genericBeanDefinition(Class<T> beanClass, Supplier<T> instanceSupplier) {
BeanDefinitionBuilder builder = new BeanDefinitionBuilder(new GenericBeanDefinition());
builder.beanDefinition.setBeanClass(beanClass);
builder.beanDefinition.setInstanceSupplier(instanceSupplier);
return builder;
}
一般来说,都是,我们提供class、构造参数、属性值,来创建一个BeanDefinition,然后spring再利用提供的BeanDefinition,自动创建对应的实例。
但是,这里是提供了一个Supplier,也即将实例的创建,交给了开发者,spring框架不再自动创建该实例。这样,就会更加的灵活。
上面,我们往容器中,注册了多个redisTemplate,下面看下,在代码中,如何使用呢?
@Component
public class RedisDynamicTemplateHolder implements ApplicationContextAware {
private ApplicationContext applicationContext;
private Map<String, RedisTemplate> companyIdRedisTemplateMap = new ConcurrentHashMap<>();
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
public RedisTemplate getTemplateByCompanyId(String companyId) {
if (companyIdRedisTemplateMap.containsKey(companyId)) {
return companyIdRedisTemplateMap.get(companyId);
} else {
RedisTemplate redisTemplate = applicationContext.getBean(RedisConstant.REDIS_TEMPLATE_STR.concat(companyId), RedisTemplate.class);
if (Objects.nonNull(redisTemplate)) {
synchronized (RedisDynamicTemplateHolder.class) {
if (companyIdRedisTemplateMap.containsKey(companyId)) {
return companyIdRedisTemplateMap.get(companyId);
}
redisTemplate.setScriptExecutor(new DefaultScriptExecutor(redisTemplate));
companyIdRedisTemplateMap.put(companyId, redisTemplate);
return redisTemplate;
}
} else {
throw new UnsupportedOperationException("该集团id,暂时未接入");
}
}
}
}
@Component
public class RedisCacheManager {
@Autowired
@Qualifier("userRedisTemplate")
private RedisTemplate userRedisTemplate;
@Autowired
private RedisDynamicTemplateHolder redisDynamicTemplateHolder;
private Map<String, RedisCacheHelper> companyIdRedisCacheHelperMap = new ConcurrentHashMap<>();
private RedisCacheHelper userRedisCacheHelper;
@PostConstruct
public void initHelper() {
userRedisCacheHelper = new RedisCacheHelper(userRedisTemplate);
}
public RedisCacheHelper getUserRedisCacheHelper() {
return userRedisCacheHelper;
}
public RedisCacheHelper getBizRedisCacheHelper(String companyId) {
if (StringUtils.isEmpty(companyId)) {
throw new UnsupportedOperationException("companyId不能为空,请检查程序");
}
RedisCacheHelper redisCacheHelper = companyIdRedisCacheHelperMap.get(companyId);
if (Objects.nonNull(redisCacheHelper)) {
return redisCacheHelper;
} else {
RedisTemplate templateByCompanyId = redisDynamicTemplateHolder.getTemplateByCompanyId(companyId);
if (Objects.nonNull(templateByCompanyId)) {
companyIdRedisCacheHelperMap.put(companyId, new RedisCacheHelper(templateByCompanyId));
return companyIdRedisCacheHelperMap.get(companyId);
} else {
throw new UnsupportedOperationException("获取cacheHelper失败,该集团id,暂时未接入");
}
}
}
}