SpringCloudGateway关于Cache的警告你关注过吗

2,451

最近更新了SpringCloudGateway到3.1.1版本,发现问题真的是不少,今天又发现了新的问题。

问题描述

今早像往常一样启动了SpringCloudGateway 3.1.1版本,不经意发现在控制台报出了如下的警告:

Spring Cloud LoadBalancer is currently working with the default cache. While this cache implementation is useful for development and tests, it's recommended to use Caffeine cache in production.You can switch to using Caffeine cache, by adding it and org.springframework.cache.caffeine.CaffeineCacheManager to the classpath.

大致意思就是说:Spring Cloud LoadBalancer 正在使用默认的缓存工作,虽然在开发和测试环境是比较有用的,但是在生产环境,还是建议使用Caffeine缓存。 推荐我们使用Caffeine缓存。

前面我们曾经提到过,在高版本的SpringCloudGateway当中,不再集成ribbon,所以我们需要手动引入如下的依赖:

<!-- Feign Client for loadBalancing -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-loadbalancer</artifactId>
    <version>3.1.1</version>
</dependency>

正是由于这个依赖报出的此警告,下面我们分析下其原因。

源码分析

通过警告信息,我们能定位到警告位置在LoadBalancerCacheAutoConfiguration这个配置类当中,下面我们看下这个类干了哪些事儿。

/**
 * 一个自动配置,在Spring Boot启动时自动启用缓存Spring Framework缓存支持。
 * 如果类路径中存在Caffeine,则将用于负载均衡器缓存。否则,将使用默认缓存。
 *
 * @author Olga Maciaszek-Sharma
 * @since 2.2.0
 * @see CacheManager
 * @see CacheAutoConfiguration
 * @see CacheAspectSupport
 * @see <a href="https://github.com/ben-manes/caffeine>Caffeine</a>
 */
@Configuration(proxyBeanMethods = false) //proxyBeanMethods 默认是true,表示使用@Bean的类使用代理;此处不使用代理
@ConditionalOnClass({ CacheManager.class, CacheAutoConfiguration.class }) //装载这CacheManager、CacheAutoConfiguration两个bean
@AutoConfigureAfter(CacheAutoConfiguration.class) // 在CacheAutoConfiguration之后进行自动装配
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.cache.enabled", matchIfMissing = true)// 属性匹配
@EnableConfigurationProperties(LoadBalancerCacheProperties.class)//启用对@ConfigurationProperties注解bean的支持,此处是LoadBalancerCacheProperties
public class LoadBalancerCacheAutoConfiguration {

   @Configuration(proxyBeanMethods = false)//不使用代理
   @Conditional(OnCaffeineCacheMissingCondition.class)//Caffeine缓存丢失条件,如果没有引入caffeine,符合则加载这个bean,输出日志
   protected static class LoadBalancerCacheManagerWarnConfiguration {

      @Bean
      LoadBalancerCaffeineWarnLogger caffeineWarnLogger() {
         // caffeine警告日志
         return new LoadBalancerCaffeineWarnLogger();
      }

   }

   static class LoadBalancerCaffeineWarnLogger {

      private static final Log LOG = LogFactory.getLog(LoadBalancerCaffeineWarnLogger.class);

      @PostConstruct//jdk注解,用来修饰非静态的void方法,加载顺序 构造器 > Autowired > postConstruct
      void logWarning() {
         if (LOG.isWarnEnabled()) {
            LOG.warn("Spring Cloud LoadBalancer is currently working with the default cache. "
                  + "While this cache implementation is useful for development and tests, it's recommended to use Caffeine cache in production."
                  + "You can switch to using Caffeine cache, by adding it and org.springframework.cache.caffeine.CaffeineCacheManager to the classpath.");
         }
      }

   }

   @Configuration(proxyBeanMethods = false)//不使用代理
   @ConditionalOnClass({ Caffeine.class, CaffeineCacheManager.class })// 加载这两个bean,Caffeine只有我们引入依赖后才有,不满足则不会创建下面的bean
   protected static class CaffeineLoadBalancerCacheManagerConfiguration {

      @Bean(autowireCandidate = false)
      @ConditionalOnMissingBean //LoadBalancerCacheManager是接口,当其不存在spring Bean工厂时,就会创建这个bean,此处一定会创建
      LoadBalancerCacheManager caffeineLoadBalancerCacheManager(LoadBalancerCacheProperties cacheProperties) {
         // 创建caffeine 缓存管理器
         return new CaffeineBasedLoadBalancerCacheManager(cacheProperties);
      }

   }

