springboot2.4.0搭建redis环境&使用RedisTemplate&序列化规则&Redis缓存原理-笔记

笔记

搭建redis环境以及缓存使用及其原理的笔记。



一、搭建redis环境

本机使用的redis是虚拟机中用docker创建的redis镜像。
在这里插入图片描述
使用Redis Desktop Manager连接redis操作数据。

在pom文件中引入redis启动器:

		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

在application.properties全局配置文件中绑定端口和地址:

spring.redis.host=192.168.153.128
spring.redis.port=6380

二、使用RedisTemplate

首先需要了解redis的命令,先熟悉redis的命令组。 --> redis命令中心 : redis命令中心

Spring-Data-Redis

Spring-data-redis是spring大家族的一部分,提供了在srping应用中通过简单的配置访问redis服务,对reids底层开发包(Jedis, JRedis, and RJC)进行了高度封装,RedisTemplate提供了redis各种操作、异常处理及序列化,支持发布订阅,并对spring 3.1 cache进行了实现。RedisTemplate则是一个高度封装redis操作的类。

redisTemplate的操作可以参照这几篇文章,其实其中的方法都是与redis命令相对应。
RedisTemplate操作Redis,这一篇文章就够了(一)
RedisTemplate常用方法总结

RedisTemplate

RedisAutoConfiguration向容器中导入了两个类RedisTemplate<Object, Object> redisTemplate和StringRedisTemplate,作为Redis客户端分别操作k-v都为对象和k-v都为字符串的值。
在这里插入图片描述

Redis常见的五大数据类型

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

使用stringRedisTemplate操作,与RedisTemplate同理。

​ stringRedisTemplate.opsForValue()[String(字符串)]

​ stringRedisTemplate.opsForList()[List(列表)]

​ stringRedisTemplate.opsForSet()[Set(集合)]

​ stringRedisTemplate.opsForHash()[Hash(散列)]

​ stringRedisTemplate.opsForZSet()[ZSet(有序集合)]

三、Redis缓存使用

jdk默认序列化机制

在导入redis依赖后,org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration类就会自动生效,创建RedisCacheManager,并使用RedisCache进行缓存数据,要缓存的对象的类要实现Serializable序列化接口

默认如果保存对象,使用jdk序列化机制,序列化后的数据保存到redis中。

	/**
     * 测试保存对象
     */
    @Test
    public void test02() {
        // 默认如果保存对象,使用jdk序列化机制,序列化后的数据保存到redis中
        redisTemplate.opsForValue().set("emp01", employeeMapper.getEmpById(1));
        
    }

redis中的数据显示:

key : \xAC\xED\x00\x05t\x00\x05emp01
value : \xAC\xED\x00\x05sr\x00"xiaojiang.springboot.bean.Employee\x97\x9F\xCC\xFBc/#\x05\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\x01pq\x00~\x00\x06q\x00~\x00\x06t\x00\x0Axiaopangzi

JdkSerializationRedisSerializer :POJO对象的存取场景,使用JDK本身序列化机制,将pojo类通过ObjectInputStream/ObjectOutputStream进行序列化操作,最终redis-server中将存储字节序列。是目前最常用的序列化策略。

json数据形式存储 - 自定义CacheManager

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

注意,这里必须用GenericJackson2JsonRedisSerializer进行value的序列化解析,如果使用Jackson2JsonRedisSerializer,序列化的json没有"@class": “cn.edu.ustc.springboot.bean.Employee”,在读取缓存时会报类型转换异常

@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;
    }
}
key:"emp-01"

value:
{
  "@class": "xiaojiang.springboot.bean.Employee",
  "id": 1,
  "lastName": "xiaopangzi",
  "email": null,
  "gender": 1,
  "dId": 1
}

JacksonJsonRedisSerializer :jackson-json工具提供了javabean与json之间的转换能力,可以将pojo实例序列化成json格式存储在redis中,也可以将json格式的数据转换成pojo实例。因为jackson工具在序列化和反序列化时,需要明确指定Class类型,因此此策略封装起来稍微复杂。【需要jackson-mapper-asl工具支持】

