• MyBatis-Plus多数据源dynamic-datasource解决多数据源Redis Key 重复问题



    前言

    在使用ynamic-datasource 因为多个租户共用一个系统,但是每个租户设置的缓存资源必须是隔离的,不然就会造成redis 缓存数据混乱的情况,如 租户1 设置key为name value 为张三的缓存,其他的租户因为缓存key 也是相同的,造成多租户缓存的数据没有做到隔离性。


    一、Redis Key 解决思路:

    既然redis 通过key 存储和获取缓存数据,那么只需要将key 进行处理,既可以为key追加数据源的标识即可解决。

    二、Redis Key 增加数据源标识:

    在springboot 中redis 会通过 RedisAutoConfiguration 自动装配 两个redisTemplate 和 stringRedisTemplate,然后让我们直接在项目中使用;

    //
    // Source code recreated from a .class file by IntelliJ IDEA
    // (powered by FernFlower decompiler)
    //
    
    package org.springframework.boot.autoconfigure.data.redis;
    
    import org.springframework.boot.autoconfigure.AutoConfiguration;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
    import org.springframework.boot.context.properties.EnableConfigurationProperties;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Import;
    import org.springframework.data.redis.connection.RedisConnectionFactory;
    import org.springframework.data.redis.core.RedisOperations;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.data.redis.core.StringRedisTemplate;
    
    @AutoConfiguration
    @ConditionalOnClass({RedisOperations.class})
    @EnableConfigurationProperties({RedisProperties.class})
    @Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
    public class RedisAutoConfiguration {
        public RedisAutoConfiguration() {
        }
    
        @Bean
        @ConditionalOnMissingBean(
            name = {"redisTemplate"}
        )
        @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
        public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
            RedisTemplate<Object, Object> template = new RedisTemplate();
            template.setConnectionFactory(redisConnectionFactory);
            return template;
        }
    
        @Bean
        @ConditionalOnMissingBean
        @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
        public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
            return new StringRedisTemplate(redisConnectionFactory);
        }
    }
    
    
    • 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

    通过代码可以知晓,这两个bean 的加载条件都是 ConditionalOnMissingBean,所以我们可以通过在项目中自己定义相同的bean 从而完成bean覆盖,在我们自定义的bean 中我们就可以对redis 的序列化key 做自定义的操作处理。

    2.1 自定义redis key 的序列化:

    CustomerStringRedisSerializer 代码如下(示例):

    
    
    import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.data.redis.serializer.RedisSerializer;
    import org.springframework.lang.Nullable;
    import org.springframework.util.Assert;
    import org.springframework.util.StringUtils;
    
    import java.nio.charset.Charset;
    import java.nio.charset.StandardCharsets;
    import java.util.ArrayList;
    import java.util.List;
    
    @Slf4j
    public class CustomerStringRedisSerializer implements RedisSerializer<String> {
        private final Charset charset;
        public static final CustomerStringRedisSerializer US_ASCII;
        public static final CustomerStringRedisSerializer ISO_8859_1;
        public static final CustomerStringRedisSerializer UTF_8;
    
        public CustomerStringRedisSerializer() {
            this(StandardCharsets.UTF_8);
        }
    
        public CustomerStringRedisSerializer(Charset charset) {
            Assert.notNull(charset, "Charset must not be null!");
            this.charset = charset;
        }
    
        public String deserialize(@Nullable byte[] bytes) {
            return bytes == null ? null : new String(bytes, this.charset);
        }
    	// 重要方法,在这里对key 的序列化完成自定义处理 ,追加数据源标识
        public byte[] serialize(@Nullable String string) {
            if (notRedisDbKey(string)) {
            	// 可以自定义过滤掉某些不需要追加 数据源标识的key
                return string == null ? null : string.getBytes(this.charset);
            }
            try {
                if (hasDbKey(string)) {
                	// 自定义方法,判断如果key 已经有了数据源标识则直接序列化并返回
                    return string == null ? null : string.getBytes(this.charset);
                }
                // 获取当前的数据源标识
                String dbKey = DynamicDataSourceContextHolder.peek();
                if (StringUtils.hasText(dbKey) && !"master".equals(dbKey)) {
                	//  如果感觉 数据源标识比较敏感 ,可以做一次转换处理
                    string = string + ":" + transtorDbKey(dbKey);
                } else {
                	// 如果当前没有数据源 则直接追加 一个自己想定义的标识
                    string = string + ":master";
                }
                return string == null ? null : string.getBytes(this.charset);
            } catch (Throwable ex) {
                log.debug("get redis dbkey not dbkey to choice");
                // 如果获取数据源过程出错了,这里可以直接抛出异常,或者按自己业务处理
                return string.getBytes(this.charset);
            }
        }
        
     	private String transtorDbKey(String dbKey) {
            // do some thing
            return dbKey;
        }
        private boolean notRedisDbKey(String string) {
           
            // OTHER KEY
            return false;
        }
    
    
    
        private boolean hasDbKey(String string) {
            
            return  false;
        }
    
        public Class<?> getTargetType() {
            return String.class;
        }
    
        static {
            US_ASCII = new CustomerStringRedisSerializer(StandardCharsets.US_ASCII);
            ISO_8859_1 = new CustomerStringRedisSerializer(StandardCharsets.ISO_8859_1);
            UTF_8 = new CustomerStringRedisSerializer(StandardCharsets.UTF_8);
        }
    }
    
    
    • 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

    在定义好 redis key 的序列化之后,后面我们将 定义好的 CustomerStringRedisSerializer 填充到redisTemplate 和 stringRedisTemplate,这样我们在对redis 进行set 和get 时,传入的key 就会在增加租户标识后在进行序列化。
    我们定义的 CustomerStringRedisSerializer 是参考 redis StringRedisSerializer 序列化进行的改造:

    StringRedisSerializer 代码如下(示例):

    //
    // Source code recreated from a .class file by IntelliJ IDEA
    // (powered by FernFlower decompiler)
    //
    
    package org.springframework.data.redis.serializer;
    
    import java.nio.charset.Charset;
    import java.nio.charset.StandardCharsets;
    import org.springframework.lang.Nullable;
    import org.springframework.util.Assert;
    
    public class StringRedisSerializer implements RedisSerializer<String> {
        private final Charset charset;
        public static final StringRedisSerializer US_ASCII;
        public static final StringRedisSerializer ISO_8859_1;
        public static final StringRedisSerializer UTF_8;
    
        public StringRedisSerializer() {
            this(StandardCharsets.UTF_8);
        }
    
        public StringRedisSerializer(Charset charset) {
            Assert.notNull(charset, "Charset must not be null!");
            this.charset = charset;
        }
    
        public String deserialize(@Nullable byte[] bytes) {
            return bytes == null ? null : new String(bytes, this.charset);
        }
    
        public byte[] serialize(@Nullable String string) {
            return string == null ? null : string.getBytes(this.charset);
        }
    
        public Class<?> getTargetType() {
            return String.class;
        }
    
        static {
            US_ASCII = new StringRedisSerializer(StandardCharsets.US_ASCII);
            ISO_8859_1 = new StringRedisSerializer(StandardCharsets.ISO_8859_1);
            UTF_8 = new StringRedisSerializer(StandardCharsets.UTF_8);
        }
    }
    
    
    • 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

    2.2 增加redis key的序列化:

    RedisConfig 示例代码

    
    
    import com.fasterxml.jackson.annotation.JsonAutoDetect;
    import com.fasterxml.jackson.annotation.JsonTypeInfo;
    import com.fasterxml.jackson.annotation.PropertyAccessor;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
    import org.springframework.cache.annotation.CachingConfigurerSupport;
    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.core.StringRedisTemplate;
    import org.springframework.data.redis.serializer.StringRedisSerializer;
    
    /**
     * redis配置
     * 
     * @author ruoyi
     */
    @Configuration
    @EnableCaching
    public class RedisConfig extends CachingConfigurerSupport
    {
        @Bean
        @SuppressWarnings(value = { "unchecked", "rawtypes" })
        public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory)
        {
            RedisTemplate<Object, Object> template = new RedisTemplate<>();
            template.setConnectionFactory(connectionFactory);
    
            FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
    
            ObjectMapper mapper = new ObjectMapper();
            mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
            mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
            serializer.setObjectMapper(mapper);
    
            // 使用CustomerStringRedisSerializer 来序列化和反序列化redis的key值
            CustomerStringRedisSerializer keySerializer = new CustomerStringRedisSerializer();
    //        template.setKeySerializer(new StringRedisSerializer());
            template.setKeySerializer(keySerializer);
            template.setValueSerializer(serializer);
    
            // Hash的key也采用CustomerStringRedisSerializer 的序列化方式
    //        template.setHashKeySerializer(new StringRedisSerializer());
            template.setHashKeySerializer(keySerializer);
            template.setHashValueSerializer(serializer);
    
            template.afterPropertiesSet();
            return template;
        }
        @Bean
        public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
            StringRedisTemplate template = new StringRedisTemplate();
            template.setConnectionFactory(redisConnectionFactory);
            // 使用CustomerStringRedisSerializer 来序列化和反序列化redis的key值
            CustomerStringRedisSerializer keySerializer = new CustomerStringRedisSerializer();
            template.setKeySerializer(keySerializer);
            template.setHashKeySerializer(keySerializer);
    
            template.afterPropertiesSet();
            return template;
        }
    }
    
    
    • 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

    因为redis 的hash 数据内层还有一个key 所以我们同时对 key 和 hashKey 都进行下处理;


    总结

    我们通过自定义 redis key 的序列化类,完成对redis key 不同租户标识的追加,从而解决不同租户缓存数据的隔离。

  • 相关阅读:
    计算机毕业设计(附源码)python中小型企业工作日志管理系统APP
    java开发工具IDEA JVM框架教程:打开 Web 应用程序选项
    RabbitMQ - 死信、TTL原理、延迟队列安装和配置
    css未知宽高,上下左右居中
    2022春招大厂-嵌入式开发经典笔试面试题目大整理
    【Java八股文总结】之异常
    C++ 指针介绍
    【Vue】模板语法,插值、指令、过滤器、计算属性及监听属性(内含面试题及毕设等实用案例)上篇
    Windows右键菜单
    区分 关系代数中的笛卡尔积、等值连接、自然连接(图文界面)
  • 原文地址:https://blog.csdn.net/l123lgx/article/details/133761562