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

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容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管理的缓存实例,配置都可能是不一样的,支持到了很强的个性化。

相关文章
|
2月前
|
存储 负载均衡 NoSQL
【赵渝强老师】Redis Cluster分布式集群
Redis Cluster是Redis的分布式存储解决方案,通过哈希槽(slot)实现数据分片,支持水平扩展,具备高可用性和负载均衡能力,适用于大规模数据场景。
216 2
|
1月前
|
存储 安全 Java
管理 Spring 微服务中的分布式会话
在微服务架构中,管理分布式会话是确保用户体验一致性和系统可扩展性的关键挑战。本文探讨了在 Spring 框架下实现分布式会话管理的多种方法,包括集中式会话存储和客户端会话存储(如 Cookie),并分析了它们的优缺点。同时,文章还涵盖了与分布式会话相关的安全考虑,如数据加密、令牌验证、安全 Cookie 政策以及服务间身份验证。此外,文中强调了分布式会话在提升系统可扩展性、增强可用性、实现数据一致性及优化资源利用方面的显著优势。通过合理选择会话管理策略,结合 Spring 提供的强大工具,开发人员可以在保证系统鲁棒性的同时,提供无缝的用户体验。
|
2月前
|
存储 缓存 NoSQL
【📕分布式锁通关指南 12】源码剖析redisson如何利用Redis数据结构实现Semaphore和CountDownLatch
本文解析 Redisson 如何通过 Redis 实现分布式信号量(RSemaphore)与倒数闩(RCountDownLatch),利用 Lua 脚本与原子操作保障分布式环境下的同步控制,帮助开发者更好地理解其原理与应用。
129 6
|
2月前
|
监控 Java API
Spring Boot 3.2 结合 Spring Cloud 微服务架构实操指南 现代分布式应用系统构建实战教程
Spring Boot 3.2 + Spring Cloud 2023.0 微服务架构实践摘要 本文基于Spring Boot 3.2.5和Spring Cloud 2023.0.1最新稳定版本,演示现代微服务架构的构建过程。主要内容包括: 技术栈选择:采用Spring Cloud Netflix Eureka 4.1.0作为服务注册中心,Resilience4j 2.1.0替代Hystrix实现熔断机制,配合OpenFeign和Gateway等组件。 核心实操步骤: 搭建Eureka注册中心服务 构建商品
408 3
|
3月前
|
存储 缓存 NoSQL
Redis核心数据结构与分布式锁实现详解
Redis 是高性能键值数据库,支持多种数据结构,如字符串、列表、集合、哈希、有序集合等,广泛用于缓存、消息队列和实时数据处理。本文详解其核心数据结构及分布式锁实现,帮助开发者提升系统性能与并发控制能力。
|
1月前
|
NoSQL Java 调度
分布式锁与分布式锁使用 Redis 和 Spring Boot 进行调度锁(不带 ShedLock)
分布式锁是分布式系统中用于同步多节点访问共享资源的机制,防止并发操作带来的冲突。本文介绍了基于Spring Boot和Redis实现分布式锁的技术方案,涵盖锁的获取与释放、Redis配置、服务调度及多实例运行等内容,通过Docker Compose搭建环境,验证了锁的有效性与互斥特性。
107 0
分布式锁与分布式锁使用 Redis 和 Spring Boot 进行调度锁(不带 ShedLock)
|
2月前
|
缓存 监控 Linux
Linux系统清理缓存(buff/cache)的有效方法。
总结而言,在大多数情形下你不必担心Linux中buffer与cache占用过多内存在影响到其他程序运行;因为当程序请求更多内存在没有足够可用资源时,Linux会自行调整其占有量。只有当你明确知道当前环境与需求并希望立即回收这部分资源给即将运行重负载任务之前才考虑上述方法去主动干预。
867 10
|
28天前
|
缓存 NoSQL 关系型数据库
Redis缓存和分布式锁
Redis 是一种高性能的键值存储系统,广泛用于缓存、消息队列和内存数据库。其典型应用包括缓解关系型数据库压力,通过缓存热点数据提高查询效率,支持高并发访问。此外,Redis 还可用于实现分布式锁,解决分布式系统中的资源竞争问题。文章还探讨了缓存的更新策略、缓存穿透与雪崩的解决方案,以及 Redlock 算法等关键技术。
|
3月前
|
NoSQL Redis
Lua脚本协助Redis分布式锁实现命令的原子性
利用Lua脚本确保Redis操作的原子性是分布式锁安全性的关键所在,可以大幅减少由于网络分区、客户端故障等导致的锁无法正确释放的情况,从而在分布式系统中保证数据操作的安全性和一致性。在将这些概念应用于生产环境前,建议深入理解Redis事务与Lua脚本的工作原理以及分布式锁的可能问题和解决方案。
147 8
|
3月前
|
NoSQL Java Redis
Redis基本数据类型及Spring Data Redis应用
Redis 是开源高性能键值对数据库,支持 String、Hash、List、Set、Sorted Set 等数据结构,适用于缓存、消息队列、排行榜等场景。具备高性能、原子操作及丰富功能,是分布式系统核心组件。
422 2