玩转Spring Cache --- 整合分布式缓存Redis Cache(使用Lettuce、使用Spring Data Redis)【享学Spring】(中)

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 玩转Spring Cache --- 整合分布式缓存Redis Cache(使用Lettuce、使用Spring Data Redis)【享学Spring】(中)

Redis和Spring Cache整合


Redis和Spring Cache整合,让能通过缓存注解优雅的操作Redis是本文的主菜。


因为Redis分布式缓存它是client/server模式,所以它的整合和前面整合Ehcache等还是有些不一样的地方的 。但在有了上篇文章做铺垫,加上上面介绍Spring Data Redis的使用之后,要集成它也是易如反掌之事。


RedisCacheManager

老规矩,整合前先看看Redis对CacheManager接口的实现RedisCacheManager:


说明:Spring Data Redis2.x对此类进行了大幅的重写,除了类名没变,内容完全重写了。

此处我给出这张对比图,希望小伙伴们也能做到心中有数。(本文以2.x版本为例)


image.png


// @since 2.0  这里面用到了2.0提供的 RedisCacheConfiguration和RedisCacheWriter等
public class RedisCacheManager extends AbstractTransactionSupportingCacheManager {
  // 这两个哥们都是不能为null的~~~
  private final RedisCacheWriter cacheWriter;
  // 这个配置类非常重要。能配置ttl、CacheKeyPrefix、ConversionService等等等等
  // 可以用链式操作进行构造~~~
  private final RedisCacheConfiguration defaultCacheConfig;
  private final Map<String, RedisCacheConfiguration> initialCacheConfiguration;、
  // allow create unconfigured caches
  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);
  }
  ...
  // 根据RedisConnectionFactory 直接new一个RedisCacheManager出来,都采用默认的方案
  public static RedisCacheManager create(RedisConnectionFactory connectionFactory) {
    Assert.notNull(connectionFactory, "ConnectionFactory must not be null!");
    return new RedisCacheManager(new DefaultRedisCacheWriter(connectionFactory), RedisCacheConfiguration.defaultCacheConfig());
  }
  //builder默认来构造、设置更加详细的参数设置
  // 注意这个builder,既能够根据RedisConnectionFactory生成,也能个根据`RedisCacheWriter`直接生成
  public static RedisCacheManagerBuilder builder(RedisConnectionFactory connectionFactory) {
    Assert.notNull(connectionFactory, "ConnectionFactory must not be null!");
    return RedisCacheManagerBuilder.fromConnectionFactory(connectionFactory);
  }
  public static RedisCacheManagerBuilder builder(RedisCacheWriter cacheWriter) {
    Assert.notNull(cacheWriter, "CacheWriter must not be null!");
    return RedisCacheManagerBuilder.fromCacheWriter(cacheWriter);
  }
  // 实现抽象父类的抽象方法,把指定的缓存Cache都加载进来~ 
  // 若没有指定,这里木有啦~
  @Override
  protected Collection<RedisCache> loadCaches() {
    List<RedisCache> caches = new LinkedList<>();
    for (Map.Entry<String, RedisCacheConfiguration> entry : initialCacheConfiguration.entrySet()) {
      caches.add(createRedisCache(entry.getKey(), entry.getValue()));
    }
    return caches;
  }
  // 此处使用的是RedisCache,它其实里面依赖的是RedisCacheWriter(1.x版本传入的是RedisOperations,也就是RedisTemplate)
  protected RedisCache createRedisCache(String name, @Nullable RedisCacheConfiguration cacheConfig) {
    return new RedisCache(name, cacheWriter, cacheConfig != null ? cacheConfig : defaultCacheConfig);
  }
  // 简单的说,默认下RedisCache也是允许动态生成的~~~~
  // 显然,默认生成的(并非自己指定的),只能使用默认配置defaultCacheConfig了
  @Override
  protected RedisCache getMissingCache(String name) {
    return allowInFlightCacheCreation ? createRedisCache(name, defaultCacheConfig) : null;
  }
  public Map<String, RedisCacheConfiguration> getCacheConfigurations() {
  ... // 获取到全部的Cache的配置(包括只传了CacheNames的情况)  它的key就是所有的cacheNames
  }
  // builder模式,详细代码省略~  
  public static class RedisCacheManagerBuilder {
    ...
    public RedisCacheManager build() {
      RedisCacheManager cm = new RedisCacheManager(cacheWriter, defaultCacheConfiguration, initialCaches, allowInFlightCacheCreation);
      // 是否允许事务:默认是false
      cm.setTransactionAware(enableTransactions);
      return cm;
    }
  }
}


这个实现看着还是非常简单的,因为它有2.0提供的两个新的类:RedisCacheWriter和RedisCacheConfiguration进行分而治之。


RedisCache

很显然,RedisCache是对Cache抽象的实现:


同样的,1.x和2.x对此类的实现完全不一样,此处就不贴图了~


// @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));
  }
  // 创建一个key,请注意这里prefixCacheKey() 前缀生效了~~~
  protected String createCacheKey(Object key) {
    String convertedKey = convertKey(key);
    if (!cacheConfig.usePrefix()) {
      return convertedKey;
    }
    return prefixCacheKey(convertedKey);
  }
  ... 
}