四、Redis缓存原理

我们从自动配置类说起吧。

RedisAutoConfiguration

Spring Boot关于Spring-Data-Redis的自动配置类。
该自动配置类检测到包spring-data-redis被使用时才应用。
并且导入了另外两个配置类LettuceConnectionConfiguration , JedisConnectionConfiguration 这两个配置类是用于配置底层Redis连接组件RedisConnectionFactory,一种基于Lettuce Redis客户端实现,一种基于Jedis Redis客户端实现,不会同时生效。因为包spring-boot-starter-data-redis自身依赖lettuce,所以缺省情况下,LettuceConnectionConfiguration会生效,JedisConnectionConfiguration不生效。

简而言之,就是导入了两种连接方式,默认使用的是Lettuce连接redis。

Jedis
Jedis在实现上是直接连接的redis server,如果在多线程环境下是非线程安全的,这个时候只有使用连接池,为每个Jedis实例增加物理连接

Lettuce
Lettuce的连接是基于Netty的,连接实例(StatefulRedisConnection)可以在多个线程间并发访问,应为StatefulRedisConnection是线程安全的,所以一个连接实例(StatefulRedisConnection)就可以满足多线程环境下的并发访问,当然这个也是可伸缩的设计,一个连接实例不够的情况也可以按需增加连接实例。
lettuce主要利用netty实现与redis的同步和异步通信。

RedisAutoConfiguration自身主要的作用是确保以下bean存在于容器中 :

  • RedisTemplate – 基于容器中的redisConnectionFactory
  • StringRedisTemplate – 基于容器中的redisConnectionFactory
@Configuration(
    proxyBeanMethods = false
)
@ConditionalOnClass({RedisOperations.class})
@EnableConfigurationProperties({RedisProperties.class})
@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
public class 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) {
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }
}

CacheAutoConfiguration

这个自动配置类的类头import了CacheAutoConfiguration.CacheConfigurationImportSelector.class,CacheConfigurationImportSelector通过selectImports方法,返回多个缓存配置类,中间通过某种规则定义优先级,当导入spring-data-redis时,RedisCacheConfiguration将被配对成功。

org.springframework.boot.autoconfigure.cache.GenericCacheConfiguration
org.springframework.boot.autoconfigure.cache.JCacheCacheConfiguration
org.springframework.boot.autoconfigure.cache.EhCacheCacheConfiguration
org.springframework.boot.autoconfigure.cache.HazelcastCacheConfiguration
org.springframework.boot.autoconfigure.cache.InfinispanCacheConfiguration
org.springframework.boot.autoconfigure.cache.CouchbaseCacheConfiguration
org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration
org.springframework.boot.autoconfigure.cache.CaffeineCacheConfiguration
org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration 【默认】
org.springframework.boot.autoconfigure.cache.NoOpCacheConfiguration
@Configuration(
    proxyBeanMethods = false
)
@ConditionalOnClass({CacheManager.class})
@ConditionalOnBean({CacheAspectSupport.class})
@ConditionalOnMissingBean(
    value = {CacheManager.class},
    name = {"cacheResolver"}
)
@EnableConfigurationProperties({CacheProperties.class})
@AutoConfigureAfter({CouchbaseDataAutoConfiguration.class, HazelcastAutoConfiguration.class, HibernateJpaAutoConfiguration.class, RedisAutoConfiguration.class})
@Import({CacheAutoConfiguration.CacheConfigurationImportSelector.class, CacheAutoConfiguration.CacheManagerEntityManagerFactoryDependsOnPostProcessor.class})
public class CacheAutoConfiguration {
	...

	static class CacheConfigurationImportSelector implements ImportSelector {
		CacheConfigurationImportSelector() {
		}
	
		public String[] selectImports(AnnotationMetadata importingClassMetadata) {
		    CacheType[] types = CacheType.values();
		    String[] imports = new String[types.length];
		
		    for(int i = 0; i < types.length; ++i) {
		        imports[i] = CacheConfigurations.getConfigurationClass(types[i]);
		    }
		
		    return imports;
		}
	}
	
