[spring-projects/spring-boot]在内存缓存中添加对cache2k的支持

2024-05-14 717 views
2

cache2k是一种高效的内存缓存,内存开销低,吞吐量高。对于缓存友好的工作负载,Cache2k 的性能比所有其他缓存高出两倍或更多,请参阅最新基准:https://cache2k.org/benchmarks.html

Cache2k 具有许多其他缓存所不具备的功能,有助于构建低延迟和弹性应用程序,例如提前刷新、弹性和异常支持。

有一个定义良好的 Java 缓存 API(请参阅:https://cache2k.org/docs/latest/apidocs/cache2k-api/index.html)以及广泛的用户指南(请参阅:https ://cache2k.org /docs/latest/user-guide.html)。

其他好处是支持 JSR107/JCache、JMX 统计和管理,例如在线缓存大小调整、Spring Framework 集成和微米统计导出。

cache2k 大小为 400kB,是最紧凑、功能最丰富的缓存库之一。它支持纯 Java 8,而不使用sun.misc.Unsafe.下一版本的 cache2k 计划需要 Java 11 并使用其高级功能来进一步提高性能和效率。

cache2k 中的 Spring 框架支持已经存在一段时间了。与 Caffeine 相比,它支持开箱即用的单个缓存配置,请参阅: https://cache2k.org/docs/latest/user-guide.html#spring

此拉取请求添加了 Spring Boot 对 cache2k 的支持。

回答

1

感谢您的公关。我已经标记了这一点来讨论我们是否想在 Spring Boot 中引入对 cache2k 的支持。请暂时忽略下面的评论。

感谢您的快速评论并查看!

Spring 支持在cache2k 中已经提供了一段时间,并且正在持续进行集成测试。 cache2k 拥有一致且完整的用户指南,可以帮助用户开始使用缓存。查看 EHCache3 或 Caffeine 用户在开始时需要将几篇博客文章和 Stackoverflow 答案拼凑在一起。我认为从这个角度来看,Spring 和cache2k 的项目理念是相似的。

注意:cache2k Spring 集成正在利用空值支持,而大多数其他缓存正在包装缓存值(EHCache3、Caffeine、JCache 基础缓存)。这减少了内存占用。

由于 Spring 文档中没有提到它,不幸的是它对于 Spring 用户来说不是很明显。我认为在没有 Spring boot 支持的 Spring 文档中提及它是没有意义的,因为用户体验与其他缓存不一致。

4

@cruftex,我们决定将其合并到我们的下一个功能版本中。我们的第一个里程碑是明年,所以有足够的时间,但您是否可以同时看一下评论?谢谢!

4

@snicoll 谢谢,这是个好消息!我计划下周完成下一个 cache2k 版本并更新此 PR。

6

更新了 PR:

  • 上述审查的建议更改
  • 检查样式问题
  • 格式问题
  • 改进的 JavaDoc

让我知道我是否应该重新调整/挤压我这边。

9

是的,请。

1

好的!重新调整基础。

9

我已经评论了完善的拉取请求。我想补充的事情:

  • 默认配置支持,可能通过Cache2kConfig属性。我会为此做一些测试。
  • 动态创建和的附加测试managerName = null
  • 也许类加载器支持

然而,完善的公关已经足以集成。如果您想合并您的完善版本并且我做了一个新的 PR,或者我是否应该在添加完善的提交后继续这里,请告诉我。

8

感谢您查看它。

然而,完善的公关已经足以集成。

它不能,构建在配置默认值的测试中失败,并期望它们在自动配置为标准属性中定义的名称创建缓存时可用cache-names。我对定制器的建议确实是阻止合并的原因。我不知道这是否是正确的方法。我不知道Cache2kConfig。公开该类型的 bean 是否有助于配置默认缓存?它肯定会与我们在其他地方所做的事情更加一致。

关于将其作为属性公开,我并不热衷于这样做。我们收到了多个请求,要求公开其他缓存库的大量属性,但我们没有采取行动。我们可能会在某个时候重新审视这一点,但我不想只为一家商店这样做。

9

Cache2kConfig: 已经有了SpringCache2kCacheManager.setCaches(Collection<Cache2kConfig<?, ?>> cacheConfigurationList)。需要复制默认配置,然后设置名称。存在适当的复制功能,cache2k-config因为它需要在 XML 配置支持中,但目前尚未公开。春天BeanUtils也许也能发挥作用。该setCaches方法目前的测试覆盖率有点薄,只有一个非常简单。我将使用有用的参数(例如到期时间和容量)进行一些测试,并查看 BeanUtils 是否足够。

关于将其作为属性公开,我并不热衷于这样做。

这是可以理解的。事情似乎已经变得很混乱,例如,expiry在一个实现上有一个参数,timeToLive在另一个实现上有一个参数,然而,这本质上是相同的概念。如果添加控制任何缓存功能的参数,将会有恒定的流量。单个配置参数(如规范字符串或配置 bean)可以避免变化,并让两个产品独立发展。

OTOH,除 JCache 之外的所有其他缓存都允许在属性中进行基本配置,而 Caffeine 则允许完整配置,因为它具有通过规范字符串提供的所有配置参数。 JCache 是一个例外,因为您始终需要实现特定的参数,并且MutableConfiguration无法提供简单的设置(过期)作为简单的 bean 属性。此外,考虑到用户文档,具有合理默认值的简单设置在缓存实现中应该看起来相似,或者至少不会混合不同的配置方法。因此,使用cache2k,应该也可以在属性中配置基本参数,以保持一致。也许这是一个很好的推理。你怎么认为?

像 Caffeine 这样的所有参数都使用一个字符串会有帮助吗?

8

OTOH,除 JCache 之外的所有其他缓存都允许在属性中进行基本配置,而 Caffeine 则允许完整配置

我认为这不准确。 EhCache、Infinispan 和 JCache 不提供任何基于属性的配置(除了配置文件的路径之外)。 Couchbase 有一个expiration.恰恰相反,Redis 更完整。

我想说的是,我现在宁愿保持程序化(使用回调 API,或检测 bean)。我愿意重新审视这个主题并公开更多潜在的属性,但必须全面完成。

考虑到这一点,公开Cache2kConfig默认缓存并使用它PropertyMapper来初始化默认构建器听起来非常有趣。你怎么认为?

2

我认为这不准确。

是的,你在这里有更好的概述,我只浏览了课程CacheProperties

我想说的是,我宁愿暂时保持程序化

对于编程配置,用户通常更喜欢流畅的构建器模式。如果我们只复制一组选定的参数,那么使用Cache2kConfig只会使事情变得更加复杂,甚至可能令人困惑。受到您最初 Cache2kBuilderCustomizer想法的启发,我现在实现了另一个解决方案,位于https://github.com/cache2k/cache2k/commit/b2bb384b12cd7b57762573e0a8f74561d748935b。这里是相关(缩短的)摘录:

interface SpringCache2kDefaultSupplier extends Supplier<Cache2kBuilder<?, ?>> {

  String DEFAULT_SPRING_CACHE_MANAGER_NAME = "springDefault";

  static void applySpringDefaults(Cache2kBuilder<?, ?> builder) {
    builder.permitNullValues(true);
  }

  static Cache2kBuilder<?, ?> supplyDefaultBuilder() {
    return
      Cache2kBuilder.forUnknownTypes()
        .manager(CacheManager.getInstance(DEFAULT_SPRING_CACHE_MANAGER_NAME))
        .setup(b -> applySpringDefaults(b));
  }

}

Spring 配置中的用法将如下所示:

  public SpringCache2kDefaultSupplier springCache2kDefaultSupplier() {
    return () -> SpringCache2kDefaultSupplier.supplyDefaultBuilder()
      .expireAfterWrite(6, TimeUnit.MINUTES)
      .entryCapacity(5555)
  }

  public SpringCache2kDefaultSupplier springCache2kDefaultSupplierAlternativeManager() {
    return () -> Cache2kBuilder.forUnknownTypes()
      .manager(org.cache2k.CacheManager.getInstance("specialManager"))
      .setup(SpringCache2kDefaultSupplier::applySpringDefaults)
      .expireAfterWrite(6, TimeUnit.MINUTES)
      .entryCapacity(6666)
  }

我们也不再需要属性中的 manager 参数。让我知道这是否适合您,以及您是否有任何建议,例如更改名称或使用Supplier.我的下一步是发布带有更改的 cache2k 的 alpha 版本,然后调整 Spring Boot 集成。如果一切看起来都不错,我将发布最终的 cache2k 版本。

3

受到您最初的 Cache2kBuilderCustomizer 想法的启发,我现在实现了另一个解决方案

很酷,在 Spring 集成中得到它本身就好多了,感谢您这样做。我玩了一下,这里有一些原始反馈

  • Cache2kDefaultSupplier有点通用。另外,我们难道不希望这是非 Spring 特定的吗?能够为添加到的缓存提供默认配置CacheManager
  • getter 上的 javadoc 在我看来是错误的“Cache2k 构建器具有 Spring 的合理默认值。允许 null,因为“Spring 缓存”允许存储 null 值。”。不能提供一个不允许的定制实例吗?
  • lambda让我很困惑。
  • 一旦您需要自定义默认缓存设置之外的其他内容,您需要记住应用“springDefaults”。因为它是一个方法(而不是构建器上的回调,所以这不能是单个语句。
  • 我已删除缓存管理器名称。也许这是一个极端的情况,但能够指定一个名称会很好。

我已经切换到2.6-SNAPSHOT本地来尝试一下。这样,测试就会失败,因为缓存管理器不会在测试之间关闭。这看起来像是 Spring 集成中的一个错误,它应该实现DisposableBean并关闭它在ApplicationContext.

WDYT?

7

getter 上的 javadoc 在我看来是错误的“Cache2k 构建器具有 Spring 的合理默认值。允许 null,因为“Spring 缓存”允许存储 null 值。”。不能提供一个不允许的定制实例吗?

我不确定你的问题,所以我尝试更好地解释:

Spring 中的缓存可能支持空值,但大多数缓存实现不支持。为了支持空值,大多数缓存集成都使用对象包装器。对于cache2k,我不这样做,而是使用cache2k 的本机空值支持。 cache2k 中的默认值不允许空值,因为当来自其他缓存实现时,这是最不令人意外的。由于Spring中默认允许空值,因此我们需要默认配置cache2k并允许空值。用户可以在默认值或缓存特定设置中覆盖此设置,以防不支持空值。

这是在回答问题吗?不知道如何在 JavaDoc 中简要地编写此内容。

Cache2kDefaultSupplier有点通用。另外,我们难道不希望这是非 Spring 特定的吗?能够为添加到 CacheManager 的缓存提供默认配置

是的。我不久前为此创建了https://github.com/cache2k/cache2k/issues/153 。如果应用程序想要将 cache2k 与 Hibernate 一起使用,则需要通过 XML 进行配置。因此需要两种不同的配置风格。最好将对此的支持添加到包含的缓存管理器中。这次讨论是一些很好的新灵感。

(我明天看看其他主题,双 lambda 目前也让我困惑。)

5

一旦您需要自定义默认缓存设置之外的其他内容,您需要记住应用“springDefaults”。因为它是一个方法(而不是构建器上的回调,所以这不能是单个语句。

不确定你在这里的意思。这作为示例包括在内:

  public SpringCache2kDefaultSupplier springCache2kDefaultSupplierAlternativeManager() {
    return () -> Cache2kBuilder.forUnknownTypes()
      .manager(org.cache2k.CacheManager.getInstance("specialManager"))
      .setup(SpringCache2kDefaultSupplier::applySpringDefaults)
      .expireAfterWrite(6, TimeUnit.MINUTES)
      .entryCapacity(6666)
  }

这看起来像是 Spring 集成中的一个错误,它应该实现 DisposableBean 并关闭它在 ApplicationContext 生命周期中创建的缓存管理器。

固定的!

我还添加了一个setCacheNames与其他实现类似的方法。请使用它。

3

这作为示例包括在内

抱歉,我错过了那里的设置方法。但我最初的评论仍然有效。如果你想自定义某些东西,你就会失去默认行为。当然有,applySpringDefaults但你很容易忘记调用它(甚至不知道它存在)。

IMO 这又回到了默认的 lambda 问题。我认为获得一个应用默认值的默认回调并让您进一步自定义它会更好,而不是要求实现该接口。

0

我的想法是,它的供应商方法是一种优雅的解决方案,可以为另一个缓存管理器提供默认值和/或更改。但是,是的,这可能会令人恼火或导致错误。那么回到你最初的想法:

interface DefaultCache2kBuilderCustomizer {
  void customize(Cache2kBuilder<?, ?> builder);
}

我认为这Cache2kBuilderCustomizer太笼统了。

如果用户想要使用另一个,CacheManager他们可以将其包含SpringCache2kCacheManager在配置中并将其绑定到任何本机缓存管理器。所以实际上,如果用户想要做更复杂的事情,无论如何都是可以的。

恕我直言,它DefaultCache2kBuilderCustomizer应该存在于 Spring Boot 中,因为它仅在 Spring Boot 创建缓存管理器时使用。或者,我可以开始处理https://github.com/cache2k/cache2k/issues/153并将其放入 中cache2k-api,但是,我认为最好将其分开。也许用户也希望在 DI 容器中拥有一个原生的 cache2k 缓存管理器,这需要另一个默认的 Spring 缓存抽象的缓存管理器。

目前还有一件事让我不高兴。通过 Caffeine 集成,我可以看到当默认值更改时缓存会刷新,并且该方法registerCustomCache会覆盖以前创建的缓存。这意味着,对于 Caffeine 来说,可以更改CacheManagerCustomizer. Caffeine 中的刷新会删除之前创建的缓存并创建新的缓存。由于 cache2k 缓存是受管理的,并且在创建时可能会导致其他事件,因此无法执行相同的操作。为了使用cache2k实现该功能(能够调整默认值并在CacheManagerCustomizer中添加自定义缓存),在管理器定制器之后处理缓存名称实际上是有意义的,并且仅添加具有尚未存在的默认设置的缓存。我认为我最初的公关中也有类似的情况,现在我知道为什么了。这对于咖啡因来说实际上也是有意义的,因为不再需要(丑陋的)提神作用了。

由于上述问题仅在指定了名称属性和 a 的情况下适用CacheManagerCustomizer,因此另一种方法可能是简单地禁止这种组合(对于cache2k)。这比不同的语义更好。

让我知道你的想法。如何进行?

7

您与供应商开始的内容应该保持一种或另一种形式,这比我们引入定制器要好得多。拥有一个应用默认值并采用定制器的默认实现应该就足够了。或者甚至是你现在所拥有的,如果你对它感到满意的话。

在管理器定制器之后处理缓存名称实际上是有意义的,并且仅添加具有尚未存在的默认设置的缓存。

我不认为我同意,但这并不重要,因为那艘船已经航行了。我们只能在主要版本中真正更改该顺序。对于 3.0,我们可以考虑在定制器中应用缓存名称,顺序为 ,0以便您可以选择在之前或之后调用。

有很多争论,所以我希望我们能尽快得出结论。

9

我不认为我同意,但这并不重要,因为那艘船已经航行了。我们只能在主要版本中真正更改该顺序。对于 3.0,我们可以考虑在定制器中应用顺序为 0 的缓存名称,以便您可以选择在之前或之后调用。

我们最好暂时离开这个话题。可能需要更多时间(和示例代码)来彻底讨论这个问题,但这不是必需的。也许我太想变得完美了。

有很多争论,所以我希望我们能尽快得出结论。

好吧。更新:

我发布了版本 2.5.2.Alpha 并将 spring-boot 更改合并为: https://github.com/cruftex/spring-boot/tree/cache2k-take2

变化:

  • SpringCache2kManager.defaultSetupIllegalStateException如果已添加缓存则抛出异常(在 Caffine 中重置默认值有效,但在 cache2k 中无效)
  • SpringCache2kDefaultCustomizer添加了接口并添加了支持Cache2kCacheConfiguration
  • 更新测试

希望我们越来越接近?

2

@snicoll:平。希望它能得到支持,我重新调整并压缩了所有内容并开始了一个新的 PR: https://github.com/spring-projects/spring-boot/pull/29536 但是,如果需要更多讨论,请随时关闭其他 PR 并在这里继续讨论。

如果还有什么地方需要更多工作,请告诉我。

1

SpringCache2kDefaultCustomizer对我来说听起来SpringCache2kDefaultSupplier很普通。我猜定制器是作为双 lambda 事物的权衡而引入的?如果是这样,我认为我们没有在讨论的问题上取得进展。

我们现在有三种方式来定制东西,配置默认值的“默认供应商”,定制默认值的“默认定制器”,以及配置缓存管理器的定制器。我认为供应商和定制商没有必要,因为供应商的目的就是提供默认值。最好的情况是,定制器可以是一个供应商的实现,但不应该是默认行为,因为两者没有连接。

拥有一个应用默认值并采用定制器的默认实现应该就足够了。

所以我并不是说要创造另一个一流的概念。相反,供应商的默认实现将消费者或其他东西作为参数。确实只是为了方便而已。

总结。如果您希望在应用缓存名称之前配置缓存管理器,则在 Spring Boot 中执行此操作的惯用方法是提供一个代表默认配置的 bean,或者可以应用于配置默认配置的内容。我已经表达了关于如何在 Cache2K 中完成此操作的意见,但可以忽略。

没有必要为了这个 PR 的目的而在 cache2k 中引入一些东西。

8

我猜定制器是作为双 lambda 事物的权衡而引入的?如果是这样,我认为我们没有在讨论的问题上取得进展。

不。我添加它是因为来自https://github.com/spring-projects/spring-boot/pull/28498#issuecomment-1011872504的评论。双 lambda 仅出现在 Spring boot 测试代码中,而不出现在普通用户代码中。这里是评论:

IMO 这又回到了默认的 lambda 问题。我认为获得一个应用默认值的默认回调并让您进一步自定义它会更好,而不是要求实现该接口。

如果用户只想提供默认值,那么定制器对我来说似乎是更好且易于理解的界面。你还写道:

您与供应商开始的内容应该保持一种或另一种形式,这比我们引入定制器要好得多。拥有一个应用默认值并采用定制器的默认实现应该就足够了。

最好有一些代码。这里有一个比较。使用定制器:

        @Bean
        SpringCache2kDefaultCustomizer cache2kDefaultCustomizer() {
            return (builder) -> builder.entryCapacity(4711);
        }

如果我理解正确的话,这里包装了定制器或一些消费者:

        @Bean
        SpringCache2kDefaultSupplier cache2kDefaults() {
            return SpringCache2kDefaultSupplier.of((builder) -> builder.entryCapacity(4711));
        }

提供默认值感觉相当难看。但也许你心里有一个我不知道的 Spring 技巧?

注意:这里复杂的因素之一是原生的空支持,这让我难以置信。其他缓存没有这个问题,只需提供完整的缓存配置即可,不需要 Spring 特定的默认值。

我们现在有三种方式来定制东西,配置默认值的“默认供应商”,定制默认值的“默认定制器”,以及配置缓存管理器的定制器。我认为供应商和定制商没有必要,因为供应商的目的就是提供默认值。

默认供应商仍然可以提供要使用的管理器。或者,aManagerSupplier会更清楚,或者,我们再次使用该属性,或者,我们简单地忽略它,因为它总是可以在没有自动配置的情况下创建缓存管理器。

配置缓存名称时,Caffeine 有 4 种提供默认值的可能性:在属性中,提供构建器 (Caffeine),提供 CaffeineSpec,然后您可以使用CacheManagerCustomizer并通过构建器、spec 或 String 再次设置默认值。因此,根据我一开始的想法或要求,它应该与人们从其他缓存中了解到的类似,它会自动导致许多不同的概念来提供选项。

所以,是的,最好少一些不同的概念。目前我倾向于放弃供应商但保留定制器,因为这会更频繁地使用并且是一个已知的 Spring boot 概念。也许将其简单地重命名为Cache2kDefaults

对于更专业的东西,他们CacheManager总是可以直接提供。跳过自动配置并提供CacheManagerbean 有什么特别的缺点吗?

由于事情有点失控,我觉得我们都已经有点沮丧了,我觉得写下目标和可能的设置场景以进行更有条理的讨论是有意义的。你怎么认为?

1

@snicoll:清理并简化了集成。

Cache2kDefaults对于自动配置默认值, Spring boot 中现在有该类。更好的名字创意总是受欢迎的。 cache2k中SpringCache2kCacheManager还进行了各种改进,以检测配置中可能的错误,例如设置默认值两次。虽然感觉我们又回到了起点,但一路上各种细节都得到了改进。

作为一个方向,我提出了以下矩阵:

自动配置变体

属性中的名称 Cache2k默认值 缓存管理器定制器 结果
- - - 根据请求动态创建缓存CacheManager.getCache
X - - 启动时创建缓存,注册指标,不允许动态创建缓存
- X - 使用指定的默认值动态创建的缓存
X X - 启动时使用指定的默认值创建缓存,注册指标,不允许动态创建缓存
- X 使用自定义缓存 以及通过缓存 CacheManager 添加特定配置的缓存,允许应用默认值的动态缓存创建
- - 具有默认值和自定义缓存 具有默认值的缓存和具有通过缓存 CacheManager 添加的特定配置的缓存
- X 带默认值 错误
X - 带默认值 错误
X X 使用自定义缓存 具有默认值的命名缓存和具有通过缓存 CacheManager 添加的特定配置的缓存,无动态缓存创建。这可行,但不推荐,因为配置分布在三个不同的配置概念中,通过管理器定制器更好地设置缓存名称和默认值

现在已经对上述变体进行了测试。

“手动”配置

CacheManager 创建为 bean -> 允许设置不同的 cache2k 管理器名称,如果有必要的话,允许在将来设置类加载器,注册已知缓存的指标。

期待您的评论。

4

将cache2k版本提升至2.6.1.Final @snicoll:ping!

2

修复了格式错误。

6

@cruftex 感谢您为 Spring Boot 做出的第一个贡献。

我认为我们已经迭代并改进了很多东西。我不想有太多的配置方式,所以我暂时将其保留给专用的定制程序。 IMO,如果你想防止defaultSetup被调用两次,它应该成为缓存管理器的构造函数参数。

2

@snicoll 感谢您添加cache2k!

我认为我们已经迭代并改进了很多东西。我不想有太多的配置方式,所以我暂时将其保留给专用的定制程序。

是的,我意识到,当我尝试实现“所有”我发现的其他配置变体时,这导致了可怕的混乱。