栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

springboot Redis 多数据源组件

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

springboot Redis 多数据源组件

由于系统需要同一个项目需要配置多个redis数据源,并且很多项目都是同样的需求;在网上查了一波,大家的做法都是在每个需要配置多个数据源的地方添加多个配置类,需要引入几个数据源就需要写几个配置类,如果有N个项目就需要在N个项目中重复N遍相同的代码;所以就想到了自己动手对springboot提供的redis模块进行封装,使其可以无需任何多余的代码编写只需引入提前写好的组件就可以完成任意多个redis数据源的配置。

组件支持哪些功能
  1. 基于springboot自动化配置,实现开箱即用
  2. 完全基于springboot redis对其进行二次封装
  3. 基于lettuce实现连接池,不支持jedis连接池
  4. 支持单节点、哨兵模式、集群模式(支持拓扑刷新能力)

先展示下属性配置方法(根据不同的标识区分不同的数据源):

spring.emily.redis.config.default.client-type=lettuce
spring.emily.redis.config.default.database=15
spring.emily.redis.config.default.password=123
spring.emily.redis.config.default.sentinel.master=123
spring.emily.redis.config.default.sentinel.nodes=xx.xx.xx.xx:xx,xx.xx.xx.xx:xx,xx.xx.xx.xx:xx
# 读取超时时间
spring.emily.redis.config.default.timeout=3000
# 连接超时时间
spring.emily.redis.config.default.connect-timeout=PT3S
spring.emily.redis.config.default.lettuce.pool.max-active=8
spring.emily.redis.config.default.lettuce.pool.max-idle=8
spring.emily.redis.config.default.lettuce.pool.min-idle=0
spring.emily.redis.config.default.lettuce.pool.max-wait=-1

spring.emily.redis.config.test.client-type=lettuce
spring.emily.redis.config.test.database=0
spring.emily.redis.config.test.password=123
spring.emily.redis.config.test.sentinel.master=123
spring.emily.redis.config.test.sentinel.nodes=xx.xx.xx.xx:xx,xx.xx.xx.xx:xx,xx.xx.xx.xx:xx
spring.emily.redis.config.test.timeout=300
spring.emily.redis.config.test.lettuce.pool.max-active=8
spring.emily.redis.config.test.lettuce.pool.max-idle=8
spring.emily.redis.config.test.lettuce.pool.min-idle=0
spring.emily.redis.config.test.lettuce.pool.max-wait=-1
一、连接工厂配置类RedisDbConnectionConfiguration
public class RedisDbConnectionConfiguration {

    private RedisProperties properties;

    public RedisDbConnectionConfiguration(RedisProperties properties) {
        this.properties = properties;
    }

    
    public RedisConfiguration createRedisConfiguration() {

        if (getSentinelConfig() != null) {
            return getSentinelConfig();
        }
        if (getClusterConfiguration() != null) {
            return getClusterConfiguration();
        }
        return getStandaloneConfig();
    }

    
    protected final RedisStandaloneConfiguration getStandaloneConfig() {

        Assert.notNull(properties, "RedisProperties must not be null");

        RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
        if (StringUtils.hasText(this.properties.getUrl())) {
            ConnectionInfo connectionInfo = ConnectionInfo.parseUrl(properties.getUrl());
            config.setHostName(connectionInfo.getHostName());
            config.setPort(connectionInfo.getPort());
            config.setUsername(connectionInfo.getUsername());
            config.setPassword(RedisPassword.of(connectionInfo.getPassword()));
        } else {
            config.setHostName(properties.getHost());
            config.setPort(properties.getPort());
            config.setUsername(properties.getUsername());
            config.setPassword(RedisPassword.of(properties.getPassword()));
        }
        config.setDatabase(properties.getDatabase());
        return config;
    }

    
    private final RedisSentinelConfiguration getSentinelConfig() {

        Assert.notNull(properties, "RedisProperties must not be null");

        RedisProperties.Sentinel sentinelProperties = properties.getSentinel();
        if (sentinelProperties != null) {
            RedisSentinelConfiguration config = new RedisSentinelConfiguration();
            config.master(sentinelProperties.getMaster());
            config.setSentinels(createSentinels(sentinelProperties));
            config.setUsername(properties.getUsername());
            if (properties.getPassword() != null) {
                config.setPassword(RedisPassword.of(properties.getPassword()));
            }
            if (sentinelProperties.getPassword() != null) {
                config.setSentinelPassword(RedisPassword.of(sentinelProperties.getPassword()));
            }
            config.setDatabase(properties.getDatabase());
            return config;
        }
        return null;
    }

    
    private final RedisClusterConfiguration getClusterConfiguration() {

        Assert.notNull(properties, "RedisProperties must not be null");

        if (properties.getCluster() == null) {
            return null;
        }
        RedisProperties.Cluster clusterProperties = properties.getCluster();
        RedisClusterConfiguration config = new RedisClusterConfiguration(clusterProperties.getNodes());
        if (clusterProperties.getMaxRedirects() != null) {
            config.setMaxRedirects(clusterProperties.getMaxRedirects());
        }
        config.setUsername(properties.getUsername());
        if (properties.getPassword() != null) {
            config.setPassword(RedisPassword.of(properties.getPassword()));
        }
        return config;
    }

    
    private List createSentinels(RedisProperties.Sentinel sentinel) {
        List nodes = new ArrayList<>();
        for (String node : sentinel.getNodes()) {
            try {
                String[] parts = StringUtils.split(node, ":");
                Assert.state(parts.length == 2, "Must be defined as 'host:port'");
                nodes.add(new RedisNode(parts[0], Integer.parseInt(parts[1])));
            } catch (RuntimeException ex) {
                throw new IllegalStateException("Invalid redis sentinel property '" + node + "'", ex);
            }
        }
        return nodes;
    }
}
二、连接工厂类RedisDbConnectionFactory
public class RedisDbConnectionFactory {

