一、Spring Cache整合分布式缓存redis(Lettuce)

1、依赖包

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>        
    <version>2.2.2.RELEASE</version>
</dependency>
<dependency>
  <groupId>io.lettuce</groupId>
  <artifactId>lettuce-core</artifactId>
  <version>5.2.1.RELEASE</version>
</dependency>

2、redis配置

方式一:

连接工厂

//@EnableCaching
@Configuration
public class CacheConfig extends CachingConfigurerSupport {

    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        // RedisStandaloneConfiguration这个配置类是Spring Data Redis2.0后才有的~~~
        RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration();
        configuration.setHostName("10.102.132.150");
        //configuration.setPassword(RedisPassword.of("123456"));
        configuration.setPort(6379);
        configuration.setDatabase(0);


        LettuceConnectionFactory factory = new LettuceConnectionFactory(configuration);
        // Spring Data Redis1.x这么来设置  2.0后建议使用RedisStandaloneConfiguration来取代
        //factory.setHostName("10.102.132.150");
        //factory.setPassword("123456");
        //factory.setPort(6379);
        //factory.setDatabase(0);
        return factory;
    }

    @Bean
    public RedisTemplate<String, String> stringRedisTemplate() {
        RedisTemplate<String, String> redisTemplate = new StringRedisTemplate();
        redisTemplate.setConnectionFactory(redisConnectionFactory());
        return redisTemplate;
    }

}

方式二:采用自动配置,yml文件配置连接信息

spring:
  redis:
    #    cluster:
    # Comma-separated list of "host:port" pairs to bootstrap from.
    #      nodes:
    #      max-redirects:
    database: 0
    host: 127.0.0.1
    #Connection URL. Overrides host, port, and password. User is ignored. Example: redis://user:password@example.com:6379
    #    url:
    port: 6379
    password:
    timeout:
    ssl: false
    lettuce:
      pool:
        max-active: 1000
        max-idle: 8
        max-wait: -1ms
        min-idle: 0

3、cacheManager、ttl、异常配置

@Slf4j
@Configuration
public class UspCacheAutoConfiguration extends CachingConfigurerSupport {

    @Autowired
    private ApplicationContext applicationContext;

    /**
     * 键默有效时间
     */
    private static final Duration TTL = Duration.ofMinutes(30);

    @Bean
    @Override
    public CacheErrorHandler errorHandler() {
        return new CacheErrorHandler() {

            @Override
            public void handleCacheGetError(RuntimeException exception, Cache cache, Object key) {
                log.error("redis异常: key=[{}]", key, exception);
            }

            @Override
            public void handleCachePutError(RuntimeException exception, Cache cache, Object key, Object value) {
                log.error("redis异常: key=[{}]", key, exception);
            }

            @Override
            public void handleCacheEvictError(RuntimeException exception, Cache cache, Object key) {
                log.error("redis异常: key=[{}]", key, exception);
            }

            @Override
            public void handleCacheClearError(RuntimeException exception, Cache cache) {
                log.error("redis异常:", exception);
            }
        };
    }

    @Bean
    public RedisCacheManager redisCacheManager(RedisConnectionFactory redisConnectionFactory) {
        return RedisCacheManager.builder(redisConnectionFactory)
                .initialCacheNames(cacheNames())
                .withInitialCacheConfigurations(redisCacheConfigurationMap())
                .build();
    }

    private Set<String> cacheNames() {
        Set<String> cacheSet = new HashSet<>();
        // 此处UspCacheDefinition为cache配置接口,包含擦车Name,TTL,prefix,序列化反序列化等
        applicationContext.getBeansOfType(UspCacheDefinition.class).forEach((key, uspCacheDefinition) -> cacheSet.add(uspCacheDefinition.getKey()));
        return cacheSet;
    }

