diff --git a/Compute-zh-CN.md b/Compute-zh-CN.md new file mode 100644 index 0000000..ed855a0 --- /dev/null +++ b/Compute-zh-CN.md @@ -0,0 +1,47 @@ +```java +Cache graphs = Caffeine.newBuilder() + .evictionListener((Key key, Graph graph, RemovalCause cause) -> { + // atomically intercept the entry's eviction + }).build(); + +graphs.asMap().compute(key, (k, v) -> { + Graph graph = createExpensiveGraph(key); + ... // update a secondary store + return graph; +}); +``` + +在复杂的工作流中,当外部资源对key的操作变更顺序有要求的时候,Caffeine 提供了实现的扩展点。对于手动操作,[Map][concurrent Map] 的 compute 方法提供了执行原子创建、原子更新或原子删除条目操作的能力。当一个元素被自动移除的时候,驱逐监听器可以根据映射关系计算扩展自定义操作。这意味着在缓存中,当一个key的写入操作在完成之前,后续其他写操作都是阻塞的,同时在这段时间内,尝试获取这个key对应的缓存元素的时候获取到的也将都是旧值。 + +### 可能的使用场景 + +#### 写模式 + +计算可以作为实现 write-through 和 write-back 两种模式的缓存的方式。 + +在一个 *write-through* 模式下的缓存里,操作都将是同步的并且缓存的变更只有在Writer中更新成功才会生效。这避免了缓存更新与外部资源更新都是独立的原子操作的时候的资源竞争。 + +在一个 *write-back* 模式下的缓存里,在缓存更新之后将会异步执行外部数据源的更新。这会增加数据不一致性的风险,比如在外部数据源更新失败的情况下缓存里的数据将会变得非法。这种方式在一定时间后延迟写,控制写的速率和批量写的场景下将会十分有效。 + +write-back模式下可以实现以下几种特性: +- 批量和合并操作 +- 延迟操作指定的时间窗口 +- 如果批处理的数据量超过阈值大小,那么将在定期刷新之前提前执行批处理操作 +- 如果外部资源操作还没有刷新,则从write-behind缓存当中加载数据 +- 根据外部资源的特性控制重试,速率和并发度 + +可以通过查看这个 [write-behind-rxjava][write-behind-rxjava] 使用了[RxJava][rxjava]的简单demo来进行参考。 + +#### 分层 + +*layered cache* 多级缓存支持从一个数据系统所支持的外部缓存中加载和写入。这允许构建一个数据量更小速度也更快的缓存将数据回落到一个数据量更大但是速度较慢的大缓存中。典型的多级缓存包含堆外缓存,基于文件的缓存和远程的缓存。 + +*victim cache* 是多级缓存的一种变种,它将把被驱逐的数据写入到二级缓存当中去。 `delete(K, V, RemovalCause)`方法支持查看被移除缓存的具体信息和移除原因。 + +#### 同步监听器 + +一个 *synchronous listener* 同步监听器将会按照指定key的操作顺序接收到相应的事件。这个监听器可以用来阻塞该操作也可以把这次操作事件投入到队列中被异步处理。这种类型的监听器可以用来作为备份或者构造一个分布式缓存。 + +[concurrent-map]: https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ConcurrentMap.html +[rxjava]: https://github.com/ReactiveX/RxJava +[write-behind-rxjava]: https://github.com/ben-manes/caffeine/tree/master/examples/write-behind-rxjava \ No newline at end of file diff --git a/Contribute-zh-CN.md b/Contribute-zh-CN.md index 3afbe78..281bfd2 100644 --- a/Contribute-zh-CN.md +++ b/Contribute-zh-CN.md @@ -1,9 +1,6 @@ -在一开始,请先[sign the Contributor License Agreement](https://www.clahub.com/agreements/ben-manes/caffeine)。 +在一开始,请先[sign the Contributor License Agreement](https://cla-assistant.io/ben-manes/caffeine)。 #### IDE - -Eclipse用户需要先安装 [Gradle STS plugin](http://marketplace.eclipse.org/content/gradle-ide-pack) 或者官方插件 [Buildship plugin](http://marketplace.eclipse.org/content/buildship-gradle-integration) (正在开发中)。这些插件支持导入Gradle项目。当使用STS插件时,选择`Use hierarchical project names`。 - 这个项目使用代码生成技术来配置缓存的特定配置减少内存开销。但是不幸的是,IDE可能没有意识到需要在编译的时候执行这个任务。你需要在命令行中重新生成并导入,使源文件在你的项目下被生成。 ```gradle @@ -62,6 +59,6 @@ public final class CacheTest { ``` ### 性能分析工具 -![YourKit](http://www.yourkit.com/images/yklogo.png) +[![YourKit](http://www.yourkit.com/images/yklogo.png)](http://www.yourkit.com) [![JProfiler](https://www.ej-technologies.com/images/product_banners/jprofiler_large.png)](https://www.ej-technologies.com/products/jprofiler/overview.html) -[YourKit](http://www.yourkit.com) 为开源项目提供了所有特性的Java剖析工具。 \ No newline at end of file +[YourKit](http://www.yourkit.com) & [JProfiler](https://www.ej-technologies.com) 为开源项目提供了所有特性的Java剖析工具。 \ No newline at end of file diff --git a/Interner-zh-CN.md b/Interner-zh-CN.md new file mode 100644 index 0000000..8b679be --- /dev/null +++ b/Interner-zh-CN.md @@ -0,0 +1,27 @@ +_在3.1.0版本中引入_ + +```java +Interner interner = Interner.newStrongInterner(); +String s1 = interner.intern(new String("value")); +String s2 = interner.intern(new String("value")); +assert s1 == s2 +``` + +一个 `Interner` 将会返回给定元素的标准规范形式。当调用 `intern(e)` 方法时,如果该 interner 已经包含一个由 [Object.equals][equals] +方法确认相等的对象的时候,将会返回其中已经包含的对象。否则将会新添加该对象返回。这种重复数据删除通常用于共享规范实例来减少内存使用。 + +```java +LoadingCache graphs = Caffeine.newBuilder() + .weakKeys() + .build(key -> createExpensiveGraph(key)); +Interner interner = Interner.newWeakInterner(); + +Key canonical = interner.intern(key); +Graph graph = cache.get(canonical); +``` + +一个弱引用的 interner 允许其中的内部对象在没有别的强引用的前提下在被 gc 回收。与 caffeine 的 `Caffeine.weakKeys()` 不同的是, +这里通过 (==) 来进行比较而不是 `equals()`。这样可以确保应用程序所引用的 interner 中的值正是缓存中的实际实例。否则在缓存当中的所 +持有的弱引用 key 很有可能是与应用程序所持有的实例不同的实例,这会导致这个 key 会在 gc 的时候被提早从缓存当中被淘汰。 + +[equals]: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/Object.html#equals(java.lang.Object) \ No newline at end of file diff --git a/Policy-zh-CN.md b/Policy-zh-CN.md index 82a5a46..a3e1875 100644 --- a/Policy-zh-CN.md +++ b/Policy-zh-CN.md @@ -18,7 +18,7 @@ cache.policy().eviction().ifPresent(eviction -> { cache.policy().expireAfterAccess().ifPresent(expiration -> ...); cache.policy().expireAfterWrite().ifPresent(expiration -> ...); cache.policy().expireVariably().ifPresent(expiration -> ...); -cache.policy().refreshAfterWrite().ifPresent(expiration -> ...); +cache.policy().refreshAfterWrite().ifPresent(refresh -> ...); ``` `ageOf(key, TimeUnit)`方法提供了查看缓存元素在`expireAfterAccess`,`expireAfterWrite`或者 `refreshAfterWrite` 策略下的空闲时间的途径。缓存中的元素最大可持续时间可以通过`getExpiresAfter(TimeUnit)`方法获取,并且可以通过`setExpiresAfter(long, TimeUnit)`方法来进行调整。 diff --git a/Refresh-zh-CN.md b/Refresh-zh-CN.md index 7d7bf49..dae44b2 100644 --- a/Refresh-zh-CN.md +++ b/Refresh-zh-CN.md @@ -13,7 +13,7 @@ LoadingCache graphs = Caffeine.newBuilder() 更新操作将会异步执行在一个[Executor][2]上。默认的线程池实现是[ForkJoinPool.commonPool()][3]当然也可以通过覆盖`Caffeine.executor(Executor)`方法自定义线程池的实现。 -在刷新的过程中,如果抛出任何异常,都会使旧值被保留,并且异常将会被打印日志 (通过[Logger][1])并被吞食。 +在刷新的过程中,如果抛出任何异常,都会使旧值被保留,并且异常将会被打印日志 (通过 [System.Logger][1] )并被吞食。 [1]: http://docs.oracle.com/javase/8/docs/api/java/util/logging/package-summary.html [2]: http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Executor.html diff --git a/Removal-zh-CN.md b/Removal-zh-CN.md index fd4c596..327215d 100644 --- a/Removal-zh-CN.md +++ b/Removal-zh-CN.md @@ -18,14 +18,16 @@ cache.invalidateAll() ### 移除监听器 ```java Cache graphs = Caffeine.newBuilder() + .evictionListener((Key key, Graph graph, RemovalCause cause) -> + System.out.printf("Key %s was evicted (%s)%n", key, cause)) .removalListener((Key key, Graph graph, RemovalCause cause) -> System.out.printf("Key %s was removed (%s)%n", key, cause)) .build(); ``` -你可以为你的缓存通过`Caffeine.removalListener(RemovalListener)`方法定义一个移除监听器在一个元素被移除的时候进行相应的操作。`RemovalListener` 可以获得key,缓存元素value和被移除原因`RemovalCause`。 +你可以为你的缓存通过`Caffeine.removalListener(RemovalListener)`方法定义一个移除监听器在一个元素被移除的时候进行相应的操作。这些操作是使用 [Executor][2] 异步执行的,其中默认的 Executor 实现是 [ForkJoinPool.commonPool()][3] 并且可以通过覆盖`Caffeine.executor(Executor)`方法自定义线程池的实现。 -移除监听器监听器的操作将会异步执行在一个[Executor][2]上。默认的线程池实现是[ForkJoinPool.commonPool()][3]当然也可以通过覆盖`Caffeine.executor(Executor)`方法自定义线程池的实现。当移除之后的自定义操作必须要同步执行的时候,你需要使用 [CacheWriter](Writer)作为代替。 +当移除之后的自定义操作必须要同步执行的时候,你需要使用 `Caffeine.evictionListener(RemovalListener)` 。这个监听器将在 `RemovalCause.wasEvicted()` 为 true 的时候被触发。为了移除操作能够明确生效, `Cache.asMap()` 提供了方法来执行原子操作。 记住任何在 `RemovalListener`中被抛出的异常将会被打印日志 (通过[Logger][1])并被吞食。 diff --git a/Roadmap-zh-CN.md b/Roadmap-zh-CN.md index e978e3a..1786785 100644 --- a/Roadmap-zh-CN.md +++ b/Roadmap-zh-CN.md @@ -1,26 +1,7 @@ -### 当前 -当前的重点是帮助用户使用该代码库并将用户的想法移植到自定义的方案中。特别是对以下几点保持强烈的兴趣, -* 解决缺陷并听取用户反馈 -* 集成到流行的框架中 (比如Spring) -* 被数据库和搜索产品所采用 (比如 HBase,Solr) -* 为移植到其他系统提供咨询 (比如 Postgres,Cassandra's off-heap) - -### Version 2.x -* 在dedicated queue中维持0权重元素 -* 为批量更新添加CacheLoader的`reloadAll`API ([#7](https://github.com/ben-manes/caffeine/issues/7)) -* 为`AsyncLoadingCache`添加`Map>`视图 ([#156](https://github.com/ben-manes/caffeine/issues/156)) - -### Version 3.0 -* 移除过时的类和方法 -* JDK 9: 将`sun.misc.Unsafe` 的用法迁移至 [VarHandles](http://openjdk.java.net/jeps/193) -* `refresh`时future使调用方能够阻碍在重载时阻塞([143](https://github.com/ben-manes/caffeine/issues/143)) -* 当元素不处于缓存当中时主动通过CacheWriter的 `delete`方法使其失效 -* 重写JCache过期部分使得`eager`和`lazy`配置合并到一起 -* 使`CacheStats` value-type ready by removing constructor (a la `Optional`) -* 使用JDK9'的shared scheduler进行主动过期 ([#195](https://github.com/ben-manes/caffeine/issues/195)) -* 适配TinyLFU策略 ([106](https://github.com/ben-manes/caffeine/issues/106)) +### 目前 +* 在 maintain 队列中维护零权重元素 +* 查看 [Issues](https://github.com/ben-manes/caffeine/issues) 列表来查看其他正在开发中的功能 ### 未来 - -* Future JDKs - * JDK 11: 思考方法如何更好利用[Value Types](http://openjdk.java.net/jeps/169) \ No newline at end of file +* 思考利用 [Value Types](http://openjdk.java.net/jeps/169) 的最佳方案 +* 将线程池的默认实现迁移到 [virtual threads](https://cr.openjdk.java.net/~rpressler/loom/Loom-Proposal.html) \ No newline at end of file diff --git a/Specification-zh-CN.md b/Specification-zh-CN.md index 0b86f93..5f6e7e7 100644 --- a/Specification-zh-CN.md +++ b/Specification-zh-CN.md @@ -21,7 +21,7 @@ LoadingCache graphs = Caffeine.from(spec) - softValues: 相当于配置 `Caffeine.softValues` - recordStats: 相当于配置 `Caffeine.recordStats` -持续时间可以通过在一个integer类型之后跟上一个"d","h","m",或者"s"来分别表示天,小时,分钟或者秒。另外,从2.8.7版本开始,ISO-8601标准的字符串也将被支持来配置持续时间,并通过[Duration.parse](https://docs.oracle.com/javase/8/docs/api/java/time/Duration.html#parse-java.lang.CharSequence-)来进行解析。出于表示缓存持续时间的目的,这里不支持配置负的持续时间,并将会抛出异常。两种持续时间表示格式的示例如下所示。 +持续时间可以通过在一个integer类型之后跟上一个"d","h","m",或者"s"来分别表示天,小时,分钟或者秒。另外,ISO-8601标准的字符串也被支持来配置持续时间,并通过[Duration.parse](https://docs.oracle.com/javase/8/docs/api/java/time/Duration.html#parse-java.lang.CharSequence-)来进行解析。出于表示缓存持续时间的目的,这里不支持配置负的持续时间,并将会抛出异常。两种持续时间表示格式的示例如下所示。 | 普通 | ISO-8601 | 描述 |--------|:--------|:------------| diff --git a/Statistics-zh-CN.md b/Statistics-zh-CN.md index 3ff0cfd..26ae8d6 100644 --- a/Statistics-zh-CN.md +++ b/Statistics-zh-CN.md @@ -14,14 +14,14 @@ Cache graphs = Caffeine.newBuilder() 这些缓存统计指标可以被基于push/pull模式的报告系统进行集成。基于pull模式的系统可以通过调用`Cache.stats()` 方法获取当前缓存最新的统计快照。一个基于push的系统可以通过自定义一个`StatsCounter`对象达到在缓存操作发生时自动推送更新指标的目的。 -[stats-metrics][stats-metrics] 是一个通过[Dropwizard Metrics][metrics]实现的简单例子。 +如果使用 [Dropwizard Metrics][metrics] 的话建议查看 [metrics-caffeine][metrics-caffeine] 。 如果使用[Prometheus][prometheus]的话可以尝试 [simpleclient-caffeine][simpleclient-caffeine]。 如果实在难以选择的话可以尝试通过[Micrometer][micrometer]来进行整合。 [metrics]: http://metrics.dropwizard.io -[stats-metrics]: https://github.com/ben-manes/caffeine/tree/master/examples/stats-metrics +[metrics-caffeine]: https://github.com/dropwizard/metrics/tree/release/4.1.x/metrics-caffeine [simpleclient-caffeine]: https://github.com/prometheus/client_java#caches [prometheus]: https://prometheus.io [micrometer]: http://micrometer.io \ No newline at end of file diff --git a/Writer-zh-CN.md b/Writer-zh-CN.md deleted file mode 100644 index 987f957..0000000 --- a/Writer-zh-CN.md +++ /dev/null @@ -1,55 +0,0 @@ -```java -LoadingCache graphs = Caffeine.newBuilder() - .writer(new CacheWriter() { - @Override public void write(Key key, Graph graph) { - // 持久化或者次级缓存 - } - @Override public void delete(Key key, Graph graph, RemovalCause cause) { - // 从持久化或者次级缓存中删除 - } - }) - .build(key -> createExpensiveGraph(key)); -``` - -`CacheWriter`给缓存提供了充当底层资源的门面的能力,当其与`CacheLoader`一起使用的时候,所有的读和写操作都可以通过缓存向下传播。Writers提供了原子性的操作,包括从外部资源同步的场景。这意味着在缓存中,当一个key的写入操作在完成之前,后续其他写操作都是阻塞的,同时在这段时间内,尝试获取这个key对应的缓存元素的时候获取到的也将都是旧值。如果写入失败那么之前的旧值将会被保留同时异常将会被传播给调用者。 - -`CacheWriter`将会在缓存元素被创建,更新或者移除的时候被触发。但是当一个映射被加载(比如`LoadingCache.get`),重载 (比如`LoadingCache.refresh`),或者生成 (比如 `Map.computeIfPresent`) 的时候将不会被触发。 - -`CacheWriter` 将不能与 `weak keys` 或者`AsyncLoadingCache`在一起使用。 - -### 可能的使用场景 -在复杂的工作流中,当外部资源对key的操作变更顺序有要求的时候,`CacheWriter`可以作为其的一个扩展点。Caffeine可以支持以下几种场景,但是并不是原生支持。 - -#### 写模式 - -一个`CacheWriter`可以用来实现write-through和write-back两种模式的缓存。 - -在一个*write-through*模式下的缓存里,操作都将是同步的并且缓存的变更只有在Writer中更新成功才会生效。这避免了缓存更新与外部资源更新都是独立的原子操作的时候的资源竞争。 - -在一个*write-back*模式下的缓存里,在缓存更新之后将会异步执行外部数据源的更新。这会增加数据不一致性的风险,比如在外部数据源更新失败的情况下缓存里的数据将会变得非法。这种方式在一定时间后延迟写,控制写的速率和批量写的场景下将会十分有效。 - -write-back模式下可以实现以下几种特性: -- 批量和合并操作 -- 延迟操作指定的时间窗口 -- 如果批处理的数据量超过阈值大小,那么将在定期刷新之前提前执行批处理操作 -- 如果外部资源操作还没有刷新,则从write-behind缓存当中加载数据 -- 根据外部资源的特性控制重试,速率和并发度 - -可以通过查看这个 [write-behind-rxjava][write-behind-rxjava] 使用了[RxJava][rxjava]的简单demo来进行参考。 - -#### 分层 - -一个`CacheWriter` 可以用来整合多级缓存。 - -*layered cache* 多级缓存支持从一个数据系统所支持的外部缓存中加载和写入。这允许构建一个数据量更小速度也更快的缓存将数据回落到一个数据量更大但是速度较慢的大缓存中。典型的多级缓存包含堆外缓存,基于文件的缓存和远程的缓存。 - -*victim cache* 是多级缓存的一种变种,它将把被驱逐的数据写入到二级缓存当中去。 `delete(K, V, RemovalCause)`方法支持查看被移除缓存的具体信息和移除原因。 - -#### 同步监听器 - -`CacheWriter` 可以用来作为发布同步监听器使用。 - -一个*synchronous listener* 同步监听器将会按照指定key的操作顺序接收到相应的事件。这个监听器可以用来阻塞该操作也可以把这次操作事件投入到队列中被异步处理。这种类型的监听器可以用来作为备份或者构造一个分布式缓存。 - -[rxjava]: https://github.com/ReactiveX/RxJava -[write-behind-rxjava]: https://github.com/ben-manes/caffeine/tree/master/examples/write-behind-rxjava \ No newline at end of file diff --git a/_Sidebar.md b/_Sidebar.md index fe6c6b5..ae8d69e 100644 --- a/_Sidebar.md +++ b/_Sidebar.md @@ -9,7 +9,8 @@ * [驱逐](https://github.com/ben-manes/caffeine/wiki/Eviction-zh-CN) * [移除](https://github.com/ben-manes/caffeine/wiki/Removal-zh-CN) * [刷新](https://github.com/ben-manes/caffeine/wiki/Refresh-zh-CN) - * [Writer](https://github.com/ben-manes/caffeine/wiki/Writer-zh-CN) + * [计算](https://github.com/ben-manes/caffeine/wiki/Compute-zh-CN) + * [Interner](https://github.com/ben-manes/caffeine/wiki/Interner-zh-CN) * [统计](https://github.com/ben-manes/caffeine/wiki/Statistics-zh-CN) * [规范](https://github.com/ben-manes/caffeine/wiki/Specification-zh-CN) * [清理](https://github.com/ben-manes/caffeine/wiki/Cleanup-zh-CN)