	...
	
}

RedisCacheConfiguration

RedisCacheConfiguration

配置类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 {
	RedisCacheConfiguration() {
    }
    
    //在容器中注册RedisCacheManager
	@Bean
    RedisCacheManager cacheManager(CacheProperties cacheProperties, CacheManagerCustomizers cacheManagerCustomizers, ObjectProvider<org.springframework.data.redis.cache.RedisCacheConfiguration> redisCacheConfiguration, ObjectProvider<RedisCacheManagerBuilderCustomizer> redisCacheManagerBuilderCustomizers, RedisConnectionFactory redisConnectionFactory, ResourceLoader resourceLoader) {
    	// 调用了RedisCacheManagerBuilder的cacheDefaults()方法
        RedisCacheManagerBuilder builder = RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(this.determineConfiguration(cacheProperties, redisCacheConfiguration, resourceLoader.getClassLoader()));
        List<String> cacheNames = cacheProperties.getCacheNames();
        if (!cacheNames.isEmpty()) {
            builder.initialCacheNames(new LinkedHashSet(cacheNames));
        }

        if (cacheProperties.getRedis().isEnableStatistics()) {
            builder.enableStatistics();
        }

        redisCacheManagerBuilderCustomizers.orderedStream().forEach((customizer) -> {
            customizer.customize(builder);
        });
        //使用RedisCacheManagerBuilder的build()方法创建RedisCacheManager并进行定制操作
        return (RedisCacheManager)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 (org.springframework.data.redis.cache.RedisCacheConfiguration)redisCacheConfiguration.getIfAvailable(() -> {
            return this.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.prefixCacheNameWith(redisProperties.getKeyPrefix());
        }

        if (!redisProperties.isCacheNullValues()) {
            config = config.disableCachingNullValues();
        }

        if (!redisProperties.isUseKeyPrefix()) {
            config = config.disableKeyPrefix();
        }

        return config;
    }

}
RedisCacheManager

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

public class RedisCacheManager extends AbstractTransactionSupportingCacheManager {
	private final RedisCacheWriter cacheWriter;
	//默认缓存配置使用RedisCacheConfiguration的默认配置
    //该默认配置缓存时默认将k按字符串存储,v按jdk序列化数据存储
    private final RedisCacheConfiguration defaultCacheConfig;
    private final Map<String, RedisCacheConfiguration> initialCacheConfiguration;
    private final boolean allowInFlightCacheCreation;
    
	...
    
    // 传入RedisCacheManagerBuilder使用的缓存配置规则RedisCacheConfiguration类
	public RedisCacheManager.RedisCacheManagerBuilder cacheDefaults(RedisCacheConfiguration defaultCacheConfiguration) {
	    Assert.notNull(defaultCacheConfiguration, "DefaultCacheConfiguration must not be null!");
	    this.defaultCacheConfiguration = defaultCacheConfiguration;
	    return this;
	}

	...
	
	// 使用默认defaultCacheConfiguration创建RedisCacheManager
	public RedisCacheManager build() {
	    Assert.state(this.cacheWriter != null, "CacheWriter must not be null! You can provide one via 'RedisCacheManagerBuilder#cacheWriter(RedisCacheWriter)'.");
	    RedisCacheWriter theCacheWriter = this.cacheWriter;
	    if (!this.statisticsCollector.equals(CacheStatisticsCollector.none())) {
	        theCacheWriter = this.cacheWriter.withStatisticsCollector(this.statisticsCollector);
	    }
	
	    RedisCacheManager cm = new RedisCacheManager(theCacheWriter, this.defaultCacheConfiguration, this.initialCaches, this.allowInFlightCacheCreation);
	    cm.setTransactionAware(this.enableTransactions);
	    return cm;
	}
}

简而言之,如下图所示。
在这里插入图片描述

RedisCacheConfiguration

此RedisCacheConfiguration为org.springframework.data.redis.cache.RedisCacheConfiguration
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() {
        return defaultCacheConfig((ClassLoader)null);
    }

	// 默认缓存配置
    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()), SerializationPair.fromSerializer(RedisSerializer.java(classLoader)), conversionService);
    }

