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

本文涉及的产品
云数据库 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
相关文章
|
1月前
|
缓存 算法 Java
Caffeine Cache~高性能 Java 本地缓存之王
Caffeine Cache~高性能 Java 本地缓存之王
54 1
|
26天前
|
存储 XML 缓存
【深入浅出Spring原理及实战】「缓存Cache开发系列」带你深入分析Spring所提供的缓存Cache功能的开发实战指南(一)
【深入浅出Spring原理及实战】「缓存Cache开发系列」带你深入分析Spring所提供的缓存Cache功能的开发实战指南
60 0
|
1月前
|
SpringCloudAlibaba Java 持续交付
【构建一套Spring Cloud项目的大概步骤】&【Springcloud Alibaba微服务分布式架构学习资料】
【构建一套Spring Cloud项目的大概步骤】&【Springcloud Alibaba微服务分布式架构学习资料】
145 0
|
1月前
|
SpringCloudAlibaba Java 网络架构
【Springcloud Alibaba微服务分布式架构 | Spring Cloud】之学习笔记(七)Spring Cloud Gateway服务网关
【Springcloud Alibaba微服务分布式架构 | Spring Cloud】之学习笔记(七)Spring Cloud Gateway服务网关
98 0
|
28天前
|
敏捷开发 监控 前端开发
Spring+SpringMVC+Mybatis的分布式敏捷开发系统架构
Spring+SpringMVC+Mybatis的分布式敏捷开发系统架构
63 0
|
1月前
|
消息中间件 SpringCloudAlibaba Java
【Springcloud Alibaba微服务分布式架构 | Spring Cloud】之学习笔记(八)Config服务配置+bus消息总线+stream消息驱动+Sleuth链路追踪
【Springcloud Alibaba微服务分布式架构 | Spring Cloud】之学习笔记(八)Config服务配置+bus消息总线+stream消息驱动+Sleuth链路追踪
783 0
|
1月前
|
SpringCloudAlibaba 负载均衡 Java
【Springcloud Alibaba微服务分布式架构 | Spring Cloud】之学习笔记(五)OpenFeign的使用
【Springcloud Alibaba微服务分布式架构 | Spring Cloud】之学习笔记(五)OpenFeign的使用
42 0
|
1月前
|
缓存 NoSQL Java
spring cache整合redis实现springboot项目中的缓存功能
spring cache整合redis实现springboot项目中的缓存功能
46 1
|
1月前
|
存储 NoSQL Java
[Redis]——Spring整合Redis(SpringDataRedis)
[Redis]——Spring整合Redis(SpringDataRedis)
|
1月前
|
Java 数据库 Spring
如何使用Spring Data JPA完成审计功能
如何使用Spring Data JPA完成审计功能