    private RedisProperties properties;
    private ClientResources clientResources;

    public RedisDbConnectionFactory(ClientResources clientResources, RedisProperties properties) {
        this.clientResources = clientResources;
        this.properties = properties;
    }

    
    public RedisConnectionFactory getRedisConnectionFactory(ObjectProvider builderCustomizers,
                                                            RedisConfiguration redisConfiguration) {

        Assert.notNull(clientResources, "ClientResources must not be null");
        Assert.notNull(clientResources, "RedisDbProperties must not be null");

        //获取链接池配置
        RedisProperties.Pool pool = getProperties().getLettuce().getPool();
        LettuceClientConfiguration lettuceClientConfiguration = getLettuceClientConfiguration(builderCustomizers, pool);
        return createLettuceConnectionFactory(lettuceClientConfiguration, redisConfiguration);
    }

    private LettuceClientConfiguration getLettuceClientConfiguration(
            ObjectProvider builderCustomizers, RedisProperties.Pool pool) {
        LettuceClientConfiguration.LettuceClientConfigurationBuilder builder = createBuilder(pool);
        applyProperties(builder);
        if (StringUtils.hasText(getProperties().getUrl())) {
            customizeConfigurationFromUrl(builder);
        }
        builder.clientOptions(createClientOptions());
        builder.clientResources(getClientResources());
        builderCustomizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
        return builder.build();
    }

    private LettuceConnectionFactory createLettuceConnectionFactory(LettuceClientConfiguration clientConfig, RedisConfiguration redisConfiguration) {
        LettuceConnectionFactory factory = new LettuceConnectionFactory(redisConfiguration, clientConfig);
        // 创建Redis连接
        factory.afterPropertiesSet();
        // 将RedisConnectionFactory丢入线程池做监控
        ThreadPoolHelper.threadPoolTaskExecutor().execute(new RedisDbRunnable(factory));
        return factory;
    }

    private LettuceClientConfiguration.LettuceClientConfigurationBuilder createBuilder(RedisProperties.Pool pool) {
        if (pool == null) {
            return LettuceClientConfiguration.builder();
        }
        return new RedisPoolBuilderFactory().createBuilder(pool);
    }

    private LettuceClientConfiguration.LettuceClientConfigurationBuilder applyProperties(
            LettuceClientConfiguration.LettuceClientConfigurationBuilder builder) {
        if (getProperties().isSsl()) {
            builder.useSsl();
        }
        // Redis客户端读取超时时间
        if (getProperties().getTimeout() != null) {
            builder.commandTimeout(getProperties().getTimeout());
        }
        // 关闭连接池超时时间
        if (getProperties().getLettuce() != null) {
            RedisProperties.Lettuce lettuce = getProperties().getLettuce();
            if (lettuce.getShutdownTimeout() != null && !lettuce.getShutdownTimeout().isZero()) {
                builder.shutdownTimeout(getProperties().getLettuce().getShutdownTimeout());
            }
        }
        if (StringUtils.hasText(getProperties().getClientName())) {
            builder.clientName(getProperties().getClientName());
        }
        return builder;
    }

