使用Spring / EHCache加载时刷新缓存

我在SpringmultithreadingWeb服务上有一个缓存问题,它带有数据库后端和基于EHCache的缓存。 该服务有许多客户端一次又一次地请求相同的对象,每秒有几十个请求。 经常请求的对象只有几个,并且不经常请求大量其他对象。 对象每隔几分钟就可以更改一次,因此缓存的TTL设置为一分钟。 从数据库加载对象很慢,至少需要几秒钟。

起初我使用了一个天真的实现来获取对象:

  1. 检查对象是否在缓存中。
  2. 如果是,请从缓存中返回。
  3. 如果没有,从数据库加载它,将其放入缓存并返回它。

这在最初在本地测试时运行良好。 但是,每当一个更频繁请求的对象在缓存中到期时,在更快的服务器上进行性能测试就会显示一些非常糟糕的负载峰值。 发生这种情况时,在接下来的10秒内,对该对象的所有请求都将导致数据库加载,直到第一个线程完成数据库加载并将新对象放入缓存。 结果是数据库上的负载很短但非常高,并且许多用户需要等待请求完成。

我当前的实现通过跟踪当前正在加载哪个对象来改进数据库负载:

  1. 检查对象是否被缓存。
  2. 如果是,请从缓存中返回。
  3. 如果没有,请检查当前是否正在加载对象。
  4. 如果是,请等待另一个线程的加载完成,从缓存中获取新对象并返回它。
  5. 如果不是,则将对象放入加载对象列表中,完成后将其放入缓存中并返回。

通过此实现,即使对象到期,也只有一个数据库操作。 而且,由于数据库负载较低,它也会更快完成。 但它仍然意味着在对象加载期间请求对象的所有用户都需要等待。

我真正想要的是只有第一个线程等待数据库加载,而所有其他线程只是在加载对象时返回’过期’对象。 对于我来说,响应时间比对象太老了几秒钟更重要。

或者,当我注意到对象将在几秒钟内过期时,我可以异步刷新缓存。 这更接近EHCache的单个TTL模型,并且意味着没有人需要等待数据库负载

我真正的问题是:在我重新发明轮子之前,是否有任何现有的框架已经实现了这样的东西(在Spring / EHCache环境中)? 或者也许在Spring / EHCache的某处已经存在对此的支持,我找不到合适的选项?

有两个Ehcache提供的构造可以帮助您:

  1. 提前刷新
  2. 预定刷新

两者都要求您更改与缓存交互的方式,因为它们需要配置CacheLoader

不幸的是,我找不到显示第二个选项示例的在线文档。 它允许使用Quartz刷新缓存条目来安排它。 它还可以基于密钥生成器仅刷新密钥的子集。 看一下net.sf.ehcache.constructs.scheduledrefresh包中的net.sf.ehcache.constructs.scheduledrefresh

您的设计存在缺陷,因为第二个线程无法从缓存中获取任何“过期”对象,因为没有(根据步骤#2:当对象位于缓存中时立即返回)。

解决方法:

  1. 加载单个对象10秒钟太长了。 检查您的SQL并尝试优化它。

  2. 缓存对象更长时间并运行更新线程,查询数据库中对象的新状态。 这意味着线程#1只会触发一些后台工作,最终刷新缓存中的对象。 缺点:缓存必须足够大,以便始终将大多数对象保留在内存中。 否则,“第一次加载对象”将太明显。

  3. 显示网页而不加载对象,并在后台使用AJAX请求加载它们。 在对象可用时更新网页。 根据您的网站在不是一切准备就绪时的有用程度,这可能是响应性和准确性之间的良好平衡。

  4. 改善对象的加载。 创建“视图”表,其中包含在每行中显示单个对象所需的所有数据。 更改“真实”(规范化)对象时更新这些行。 “视图缓存”仅从此表填充。 这使得加载对象的速度非常快,但代价是数据模型的更改。 有关极端解决方案,请参阅“ 命令查询分离 ”。

  5. 尝试对数据模型进行非规范化,以减少加载单个对象所需的连接数。 或者,缓存一些您通常会加入的对象,并在Web服务器上进行过滤/聚合。

  6. 更新对象时,触发刷新缓存。 很快就会有人想要看到这个对象。 当人们手动编辑对象时,这种方法效果最好,而当外部系统(新闻报道,股票报价等)随机触发更改时,此方法最少。

  7. 如果您只需要很多连接来显示所有详细信息,请尝试加载概述,然后使用第二个缓存获取详细信息,然后可以在第二个线程中加载。 与AJAX一起,您可以快速显示对象的概述,这将为您带来一些等待细节的善意。