我们来看defaultCacheConfig方法的返回值。

return new RedisCacheConfiguration(
Duration.ZERO,
true,
true,
CacheKeyPrefix.simple(),
SerializationPair.fromSerializer(RedisSerializer.string()), //定义了key使用字符串
SerializationPair.fromSerializer(RedisSerializer.java(classLoader)),
conversionService
); //value按jdk序列化存储

简而言之,如下图所示。

在这里插入图片描述

RedisCacheManager

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

public class RedisCacheManager extends AbstractTransactionSupportingCacheManager {

	...

	//加载缓存
	protected Collection<RedisCache> loadCaches() {
        List<RedisCache> caches = new LinkedList();
        Iterator var2 = this.initialCacheConfiguration.entrySet().iterator();

        while(var2.hasNext()) {
            Entry<String, RedisCacheConfiguration> entry = (Entry)var2.next();
            caches.add(this.createRedisCache((String)entry.getKey(), (RedisCacheConfiguration)entry.getValue()));
        }

        return caches;
    }

    protected RedisCache getMissingCache(String name) {
        return this.allowInFlightCacheCreation ? this.createRedisCache(name, this.defaultCacheConfig) : null;
    }

    public Map<String, RedisCacheConfiguration> getCacheConfigurations() {
        Map<String, RedisCacheConfiguration> configurationMap = new HashMap(this.getCacheNames().size());
        this.getCacheNames().forEach((it) -> {
            RedisCache cache = (RedisCache)RedisCache.class.cast(this.lookupCache(it));
            configurationMap.put(it, cache != null ? cache.getCacheConfiguration() : null);
        });
        return Collections.unmodifiableMap(configurationMap);
    }

	// 创建 RedisCache 缓存
    protected RedisCache createRedisCache(String name, @Nullable RedisCacheConfiguration cacheConfig) {
    	//如果调用该方法时RedisCacheConfiguration有值则使用定制的,否则则使用默认的RedisCacheConfiguration defaultCacheConfig,即RedisCacheManagerBuilder传递过来的配置
        return new RedisCache(name, this.cacheWriter, cacheConfig != null ? cacheConfig : this.defaultCacheConfig);
    }

	...
	
}
RedisCache

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

public class RedisCache extends AbstractValueAdaptingCache {
    private static final byte[] BINARY_NULL_VALUE;
    private final String name;
    private final RedisCacheWriter cacheWriter;
    private final RedisCacheConfiguration cacheConfig;
    private final ConversionService conversionService;

	...
	
	// 查找
	protected Object lookup(Object key) {
        byte[] value = this.cacheWriter.get(this.name, this.createAndConvertCacheKey(key));
        return value == null ? null : this.deserializeCacheValue(value);
    }
    
    public void put(Object key, @Nullable Object value) {
        Object cacheValue = this.preProcessCacheValue(value);
        if (!this.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.", this.name));
        } else {
        	//在put k-v时使用cacheConfig中的k-v序列化器分别对k-v进行序列化
            this.cacheWriter.put(this.name, this.createAndConvertCacheKey(key), this.serializeCacheValue(cacheValue), this.cacheConfig.getTtl());
        }
    }
    
    //从cacheConfig(即RedisCacheConfiguration)中获取KeySerializationPair并写入key值
    protected byte[] serializeCacheKey(String cacheKey) {
        return ByteUtils.getBytes(this.cacheConfig.getKeySerializationPair().write(cacheKey));
    }
    
    //从cacheConfig(即RedisCacheConfiguration)中获取ValueSerializationPair并写入key值
    protected byte[] serializeCacheValue(Object value) {
        return this.isAllowNullValues() && value instanceof NullValue ? BINARY_NULL_VALUE : ByteUtils.getBytes(this.cacheConfig.getValueSerializationPair().write(value));
    }
    
}

五、总结

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

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

舍其小伙伴

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值