    private void customizeConfigurationFromUrl(LettuceClientConfiguration.LettuceClientConfigurationBuilder builder) {
        ConnectionInfo connectionInfo = ConnectionInfo.parseUrl(getProperties().getUrl());
        if (connectionInfo.isUseSsl()) {
            builder.useSsl();
        }
    }

    
    private ClientOptions createClientOptions() {
        ClientOptions.Builder builder = initializeClientOptionsBuilder();
        Duration connectTimeout = getProperties().getConnectTimeout();
        if (connectTimeout != null) {
            builder.socketOptions(SocketOptions.builder().connectTimeout(connectTimeout).build());
        }
        return builder.timeoutOptions(TimeoutOptions.enabled()).build();
    }

    
    private ClientOptions.Builder initializeClientOptionsBuilder() {
        if (getProperties().getCluster() != null) {
            ClusterClientOptions.Builder builder = ClusterClientOptions.builder();
            RedisProperties.Lettuce.Cluster.Refresh refreshProperties = getProperties().getLettuce().getCluster().getRefresh();
            ClusterTopologyRefreshOptions.Builder refreshBuilder = ClusterTopologyRefreshOptions.builder()
                    .dynamicRefreshSources(refreshProperties.isDynamicRefreshSources());
            if (refreshProperties.getPeriod() != null) {
                
                refreshBuilder.enablePeriodicRefresh(refreshProperties.getPeriod());
            }

            if (refreshProperties.isAdaptive()) {
                
                refreshBuilder.enableAllAdaptiveRefreshTriggers();
            }

            return builder.topologyRefreshOptions(refreshBuilder.build());
        } else {
            return ClientOptions.builder();
        }
    }

    public RedisProperties getProperties() {
        return properties;
    }

    public void setProperties(RedisProperties properties) {
        this.properties = properties;
    }

    public ClientResources getClientResources() {
        return clientResources;
    }

    public void setClientResources(ClientResources clientResources) {
        this.clientResources = clientResources;
    }
}

三、对redis进行连接池支持的配置类
public class RedisPoolBuilderFactory {
    LettuceClientConfiguration.LettuceClientConfigurationBuilder createBuilder(RedisProperties.Pool properties) {
        return LettucePoolingClientConfiguration.builder().poolConfig(getPoolConfig(properties));
    }

    private GenericObjectPoolConfig getPoolConfig(RedisProperties.Pool properties) {
        GenericObjectPoolConfig config = new GenericObjectPoolConfig<>();
        config.setMaxTotal(properties.getMaxActive());
        config.setMaxIdle(properties.getMaxIdle());
        config.setMinIdle(properties.getMinIdle());
        if (properties.getTimeBetweenEvictionRuns() != null) {
            config.setTimeBetweenEvictionRunsMillis(properties.getTimeBetweenEvictionRuns().toMillis());
        }
        if (properties.getMaxWait() != null) {
            config.setMaxWaitMillis(properties.getMaxWait().toMillis());
        }
        return config;
    }
}
四、自动化配置类三件套中的配置类
@ConfigurationProperties(prefix = RedisDbProperties.PREFIX)
public class RedisDbProperties {
    
    public static final String PREFIX = "spring.emily.redis";
    
    public static final String DEFAULT_ConFIG = "default";
    
    private boolean enabled = true;
    
    private boolean monitorEnabled = false;
    
    private Duration monitorFireRate = Duration.ofSeconds(30);
    
    private String defaultConfig = DEFAULT_CONFIG;
    
    private Map config = new HashMap<>();

    public Map getConfig() {
        return config;
    }

    public String getDefaultConfig() {
        return defaultConfig;
    }

    public void setDefaultConfig(String defaultConfig) {
        this.defaultConfig = defaultConfig;
    }

    public void setConfig(Map config) {
        this.config = config;
    }

    public boolean isEnabled() {
        return enabled;
    }

    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }

    public RedisProperties getDefaultDataSource() {
        return this.config.get(this.getDefaultConfig());
    }

    public Duration getMonitorFireRate() {
        return monitorFireRate;
    }

    public void setMonitorFireRate(Duration monitorFireRate) {
        this.monitorFireRate = monitorFireRate;
    }

    public boolean isMonitorEnabled() {
        return monitorEnabled;
    }

    public void setMonitorEnabled(boolean monitorEnabled) {
        this.monitorEnabled = monitorEnabled;
    }
}