    // TODO 待优化
    private Map<String, RedisCacheConfiguration> redisCacheConfigurationMap() {
        Map<String, RedisCacheConfiguration> redisCacheConfigurationMap = new HashMap<>();

        applicationContext.getBeansOfType(UspCacheDefinition.class).forEach((key, uspCacheDefinition) -> {
            RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
                    .entryTtl(uspCacheDefinition.getTimeToLive())
                    .disableCachingNullValues();

            if (uspCacheDefinition.getTimeToLive() != null) {
                redisCacheConfiguration = redisCacheConfiguration.entryTtl(uspCacheDefinition.getTimeToLive());
            }

            if (!"".equals(uspCacheDefinition.getPrefix()) && uspCacheDefinition.getPrefix() != null) {
                redisCacheConfiguration.usePrefix();
                redisCacheConfiguration = redisCacheConfiguration.prefixKeysWith(uspCacheDefinition.getPrefix());
            }

            if (uspCacheDefinition.getSerializeKey() != null) {
                redisCacheConfiguration = redisCacheConfiguration.serializeKeysWith(uspCacheDefinition.getSerializeKey());
            }

            if (uspCacheDefinition.getSerializeValue() != null) {
                redisCacheConfiguration = redisCacheConfiguration.serializeValuesWith(uspCacheDefinition.getSerializeValue());
            }

            redisCacheConfigurationMap.put(uspCacheDefinition.getKey(), redisCacheConfiguration);
        });

        return redisCacheConfigurationMap;
    }
}

4、配置类上开启缓存注解支持:@EnableCaching

5、RedisCacheManager中注意RedisCacheWriterRedisCacheConfiguration

public class RedisCacheManager extends AbstractTransactionSupportingCacheManager {

	private final RedisCacheWriter cacheWriter;
    // 这个配置类非常重要。能配置ttl、CacheKeyPrefix、ConversionService等等
	private final RedisCacheConfiguration defaultCacheConfig;
	private final Map<String, RedisCacheConfiguration> initialCacheConfiguration;
	private final boolean allowInFlightCacheCreation;

	private RedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration,
			boolean allowInFlightCacheCreation) {

		Assert.notNull(cacheWriter, "CacheWriter must not be null!");
		Assert.notNull(defaultCacheConfiguration, "DefaultCacheConfiguration must not be null!");

		this.cacheWriter = cacheWriter;
		this.defaultCacheConfig = defaultCacheConfiguration;
		this.initialCacheConfiguration = new LinkedHashMap<>();
		this.allowInFlightCacheCreation = allowInFlightCacheCreation;
	}

	
	public RedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration) {
		this(cacheWriter, defaultCacheConfiguration, true);
	}

	
	public RedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration,
			String... initialCacheNames) {

		this(cacheWriter, defaultCacheConfiguration, true, initialCacheNames);
	}

	
	public RedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration,
			boolean allowInFlightCacheCreation, String... initialCacheNames) {

		this(cacheWriter, defaultCacheConfiguration, allowInFlightCacheCreation);
        // 给每个Cache都放一个配置,默认都使用全局的defaultCacheConfiguration
		// 可见:它是支持给不同的Cache,给出不同的配置的(比如过期时间,,,等等等)
		for (String cacheName : initialCacheNames) {
			this.initialCacheConfiguration.put(cacheName, defaultCacheConfiguration);
		}
	}
    // 这里也可以自己把initialCacheConfigurations这个Map传进来(完成完全个性化)
	public RedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration,
			Map<String, RedisCacheConfiguration> initialCacheConfigurations) {

		this(cacheWriter, defaultCacheConfiguration, initialCacheConfigurations, true);
	}

	
	public RedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration,
			Map<String, RedisCacheConfiguration> initialCacheConfigurations, boolean allowInFlightCacheCreation) {

		this(cacheWriter, defaultCacheConfiguration, allowInFlightCacheCreation);

		Assert.notNull(initialCacheConfigurations, "InitialCacheConfigurations must not be null!");

		this.initialCacheConfiguration.putAll(initialCacheConfigurations);
	}

	
	public static RedisCacheManager create(RedisConnectionFactory connectionFactory) {

		Assert.notNull(connectionFactory, "ConnectionFactory must not be null!");

		return new RedisCacheManager(new DefaultRedisCacheWriter(connectionFactory),
				RedisCacheConfiguration.defaultCacheConfig());
	}

	...

	/**
	 * Configurator for creating {@link RedisCacheManager}.
	 */
	public static class RedisCacheManagerBuilder {

		...
		public RedisCacheManager build() {

			RedisCacheManager cm = new RedisCacheManager(cacheWriter, defaultCacheConfiguration, initialCaches,
					allowInFlightCacheCreation);

			cm.setTransactionAware(enableTransactions);

			return cm;
		}
	}
}

