美文网首页
Java分布式缓存之Redis

Java分布式缓存之Redis

作者: 在error边缘疯狂试探 | 来源:发表于2020-04-24 07:19 被阅读0次

    JSR107(最早提出)

    • Java Caching定义了5个核心接口

      • CachingProvider:
        定义了创建、配置、获取、管理和控制多个CacheManager。一个应用可
        以在运行期访问多个CachingProvider。

      • CacheManager:
        定义了创建、配置、获取、管理和控制多个唯一命名的Cache,这些Cache
        存在于CacheManager的上下文中。一个CacheManager仅被一个CachingProvider所拥有。

      • Cache:
        一个类似Map的数据结构并临时存储以Key为索引的值。一个Cache仅被一个
        CacheManager所拥有。

      • Entry:
        一个存储在Cache中的key-value对。

      • Expiry:

        每一个存储在Cache中的条目有一个定义的有效期。一旦超过这个时间,条目为过期的状态。一旦过期,条目将不可访问、更新和删除。缓存有效期可以通过ExpiryPolicy设置。 jsr107示意图

    Spring缓存抽象

    • Spring从3.1开始定义了org.springframework.cache.Cacheorg.springframework.cache.CacheManager接口来统一不同的缓存技术;
      并支持使用 JCache(JSR-107)注解 简化我们开发;

    • Cache接口有以下功能:

      • 为缓存的组件规范定义,包含缓存的各种操作集合;
      • Spring提供了各种xxxCache的实现;如RedisCache,EhCacheCache ,

        ConcurrentMapCache等; Spring缓存抽象

    重要缓存注解及概念

    • Cache:缓存接口,定义缓存操作。实现有:RedisCache、EhCacheCache、ConcurrentMapCache等
    • CacheManager:缓存管理器,管理各种缓存(Cache)组件
    • @Cacheable: 根据方法的请求参数对其结果进行缓存
    • @CacheEvict: 清空缓存
    • @CachePut: 更新缓存
    • @EnableCaching: 开启基于注解的缓存
    • keyGenerator: 缓存数据时key生成策略
    • serialize: 缓存数据时value序列化策略
    1 . @Cacheable/@CachePut/@CacheEvict 主要的参数(属性值)
    • value
      缓存名称,字符串/字符数组形式;
      如@Cacheable(value=”mycache”) 或者@Cacheable(value={”cache1”,”cache2”}

    • key
      缓存的key,需要按照SpEL表达式编写,如果不指定则按照方法所有参数进行组合;
      如@Cacheable(value=”testcache”,key=”#userName”)

    • keyGenerator
      key的生成器;可以自己指定key的生成器的组件id
      【注意】:key/keyGenerator:二选一使用;

    • condition
      缓存条件,使用SpEL编写,在调用方法之前之后都能判断;
      如@Cacheable(value=”testcache”,condition=”#userName.length()>2”)

    • unless(@CachePut、@Cacheable)
      用于否决缓存的条件,只在方法执行之后判断;
      如@Cacheable(value=”testcache”,unless=”#result ==null”)-->当结果不为空时更新缓存

    • beforeInvocation(@CacheEvict)
      是否在执行前清空缓存,默认为false,false情况下方法执行异常则不会清空;
      如@CachEvict(value=”testcache”,beforeInvocation=true)

    • allEntries(@CacheEvict)
      是否清空所有缓存内容,默认为false;
      如@CachEvict(value=”testcache”,allEntries=true)

    2 . 缓存可用的SpEL表达式(可以百度看具体的表达式详情)

    root

    表示根对象,不可省略

    • 被调用方法名 methodName,如 #root.methodName

    • 被调用方法 method,如 #root.method.name

    • 目标对象 target,如 #root.target

    • 被调用的目标对象类 targetClass,如 #root.targetClass

    • 被调用的方法的参数列表 args,如 #root.args[0]

    • 方法调用使用的缓存列表 caches,如 #root.caches[0].name

    参数名

    方法参数的名字. 可以直接 #参数名 ,也可以使用 #p0或#a0 的形式,0代表参数的索引;

    如 #iban 、 #a0 、 #p0

    返回值

    方法执行后的返回值(仅当方法执行之后的判断有效,如‘unless’ , @CachePut、@CacheEvict’的表达式beforeInvocation=false )

    如 #result

    SpringBoot 整合 Redis

    在SpringBoot中,缓存的自动配置类CacheAutoConfiguration向容器中导入了CacheConfigurationImportSelector,此类的selectImports()方法添加了许多配置类,其中SimpleCacheConfiguration默认生效。

    • ​GenericCacheConfiguration
    • JCacheCacheConfiguration
    • EhCacheCacheConfiguration
    • HazelcastCacheConfiguration
    • InfinispanCacheConfiguration
    • CouchbaseCacheConfiguration
    • RedisCacheConfiguration
    • CaffeineCacheConfiguration
    • GuavaCacheConfiguration
    • SimpleCacheConfiguration【默认】
    • NoOpCacheConfiguration

    【但是我们一般都不使用默认的,导入对应的缓存依赖就会实现对应的缓存配置类】

    • 导入依赖
    <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-data-redis</artifactId>
     </dependency>
    
    • 在spring.properties指定Redis服务器地址(前提是需要安装Redis)
    #redis服务器主机地址(可在Redis服务器中查看)
    spring.redis.host=192.168.31.162
    
    • RedisTemplate
      RedisAutoConfiguration向容器中导入了两个类RedisTemplate<Object, Object> redisTemplate和StringRedisTemplate,作为Redis客户端分别操作k-v都为对象和k-v都为字符串。

    • Redis常见的五大数据类型

      String(字符串)、List(列表)、Set(集合)、Hash(散列)、ZSet(有序集合)

    • stringRedisTemplate.opsForValue()[String(字符串)

    • stringRedisTemplate.opsForList()[List(列表)

    • stringRedisTemplate.opsForSet()[Set(集合)

    • stringRedisTemplate.opsForHash()[Hash(散列)

    • stringRedisTemplate.opsForZSet()[ZSet(有序集合)

    • Redis缓存使用
      在导入redis依赖后RedisCacheConfiguration类就会自动生效,创建RedisCacheManager,并使用RedisCache进行缓存数据,要缓存的对象的类要实现Serializable接口,默认情况下是以 jdk序列化 数据存在redis中,如下:

    k:"emp::1"
    v:
    \xAC\xED\x00\x05sr\x00$cn.edu.ustc.springboot.bean.Employeeuqf\x03p\x9A\xCF\xE0\x02\x00\x05L\x00\x03dIdt\x00\x13Ljava/lang/Integer;L\x00\x05emailt\x00\x12Ljava/lang/String;L\x00\x06genderq\x00~\x00\x01L\x00\x02idq\x00~\x00\x01L\x00\x08lastNameq\x00~\x00\x02xpsr\x00\x11java.lang.Integer\x12\xE2\xA0\xA4\xF7\x81\x878\x02\x00\x01I\x00\x05valuexr\x00\x10java.lang.Number\x86\xAC\x95\x1D\x0B\x94\xE0\x8B\x02\x00\x00xp\x00\x00\x00\x03t\x00\x07cch@aaasq\x00~\x00\x04\x00\x00\x00\x01q\x00~\x00\x08t\x00\x03cch
    

    要想让对象以json形式存储在redis中,需要自定义RedisCacheManager,使用GenericJackson2JsonRedisSerializer类对value进行序列化!

    @Configuration
    public class MyRedisConfig {
    
        @Bean
        public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
            RedisTemplate<Object, Object> template = new RedisTemplate();
            template.setConnectionFactory(redisConnectionFactory);
        // 使用JSON格式序列化对象,对缓存数据key和value进行转换
            Jackson2JsonRedisSerializer jacksonSeial = new Jackson2JsonRedisSerializer(Object.class);
          // 解决查询缓存转换异常的问题
          ObjectMapper om = new ObjectMapper();
          om.setVisibility(PropertyAccessor.ALL,JsonAutoDetect.Visibility.ANY);
          om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
          jacksonSeial.setObjectMapper(om);
          // 设置RedisTemplate模板API的序列化方式为JSON
            template.setDefaultSerializer(jacksonSeial);
            return template;
        }
    }
    

    或者使用这种方法(推荐)

    @Configuration
    public class MyRedisConfig {
        @Bean
        RedisCacheManager cacheManager(RedisConnectionFactory factory){
            //创建默认RedisCacheWriter
            RedisCacheWriter cacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(factory);
            
            //创建默认RedisCacheConfiguration并使用GenericJackson2JsonRedisSerializer构造的       SerializationPair对value进行转换
            //创建GenericJackson2JsonRedisSerializer的json序列化器
            GenericJackson2JsonRedisSerializer jsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
            //使用json序列化器构造出对转换Object类型的SerializationPair序列化对
            RedisSerializationContext.SerializationPair<Object> serializationPair = RedisSerializationContext.SerializationPair.fromSerializer(jsonRedisSerializer);
            //将可以把Object转换为json的SerializationPair传入RedisCacheConfiguration
            //使得RedisCacheConfiguration在转换value时使用定制序列化器
            RedisCacheConfiguration cacheConfiguration=RedisCacheConfiguration.defaultCacheConfig().serializeValuesWith(serializationPair);
            
            RedisCacheManager cacheManager = new RedisCacheManager(cacheWriter,cacheConfiguration);
            return cacheManager;
        }
    }
    

    序列化数据如下:

    k:"emp::3"
    
    v:
    {
      "@class": "cn.edu.ustc.springboot.bean.Employee",
      "id": 3,
      "lastName": "aaa",
      "email": "aaaa",
      "gender": 1,
      "dId": 5
    }
    

    【注意】:这里可以用GenericJackson2JsonRedisSerializer进行value的序列化解析,如果使用Jackson2JsonRedisSerializer,序列化的json没有"@class": "com.cwx.entity.Employee",在读取缓存时会报类型转换异常。

    • 基于API的Redis缓存实现
    package com.cwx.cache.service;
    import com.uos.cache.domain.Comment;
    import com.uos.cache.repository.CommentRepository;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.stereotype.Service;
    
    import java.util.Optional;
    import java.util.concurrent.TimeUnit;
    
    @Service
    public class ApiCommentService {
        @Autowired
        private RedisTemplate redisTemplate;
        @Autowired
        private CommentRepository commentRepository;
        public Comment findById(int comment_id){
          // 先从Redis缓存中查询数据
            Object object =  redisTemplate.opsForValue().get("comment_"+comment_id);
            if (object!=null){
                return (Comment)object;}else {
                Optional<Comment> optional = commentRepository.findById(comment_id);
                if(optional.isPresent()){Comment comment= optional.get();
                    // 将查询结果进行缓存,并设置有效期为1天
                    redisTemplate.opsForValue().set("comment_"+comment_id,
                            comment,1, TimeUnit.DAYS);return comment;
                }else {return null;}
            }
        }
        public Comment updateComment(Comment comment){
            commentRepository.updateComment(comment.getAuthor(), comment.getaId());
            // 更新数据后进行缓存更新
            redisTemplate.opsForValue().set("comment_"+comment.getId(),comment);
            return comment;
        }
        public void deleteComment(int comment_id){
            commentRepository.deleteById(comment_id);
            // 删除数据后进行缓存删除
            redisTemplate.delete("comment_"+comment_id);
        }
    
    }
    
    • Redis缓存原理(源码分析)
      配置类RedisCacheConfiguration向容器中导入了其定制的RedisCacheManager,在默认的RedisCacheManager的配置中,是使用jdk序列化value值。
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass(RedisConnectionFactory.class)
    @AutoConfigureAfter(RedisAutoConfiguration.class)
    @ConditionalOnBean(RedisConnectionFactory.class)
    @ConditionalOnMissingBean(CacheManager.class)
    @Conditional(CacheCondition.class)
    class RedisCacheConfiguration {
        
        //向容器中导入RedisCacheManager
        @Bean
        RedisCacheManager cacheManager(CacheProperties cacheProperties, CacheManagerCustomizers cacheManagerCustomizers,
                ObjectProvider<org.springframework.data.redis.cache.RedisCacheConfiguration> redisCacheConfiguration,
                ObjectProvider<RedisCacheManagerBuilderCustomizer> redisCacheManagerBuilderCustomizers,
                RedisConnectionFactory redisConnectionFactory, ResourceLoader resourceLoader) {
            //使用determineConfiguration()的返回值生成RedisCacheManagerBuilder
            //调用了RedisCacheManagerBuilder的cacheDefaults()方法(见下一代码块)
            RedisCacheManagerBuilder builder = RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(
                    determineConfiguration(cacheProperties, redisCacheConfiguration, resourceLoader.getClassLoader()));
            List<String> cacheNames = cacheProperties.getCacheNames();
            if (!cacheNames.isEmpty()) {
                builder.initialCacheNames(new LinkedHashSet<>(cacheNames));
            }
            redisCacheManagerBuilderCustomizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
            //使用RedisCacheManagerBuilder的build()方法创建RedisCacheManager并进行定制操作
            return cacheManagerCustomizers.customize(builder.build());
        }
    
        
        private org.springframework.data.redis.cache.RedisCacheConfiguration determineConfiguration(
                CacheProperties cacheProperties,
                ObjectProvider<org.springframework.data.redis.cache.RedisCacheConfiguration> redisCacheConfiguration,
                ClassLoader classLoader) {
            //determineConfiguration()调用了createConfiguration()
            return redisCacheConfiguration.getIfAvailable(() -> createConfiguration(cacheProperties, classLoader));
        }
    
        
        //createConfiguration()定义了其序列化value的规则
        private org.springframework.data.redis.cache.RedisCacheConfiguration createConfiguration(
                CacheProperties cacheProperties, ClassLoader classLoader) {
            Redis redisProperties = cacheProperties.getRedis();
            org.springframework.data.redis.cache.RedisCacheConfiguration config = org.springframework.data.redis.cache.RedisCacheConfiguration
                    .defaultCacheConfig();
            //使用jdk序列化器对value进行序列化
            config = config.serializeValuesWith(
                    SerializationPair.fromSerializer(new JdkSerializationRedisSerializer(classLoader)));
            //设置properties文件中设置的各项属性
            if (redisProperties.getTimeToLive() != null) {
                config = config.entryTtl(redisProperties.getTimeToLive());
            }
            if (redisProperties.getKeyPrefix() != null) {
                config = config.prefixKeysWith(redisProperties.getKeyPrefix());
            }
            if (!redisProperties.isCacheNullValues()) {
                config = config.disableCachingNullValues();
            }
            if (!redisProperties.isUseKeyPrefix()) {
                config = config.disableKeyPrefix();
            }
            return config;
        }
    
    }
    

    RedisCacheManager的直接构造类,该类保存了配置类RedisCacheConfiguration,该配置在会传递给RedisCacheManager

    public static class RedisCacheManagerBuilder {
    
            private final RedisCacheWriter cacheWriter;
            //默认缓存配置使用RedisCacheConfiguration的默认配置
            //该默认配置缓存时默认将k按字符串存储,v按jdk序列化数据存储(见下一代码块)
            private RedisCacheConfiguration defaultCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
            private final Map<String, RedisCacheConfiguration> initialCaches = new LinkedHashMap<>();
            private boolean enableTransactions;
            boolean allowInFlightCacheCreation = true;
    
            private RedisCacheManagerBuilder(RedisCacheWriter cacheWriter) {
                this.cacheWriter = cacheWriter;
            }
    
            
            //传入RedisCacheManagerBuilder使用的缓存配置规则RedisCacheConfiguration类
            public RedisCacheManagerBuilder cacheDefaults(RedisCacheConfiguration defaultCacheConfiguration) {
    
                Assert.notNull(defaultCacheConfiguration, "DefaultCacheConfiguration must not be null!");
    
                this.defaultCacheConfiguration = defaultCacheConfiguration;
    
                return this;
            }
        
        
        //使用默认defaultCacheConfiguration创建RedisCacheManager
        public RedisCacheManager build() {
    
                RedisCacheManager cm = new RedisCacheManager(cacheWriter, defaultCacheConfiguration, initialCaches,
                        allowInFlightCacheCreation);
    
                cm.setTransactionAware(enableTransactions);
    
                return cm;
            }
    

    RedisCacheConfiguration保存了许多缓存规则,这些规则都保存在RedisCacheManagerBuilder的RedisCacheConfiguration defaultCacheConfiguration属性中,并且当RedisCacheManagerBuilder创建RedisCacheManager传递过去。

    public class RedisCacheConfiguration {
    
        private final Duration ttl;
        private final boolean cacheNullValues;
        private final CacheKeyPrefix keyPrefix;
        private final boolean usePrefix;
    
        private final SerializationPair<String> keySerializationPair;
        private final SerializationPair<Object> valueSerializationPair;
    
        private final ConversionService conversionService;
        
        //默认缓存配置
        public static RedisCacheConfiguration defaultCacheConfig(@Nullable ClassLoader classLoader) {
    
                DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
    
                registerDefaultConverters(conversionService);
    
                return new RedisCacheConfiguration(Duration.ZERO, true, true, CacheKeyPrefix.simple(),
                                         SerializationPair.fromSerializer(RedisSerializer.string()),//key使用字符串
                                                   SerializationPair.fromSerializer(RedisSerializer.java(classLoader)), conversionService);
            //value按jdk序列化存储
        }
    

    RedisCacheManager在创建RedisCache时将RedisCacheConfiguration传递过去,并在创建RedisCache时通过createRedisCache()起作用。

    public class RedisCacheManager extends AbstractTransactionSupportingCacheManager {
    
        private final RedisCacheWriter cacheWriter;
        private final RedisCacheConfiguration defaultCacheConfig;
        private final Map<String, RedisCacheConfiguration> initialCacheConfiguration;
        private final boolean allowInFlightCacheCreation;
        
            protected RedisCache createRedisCache(String name, @Nullable RedisCacheConfiguration cacheConfig) {
            //如果调用该方法时RedisCacheConfiguration有值则使用定制的,否则则使用默认的RedisCacheConfiguration defaultCacheConfig,即RedisCacheManagerBuilder传递过来的配置
            return new RedisCache(name, cacheWriter, cacheConfig != null ? cacheConfig : defaultCacheConfig);
        }
    

    RedisCache,Redis缓存,具体负责将缓存数据序列化的地方,将RedisCacheConfiguration的序列化对SerializationPair提取出来并使用其定义的序列化方式分别对k和v进行序列化操作。

    public class RedisCache extends AbstractValueAdaptingCache {
       
       private static final byte[] BINARY_NULL_VALUE = RedisSerializer.java().serialize(NullValue.INSTANCE);
    
       private final String name;
       private final RedisCacheWriter cacheWriter;
       private final RedisCacheConfiguration cacheConfig;
       private final ConversionService conversionService;
       
       public void put(Object key, @Nullable Object value) {
    
           Object cacheValue = preProcessCacheValue(value);
    
           if (!isAllowNullValues() && cacheValue == null) {
    
               throw new IllegalArgumentException(String.format(
                       "Cache '%s' does not allow 'null' values. Avoid storing null via '@Cacheable(unless=\"#result == null\")' or configure RedisCache to allow 'null' via RedisCacheConfiguration.",
                       name));
           }
    
           //在put k-v时使用cacheConfig中的k-v序列化器分别对k-v进行序列化
           cacheWriter.put(name, createAndConvertCacheKey(key), serializeCacheValue(cacheValue), cacheConfig.getTtl());
       }
       
       //从cacheConfig(即RedisCacheConfiguration)中获取KeySerializationPair并写入key值
       protected byte[] serializeCacheKey(String cacheKey) {
           return ByteUtils.getBytes(cacheConfig.getKeySerializationPair().write(cacheKey));
       }
       
       
       //从cacheConfig(即RedisCacheConfiguration)中获取ValueSerializationPair并写入key值
       protected byte[] serializeCacheValue(Object value) {
    
           if (isAllowNullValues() && value instanceof NullValue) {
               return BINARY_NULL_VALUE;
           }
    
           return ByteUtils.getBytes(cacheConfig.getValueSerializationPair().write(value));
       }
    

    分析到这也就不难理解,要使用json保存序列化数据时,需要自定义RedisCacheManager,在RedisCacheConfiguration中定义序列化转化规则,并向RedisCacheManager传入我们自己定制的RedisCacheConfiguration了,我定制的序列化规则会跟随RedisCacheConfiguration一直传递到RedisCache,并在序列化时发挥作用。

    相关文章

      网友评论

          本文标题:Java分布式缓存之Redis

          本文链接:https://www.haomeiwen.com/subject/npztwhtx.html