什么时候拥有 Hibernate 二级缓存的 CPU 好处超过初始命中

When does the CPU benefit of having an Hibernate 2nd level cache outweigh the initial hit

何时将对象添加到 Hibernate 二级对象缓存的 CPU 好处超过初始命中。

我目前正在使用没有二级缓存的 Hibernate。这是用于处理音乐文件 (www.jthink.net/songkong) 的应用程序,它使用 Hibernate,因此它可以扩展更多数据,即它可以处理 100,000 首歌曲,而内存只比 1000 首歌曲多一点。处理完歌曲后,这些歌曲就没有意义了(除非用户 运行 撤消)

据我了解,如果我启用二级缓存(对于我的歌曲 class),那么将歌曲初始写入缓存将使用更多 cpu,然后如果只写入数据库,并且对歌曲对象的其他修改也将需要更多 cpu 资源。但是随后从 Ehcache 中检索歌曲比从数据库中检索歌曲需要更少的资源。

我的歌曲是逐个文件夹处理的,并经过多个阶段(在不同的执行器上),当它们在下一个执行器上排队时,我们只需将歌曲 ID 作为参数传递,否则会使用大量堆存储 Song 对象本身的内存。因此,当一个特定的任务实际上 运行 在执行器上时,它所做的第一件事就是检索这些 ID 的歌曲。

所以没有特定的歌曲 ID 被检索 1000 次,但每首歌曲通常写入 1 到 4 次,检索 10 次。因此,如果我们有一个非常小的缓存(因为我想密切控制堆内存),我希望处理前几个文件夹以将它们的歌曲添加到缓存中,然后当它们完成新文件夹中的歌曲时,它们将使用它们放在缓存中。

但我的问题是,这值得吗?

根据经验,10 次检索与 1-4 次写入相比使用二级缓存是否有意义,或者仅当比率更像 100:1?

时才有用

真正的答案是:只需对其进行基准测试。

写入堆缓存并没有那么昂贵。所以是的,即使从缓存中检索一次也会使它比返回数据库更快。

然后,缓存主要在 HashMap 之上做两件事。它逐出并过期。

逐出意味着您为缓存设置了一些最大大小。达到此值时,缓存将逐出 "oldest" 条目以添加新条目。 oldest 有多种定义。 Ehcache 对一组条目进行采样,并踢出样本中未访问时间最长的条目。

过期意味着给定条目在某个时候将被视为过时。例如,您希望在使用数据库中的最新条目刷新条目之前将条目保留 1 小时。当你得到一个条目时,Ehcache 首先查看条目是否过期。如果是,它将 return null 并从缓存中删除该条目。这意味着过期条目将保留在缓存中,直到您尝试访问它。

在您的情况下,您需要加载一次条目。然后将其放入缓存中。使用它并最终将其删除以节省内存。如果您在最后一步知道不再需要该条目,只需将其删除即可。

不行就只能靠驱逐了。因为驱逐算法将首先删除过期的条目(如果可以删除过期的条目,为什么要删除完全有效的条目?)。

您应该计算一个条目应该在缓存中保留多少次以通过所有执行器。这将是您的到期时间 (TTL)。然后你将缓存的大小或多或少设置为 NB_EXECUTORS * NB_STEPS。然后它将是当前使用的歌曲的大小。添加新歌曲时,缓存需要清除旧条目。在大多数情况下,此条目将过期,因此不会造成任何伤害。

为了防止逐出(当找不到过期的条目时这可能代价高昂),您可以编写一个获取条目的后台例程。它会触发到期。但同样,在确定之前不要这样做,使用基准测试,它实际上更快。

最后,您可能希望直接缓存歌曲而不是使用 Hibernate level 2。因为获取歌曲所需的操作更少。此外,当写入 second-level 缓存中的条目时,Hibernate 倾向于从缓存中逐出。确保将其配置为 NOT

关于修改的说明。默认情况下,Ehcache on-heap 缓存(并且只有 on-heap 缓存)是每个引用。因此,如果您从缓存中检索歌曲 object 然后修改它,缓存中的条目也会被修改,因为它实际上是唯一的实例。

然而,这不是 Hibernate 二级缓存的工作方式。他们将在缓存中保留某种数据库行。这将转换为歌曲并return发送给您。

当您将歌曲保存到数据库时,Hibernate 会像我上面所说的那样将其从缓存中逐出(但是您可能会要求在配置中更新缓存,我不确定)。

这就是为什么我认为您应该直接缓存而不是使用 second-level 缓存。但是,请注意,您会得到一个由 Hibernate 加载的 object。在将其放入缓存之前,您需要将其与 Hibernate 分离。然后将它附加到新的执行器中。否则,如果你有 collections,就会发生奇怪的事情。

现在,假设您希望每次都更新缓存和数据库。您有两种方法可以做到。

使用 Cache-aside,您将更新数据库,然后更新缓存。

使用 Cache-through,您将更新缓存,它负责(原子地)更新数据库。 Cache-through 有点复杂,因为您需要提供 CacheLoaderWriter 实现。但它确保缓存和数据库始终同步。