   @Configuration(proxyBeanMethods = false)//不使用代理
   @Conditional(OnCaffeineCacheMissingCondition.class)// 满足没有引入caffeine才执行
   @ConditionalOnClass(ConcurrentMapWithTimedEviction.class)// 有ConcurrentMapWithTimedEviction才执行
   protected static class DefaultLoadBalancerCacheManagerConfiguration {

      @Bean(autowireCandidate = false)
      @ConditionalOnMissingBean //LoadBalancerCacheManager是接口,当其不存在spring Bean工厂时,就会创建这个bean,此处一定会创建
      LoadBalancerCacheManager defaultLoadBalancerCacheManager(LoadBalancerCacheProperties cacheProperties) {
         // 创建默认缓存管理器
         return new DefaultLoadBalancerCacheManager(cacheProperties);
      }

   }

   static final class OnCaffeineCacheMissingCondition extends AnyNestedCondition {

      private OnCaffeineCacheMissingCondition() {
         super(ConfigurationPhase.REGISTER_BEAN);
      }

      @ConditionalOnMissingClass("com.github.benmanes.caffeine.cache.Caffeine")
      static class CaffeineClassMissing {

      }

      @ConditionalOnMissingClass("org.springframework.cache.caffeine.CaffeineCacheManager")
      static class CaffeineCacheManagerClassMissing {

      }

   }

}

具体的内容都在注释当中,下面我简单总结一下:

  • LoadBalancerCacheManagerWarnConfiguration:如果没有引入caffeine,则输出默认日志
  • CaffeineLoadBalancerCacheManagerConfiguration:加载caffeine配置,生成对应的缓存bean
  • DefaultLoadBalancerCacheManagerConfiguration:生成默认的缓存bean

注意:我在看源码的过程中,发现这个默认的DefaultLoadBalancerCacheManagerConfiguration不会被加载,因为其前面的条件注解@ConditionalOnClass(ConcurrentMapWithTimedEviction.class)没有满足,缺少这个bean,需要额外增加依赖,所以不会执行。这里留下一点疑问,那是不是就是说如果不引入caffeine,是不是就相当于没有缓存?

image.png

加入caffeine依赖使用caffeine缓存

既然我们已经知道为什么给出这个警告了,且已经推荐我们使用caffeine了,那么我们就直接引入这个依赖就好了。

<!-- https://mvnrepository.com/artifact/com.github.ben-manes.caffeine/caffeine -->
<dependency>
    <groupId>com.github.ben-manes.caffeine</groupId>
    <artifactId>caffeine</artifactId>
    <version>2.8.8</version>
</dependency>

需要注意

  • 不要引入过高的版本,建议在3.X以下版本,否则会导致引入无效,仍然抛出警告

    可能有的同学会说,无效是不是CaffeineManager没有加载,并不是的,当你重新构造一个CaffeineManager,并且加到spring容器后,会报出版本兼容性的问题。也许你升高jdk的版本可能会解决这个问题,但基于jdk1.8的环境还是建议在3.x之下版本。

  • 引入即生效,无需其他配置,如果发现无效请参考上面一条注意事项。

加入evictor依赖使用默认缓存

前面我们就曾提到过,默认情况下缺少依赖,导致默认的缓存也是无法使用的,所以虽然日志输出了当前使用的是本地缓存,然而经过我的代码跟踪,发现根本没有使用本地缓存。甚至没有使用缓存。

到底是不是这样的,我们可以直接调用其他服务接口看下日志输出即可:

2022-03-21 13:26:50 WARN  Thread-wjbgn-1 org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplierBuilder LoadBalancerCacheManager not available, returning delegate without caching.

清晰的看到没有使用到缓存。

引入如下依赖:

<!-- https://mvnrepository.com/artifact/com.stoyanr/evictor -->
<dependency>
    <groupId>com.stoyanr</groupId>
    <artifactId>evictor</artifactId>
    <version>1.0.0</version>
</dependency>

此时可以使用默认缓存了。

总结

SpringCloud使用此缓存的主要目的是为了解决其服务实例负载均衡时的缓存问题,说到底缓存的最大作用还是提升性能。

官方推荐咱们使用caffeine缓存,为了使网关性能达到最大,建议遵从官方建议。