五、自动化配置类三件套中的配置类
@Configuration
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE+1)
@EnableConfigurationProperties(RedisDbProperties.class)
@ConditionalOnProperty(prefix = RedisDbProperties.PREFIX, name = "enabled", havingValue = "true", matchIfMissing = true)
public class RedisDbAutoConfiguration implements InitializingBean, DisposableBean {

    private static final Logger logger = LoggerFactory.getLogger(RedisDbAutoConfiguration.class);

    private DefaultListableBeanFactory defaultListableBeanFactory;

    public RedisDbAutoConfiguration(DefaultListableBeanFactory defaultListableBeanFactory) {
        this.defaultListableBeanFactory = defaultListableBeanFactory;
    }

    
    @Bean(destroyMethod = "shutdown")
    @ConditionalOnMissingBean(ClientResources.class)
    public DefaultClientResources clientResources() {
        return DefaultClientResources.create();
    }

    
    @Bean
    public Object initTargetRedis(ObjectProvider builderCustomizers, ClientResources clientResources, RedisDbProperties redisDbProperties) {

        Assert.notNull(clientResources, "ClientResources must not be null");
        Assert.notNull(clientResources, "RedisDbProperties must not be null");

        //创建Redis数据源配置key-value映射
        Table table = createConfiguration(redisDbProperties);
        table.rowKeySet().stream().forEach(key -> {
            Map dataMap = table.row(key);
            dataMap.forEach((properties, redisConfiguration) -> {
                //Redis连接工厂类
                RedisDbConnectionFactory redisDbConnectionFactory = new RedisDbConnectionFactory(clientResources, properties);
                //创建链接工厂类
                RedisConnectionFactory redisConnectionFactory = redisDbConnectionFactory.getRedisConnectionFactory(builderCustomizers, redisConfiguration);
                // 获取StringRedisTemplate对象
                StringRedisTemplate stringRedisTemplate = createStringRedisTemplate(redisConnectionFactory);
                // 将StringRedisTemplate对象注入IOC容器bean
                defaultListableBeanFactory.registerSingleton(RedisDbFactory.getStringRedisTemplateBeanName(key), stringRedisTemplate);
                // 获取RedisTemplate对象
                RedisTemplate redisTemplate = createRedisTemplate(redisConnectionFactory);
                // 将RedisTemplate对象注入IOC容器
                defaultListableBeanFactory.registerSingleton(RedisDbFactory.getRedisTemplateBeanName(key), redisTemplate);
            });
        });
        return "UNSET";
    }

    
    protected StringRedisTemplate createStringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {

        Assert.notNull(redisConnectionFactory, "RedisConnectionFactory must not be null");

        StringRedisTemplate stringRedisTemplate = new StringRedisTemplate(redisConnectionFactory);
        stringRedisTemplate.setKeySerializer(stringSerializer());
        stringRedisTemplate.setValueSerializer(stringSerializer());
        stringRedisTemplate.setHashKeySerializer(stringSerializer());
        stringRedisTemplate.setHashValueSerializer(stringSerializer());
        // bean初始化完成后调用方法,对于StringRedisTemplate可忽略,主要检查key-value序列化对象是否初始化,并标注RedisTemplate已经被初始化
        stringRedisTemplate.afterPropertiesSet();
        return stringRedisTemplate;
    }

    
    protected RedisTemplate createRedisTemplate(RedisConnectionFactory redisConnectionFactory) {

        Assert.notNull(redisConnectionFactory, "RedisConnectionFactory must not be null");

        RedisTemplate redisTemplate = new RedisTemplate();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        redisTemplate.setKeySerializer(stringSerializer());
        redisTemplate.setValueSerializer(jacksonSerializer());
        redisTemplate.setHashKeySerializer(stringSerializer());
        redisTemplate.setHashValueSerializer(jacksonSerializer());
        // bean初始化完成后调用方法,主要检查key-value序列化对象是否初始化,并标注RedisTemplate已经被初始化,否则会报:
        // "template not initialized; call afterPropertiesSet() before using it" 异常
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }

    
    protected Table createConfiguration(RedisDbProperties redisDbProperties) {

        Assert.notNull(redisDbProperties, "RedisDbProperties must not be null");

        Table table = HashbasedTable.create();
        Map redisPropertiesMap = redisDbProperties.getConfig();
        redisPropertiesMap.forEach((key, properties) -> {
            RedisDbConnectionConfiguration redisDbConnectionConfiguration = new RedisDbConnectionConfiguration(properties);
            RedisConfiguration redisConfiguration = redisDbConnectionConfiguration.createRedisConfiguration();
            table.put(key, properties, redisConfiguration);
        });
        return table;
    }

    
    protected StringRedisSerializer stringSerializer() {
        return new StringRedisSerializer();
    }

    
    protected Jackson2JsonRedisSerializer jacksonSerializer() {
        //使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();

        //指定要序列化的域、field、get和set,以及修饰符范围,ANY是都有包括private和public
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        // objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        // 第一个参数用于验证要反序列化的实际子类型是否对验证器使用的任何条件有效,在反序列化时必须设置,否则报异常
        // 第二个参数设置序列化的类型必须为非final类型,只有少数的类型(String、Boolean、Integer、Double)可以从JSON中正确推断
        objectMapper.activateDefaultTyping(LaissezFaireSubTypevalidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
        // 解决jackson2无法反序列化LocalDateTime的问题
        objectMapper.registerModule(new JavaTimeModule());

        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);

        return jackson2JsonRedisSerializer;
    }
}
 
