0

我正在使用 LoadingCache:

val cacheloader = 
new CacheLoader[Key, Value]() {
  override def load(key: Key): Value = loadKeyFunc(key, None)
  override def reload(key: Key, prevValue: Value): ListenableFuture[Value] = {
    val task = ListenableFutureTask.create(new Callable[Value]() {
                                              def call(): Value = {
                                                loadKeyFunc(key, Some(prevValue))
                                              }
                                           })
    executor.execute(task)
    return task
  }
}

val cache: LoadingCache[FirstPageSearch, Array[String]] =
                   CacheBuilder.newBuilder()
                               .maximumSize(10)
                               .refreshAfterWrite(5, TimeUnit.MINUTES)
                               .build(cacheLoader)

loadKeyFunc只是表单的匿名函数val loadKeyFunc: (Key, Option[Value]) => Value。cacheLoader 使用 executor ( Executors.newFixedThreadPool(6)) 使刷新异步。系统收到一个始终通过此缓存的 HTTP 请求(get(key)向缓存发出 a),始终从缓存中获取结果。当它太旧时,它会在后台重新计算并在下一个请求时提供它。

几天甚至几周内一切正常。但有时(通常在非常低的使用时间)缓存会停止刷新。新请求开始接收总是相同的旧数据。我里面有一个日志语句loadKeyFunc,我知道它没有被调用。

似乎由于某种原因LoadingCache没有看到数据超过 5 分钟。在我重新启动系统(HTTP 服务器)后,一切又恢复正常。

有任何想法吗?

PS:我们使用的 loadKeyFunc 只是一个简单的日志语句,然后调用一个无状态对象,该对象查询我们的搜索后端系统返回一个字符串数组(每个数组位置都是一个搜索页面)。

PS2:它是一个基于 Scalatra 的运行嵌入式 Jetty HTTP 服务器。LoadingCache是在对象内部创建的ScalatraServlet

清理后的小日志(/first_page 是始终使用缓存的请求,“Executing...”是 (re)load 方法中的日志语句CacheLoader):

[INFO] [qtp48202314-8659] 2012-11-03 04:55:58 - Request(/first_page)
[INFO] [pool-2-thread-10] 2012-11-03 04:55:58 - Executing FirstPageSearch to put in cache
[INFO] [qtp48202314-8659] 2012-11-03 05:19:17 - Request(/first_page)
[INFO] [qtp48202314-8659] 2012-11-03 05:20:32 - Request(/first_page)
[INFO] [qtp48202314-8661] 2012-11-03 05:25:22 - Request(/first_page)
[INFO] [qtp48202314-8659] 2012-11-03 05:26:09 - Request(/first_page)
[INFO] [qtp48202314-8659] 2012-11-03 05:26:18 - Request(/first_page)
[INFO] [qtp48202314-8661] 2012-11-03 05:38:37 - Request(/first_page)
[INFO] [qtp48202314-8659] 2012-11-03 06:54:36 - Request(/first_page)
[INFO] [qtp48202314-26]   2012-11-03 11:31:37 - Request(/first_page)
[INFO] [pool-2-thread-1]  2012-11-03 11:31:37 - Executing FirstPageSearch to put in cache
[INFO] [qtp48202314-25]   2012-11-03 11:41:53 - Request(/first_page)
[INFO] [qtp48202314-8674] 2012-11-03 14:48:58 - Request(/first_page)
[INFO] [qtp48202314-8674] 2012-11-03 14:54:45 - Request(/first_page)
[INFO] [qtp48202314-8674] 2012-11-03 15:31:32 - Request(/first_page)
[INFO] [qtp48202314-26]   2012-11-03 15:31:48 - Request(/first_page)
[INFO] [qtp48202314-8674] 2012-11-03 15:32:05 - Request(/first_page)
[INFO] [qtp48202314-8674] 2012-11-03 15:44:44 - Request(/first_page)
[INFO] [qtp48202314-8674] 2012-11-03 15:44:44 - Request(/first_page)
[INFO] [qtp48202314-26]   2012-11-03 15:47:39 - Request(/first_page)
[INFO] [qtp48202314-8674] 2012-11-03 15:51:20 - Request(/first_page)
[INFO] [qtp48202314-26]   2012-11-03 15:52:59 - Request(/first_page)
[INFO] [qtp48202314-8674] 2012-11-03 15:54:18 - Request(/first_page)
[INFO] [qtp48202314-26]   2012-11-03 15:55:37 - Request(/first_page)
4

2 回答 2

1

来自CacheBuilder.refreshAfterWriteJavadoc:

当前,当对条目的第一个陈旧请求发生时,会执行自动刷新。如果返回的future完成,触发刷新的请求将阻塞调用CacheLoader.reload(K, V)并立即返回新值,否则返回旧值。

因此,当一个值过时时,在您实际查询该键之前不会触发刷新,此时它将返回旧值并异步触发刷新。一旦值完成刷新,它将开始从缓存中返回。

你确定这不是你看到这种行为的原因吗?

想到的另一种可能性是,由于某种原因,您的 loadKeyFunc 没有终止。在固定大小的线程池中,如果您有六个查询因任何原因被锁定,这可能会阻止新查询进入线程池,这似乎会导致您观察到的问题。也许您应该使用Executors.newCachedThreadPool,这可以避免这个问题——尽管您仍然会遇到由锁定线程引起的内存泄漏。=/

于 2012-11-03T18:30:55.673 回答
0

您可以避免使用expireAfterWrite. 这很难保证不提供陈旧的数据。我通常建议与 结合expireAfterWrite使用refreshAFterWrite,但显然在到期前有更大的延迟,以便有时间执行刷新。

于 2012-11-05T10:16:35.480 回答