RedisCache它持有RedisCacheWriter的引用,所以的对Redis服务的操作都是委托给了它。同时它还持有RedisCacheConfiguration和ConversionService,证明即使是同一个CacheManager管理的缓存实例,配置都可能是不一样的,支持到了很强的个性化。

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
3月前
|
NoSQL Java Redis
太惨痛: Redis 分布式锁 5个大坑,又大又深, 如何才能 避开 ?
Redis分布式锁在高并发场景下是重要的技术手段,但其实现过程中常遇到五大深坑:**原子性问题**、**连接耗尽问题**、**锁过期问题**、**锁失效问题**以及**锁分段问题**。这些问题不仅影响系统的稳定性和性能,还可能导致数据不一致。尼恩在实际项目中总结了这些坑,并提供了详细的解决方案,包括使用Lua脚本保证原子性、设置合理的锁过期时间和使用看门狗机制、以及通过锁分段提升性能。这些经验和技巧对面试和实际开发都有很大帮助,值得深入学习和实践。
太惨痛: Redis 分布式锁 5个大坑,又大又深, 如何才能 避开 ?
|
1月前
|
存储 NoSQL Java
使用lock4j-redis-template-spring-boot-starter实现redis分布式锁
通过使用 `lock4j-redis-template-spring-boot-starter`,我们可以轻松实现 Redis 分布式锁,从而解决分布式系统中多个实例并发访问共享资源的问题。合理配置和使用分布式锁,可以有效提高系统的稳定性和数据的一致性。希望本文对你在实际项目中使用 Redis 分布式锁有所帮助。
119 5
|
2月前
|
NoSQL Java 数据处理
基于Redis海量数据场景分布式ID架构实践
【11月更文挑战第30天】在现代分布式系统中,生成全局唯一的ID是一个常见且重要的需求。在微服务架构中,各个服务可能需要生成唯一标识符,如用户ID、订单ID等。传统的自增ID已经无法满足在集群环境下保持唯一性的要求,而分布式ID解决方案能够确保即使在多个实例间也能生成全局唯一的标识符。本文将深入探讨如何利用Redis实现分布式ID生成,并通过Java语言展示多个示例,同时分析每个实践方案的优缺点。
74 8
|
1月前
|
缓存 NoSQL Java
Spring Boot中的分布式缓存方案
Spring Boot提供了简便的方式来集成和使用分布式缓存。通过Redis和Memcached等缓存方案,可以显著提升应用的性能和扩展性。合理配置和优化缓存策略,可以有效避免常见的缓存问题,保证系统的稳定性和高效运行。
50 3
|
1月前
|
缓存 Java 数据库连接
深入探讨:Spring与MyBatis中的连接池与缓存机制
Spring 与 MyBatis 提供了强大的连接池和缓存机制,通过合理配置和使用这些机制,可以显著提升应用的性能和可扩展性。连接池通过复用数据库连接减少了连接创建和销毁的开销,而 MyBatis 的一级缓存和二级缓存则通过缓存查询结果减少了数据库访问次数。在实际应用中,结合具体的业务需求和系统架构,优化连接池和缓存的配置,是提升系统性能的重要手段。
66 4
|
2月前
|
存储 Java 关系型数据库
在Spring Boot中整合Seata框架实现分布式事务
可以在 Spring Boot 中成功整合 Seata 框架,实现分布式事务的管理和处理。在实际应用中,还需要根据具体的业务需求和技术架构进行进一步的优化和调整。同时,要注意处理各种可能出现的问题,以保障分布式事务的顺利执行。
101 6
|
2月前
|
NoSQL Redis
Redis分布式锁如何实现 ?
Redis分布式锁通过SETNX指令实现,确保仅在键不存在时设置值。此机制用于控制多个线程对共享资源的访问,避免并发冲突。然而,实际应用中需解决死锁、锁超时、归一化、可重入及阻塞等问题,以确保系统的稳定性和可靠性。解决方案包括设置锁超时、引入Watch Dog机制、使用ThreadLocal绑定加解锁操作、实现计数器支持可重入锁以及采用自旋锁思想处理阻塞请求。
64 16
|
2月前
|
缓存 NoSQL PHP
Redis作为PHP缓存解决方案的优势、实现方式及注意事项。Redis凭借其高性能、丰富的数据结构、数据持久化和分布式支持等特点,在提升应用响应速度和处理能力方面表现突出
本文深入探讨了Redis作为PHP缓存解决方案的优势、实现方式及注意事项。Redis凭借其高性能、丰富的数据结构、数据持久化和分布式支持等特点,在提升应用响应速度和处理能力方面表现突出。文章还介绍了Redis在页面缓存、数据缓存和会话缓存等应用场景中的使用,并强调了缓存数据一致性、过期时间设置、容量控制和安全问题的重要性。
46 5
|
3月前
|
缓存 NoSQL Java
大数据-50 Redis 分布式锁 乐观锁 Watch SETNX Lua Redisson分布式锁 Java实现分布式锁
大数据-50 Redis 分布式锁 乐观锁 Watch SETNX Lua Redisson分布式锁 Java实现分布式锁
80 3
大数据-50 Redis 分布式锁 乐观锁 Watch SETNX Lua Redisson分布式锁 Java实现分布式锁
|
2月前
|
存储 缓存 Java
Spring缓存注解【@Cacheable、@CachePut、@CacheEvict、@Caching、@CacheConfig】使用及注意事项
Spring缓存注解【@Cacheable、@CachePut、@CacheEvict、@Caching、@CacheConfig】使用及注意事项
368 2