六、自动化配置类中的spring.factories属性配置请参考源码 
七、对外提供服务的工厂方法RedisDbFactory 
public class RedisDbFactory {
    
    private static final String PREFIX_STRING = "ST";
    
    private static final String PREFIX_REST = "RT";
    
    private static final Map stringCache = new ConcurrentHashMap<>();
    
    private static final Map restCache = new ConcurrentHashMap<>();

    
    public static StringRedisTemplate getStringRedisTemplate() {
        return getStringRedisTemplate(RedisDbProperties.DEFAULT_CONFIG);
    }

    
    public static StringRedisTemplate getStringRedisTemplate(String redisMark) {
        
        String key = getStringRedisTemplateBeanName(redisMark);
        if (stringCache.containsKey(key)) {
            return stringCache.get(key);
        }
        if (!IOCContext.containsBean(key)) {
            throw new BasicException(AppHttpStatus.DATAbase_EXCEPTION.getStatus(), "Redis数据库标识对应的数据库不存在");
        }
        StringRedisTemplate stringRedisTemplate = IOCContext.getBean(key, StringRedisTemplate.class);
        stringCache.put(key, stringRedisTemplate);
        return stringRedisTemplate;
    }

    
    public static RedisTemplate getRedisTemplate() {
        return getRedisTemplate(RedisDbProperties.DEFAULT_CONFIG);
    }

    
    public static RedisTemplate getRedisTemplate(String redisMark) {
        
        String key = getRedisTemplateBeanName(redisMark);
        if (restCache.containsKey(key)) {
            return restCache.get(key);
        }
        if (!IOCContext.containsBean(key)) {
            throw new BasicException(AppHttpStatus.DATAbase_EXCEPTION.getStatus(), "Redis数据库标识对应的数据库不存在");
        }
        RedisTemplate redisTemplate = IOCContext.getBean(key, RedisTemplate.class);
        restCache.put(key, redisTemplate);
        return redisTemplate;
    }

    
    public static String getStringRedisTemplateBeanName(String redisMark) {
        if (Objects.isNull(redisMark)) {
            throw new BasicException(AppHttpStatus.ILLEGAL_ARGUMENT_EXCEPTION.getStatus(), "Redis数据库标识不可为空");
        }
        return MessageFormat.format("{0}_{1}", PREFIX_STRING, redisMark);
    }

    
    public static String getRedisTemplateBeanName(String redisMark) {
        if (Objects.isNull(redisMark)) {
            throw new BasicException(AppHttpStatus.ILLEGAL_ARGUMENT_EXCEPTION.getStatus(), "Redis数据库标识不可为空");
        }
        return MessageFormat.format("{0}_{1}", PREFIX_REST, redisMark);
    }
}

八、RedisTemplate、StringRedisTemplate的使用方法
    @GetMapping("get1")
    public String get1() {

        RedisDbFactory.getStringRedisTemplate().opsForValue().set("test", "123", 12, TimeUnit.MINUTES);
        Map dataMap = Maps.newHashMap();
        dataMap.put("te", 12);
        dataMap.put("te2", 12);
        dataMap.put("te3", "哈哈");
        RedisDbFactory.getRedisTemplate().opsForValue().set("test1", dataMap, 1, TimeUnit.MINUTES);
        RedisDbFactory.getRedisTemplate("one").opsForValue().set("one", "adf", 1, TimeUnit.MINUTES);
        return RedisDbFactory.getStringRedisTemplate("default").opsForValue().get("test");
    }

解说:文章中有部分辅助代码未展示完整,详情请参考源码

GitHub源码:https://github.com/mingyang66/spring-parent

转载请注明:文章转载自 www.mshxw.com
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号