6、RedisCache是对Cache的实现
 

// @since 2.0   请注意:这里也是以2.0版本的为准的
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; // cache的配置(因为每个Cache的配置可能不一样,即使来自于同一个CacheManager)
	private final ConversionService conversionService; // 数据转换
	
	// 唯一构造函数,并且还是protected 的。可见我们自己并不能操控它
	protected RedisCache(String name, RedisCacheWriter cacheWriter, RedisCacheConfiguration cacheConfig) {

		super(cacheConfig.getAllowCacheNullValues());

		Assert.notNull(name, "Name must not be null!");
		Assert.notNull(cacheWriter, "CacheWriter must not be null!");
		Assert.notNull(cacheConfig, "CacheConfig must not be null!");

		this.name = name;
		this.cacheWriter = cacheWriter;
		this.cacheConfig = cacheConfig;
		this.conversionService = cacheConfig.getConversionService();
	}

	@Override
	public String getName() {
		return name;
	}
	@Override
	public RedisCacheWriter getNativeCache() {
		return this.cacheWriter;
	}

	// cacheWriter会有网络访问请求~~~去访问服务器
	@Override
	protected Object lookup(Object key) {
		byte[] value = cacheWriter.get(name, createAndConvertCacheKey(key));
		if (value == null) {
			return null;
		}
		return deserializeCacheValue(value);
	}

	@Override
	@Nullable
	public ValueWrapper get(Object key) {
		Object value = lookup(key);
		return toValueWrapper(value);
	}

	// 请注意:这个方法因为它要保证同步性,所以使用了synchronized 
	// 还记得我说过的sync=true这个属性吗,靠的就是它来保证的(当然在分布式情况下 不能百分百保证)
	@Override
	@SuppressWarnings("unchecked")
	public synchronized <T> T get(Object key, Callable<T> valueLoader) {
		ValueWrapper result = get(key);
		if (result != null) { // 缓存里有,就直接返回吧
			return (T) result.get();
		}
	
		// 缓存里没有,那就从valueLoader里拿值,拿到后马上put进去
		T value = valueFromLoader(key, valueLoader);
		put(key, value); 
		return value;
	}


	@Override
	public void put(Object key, @Nullable Object value) {
		Object cacheValue = preProcessCacheValue(value);

		// 对null值的判断,看看是否允许存储它呢~~~
		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));
		}
		cacheWriter.put(name, createAndConvertCacheKey(key), serializeCacheValue(cacheValue), cacheConfig.getTtl());
	}

	// @since 4.1
	@Override
	public ValueWrapper putIfAbsent(Object key, @Nullable Object value) {
	...
	}

	@Override
	public void evict(Object key) {
		cacheWriter.remove(name, createAndConvertCacheKey(key));
	}

	// 清空:在redis中慎用。因为它是传一个*去  进行全部删除的。
	@Override
	public void clear() {
		byte[] pattern = conversionService.convert(createCacheKey("*"), byte[].class);
		cacheWriter.clean(name, pattern);
	}

	public RedisCacheConfiguration getCacheConfiguration() {
		return cacheConfig;
	}
	// 序列化key、value   ByteUtils为redis包的工具类,Data Redis 1.7
	protected byte[] serializeCacheKey(String cacheKey) {
		return ByteUtils.getBytes(cacheConfig.getKeySerializationPair().write(cacheKey));
	}
	protected byte[] serializeCacheValue(Object value) {
		if (isAllowNullValues() && value instanceof NullValue) {
			return BINARY_NULL_VALUE;
		}
		return ByteUtils.getBytes(cacheConfig.getValueSerializationPair().write(value));
	}

	protected String createCacheKey(Object key) {

		String convertedKey = convertKey(key);

		if (!cacheConfig.usePrefix()) {
			return convertedKey;
		}

		return prefixCacheKey(convertedKey);
	}
	... 
}

RedisCache持有RedisCacheWriter的引用,所有的对Redis服务的操作都是委托给了它,同时它还持有RedisCacheConfiguration和ConversionService

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值