如何可靠地从 Guava LoadingCache 中删除记录?
How to reliably drop records from Guava LoadingCache?
我正在使用 Guava LoadingCache
向其中填充一些数据,我想每 1 分钟从 LoadingCache
中删除所有条目。
public class MetricHolder {
private final ExecutorService executor = Executors.newFixedThreadPool(2);
private final LoadingCache<String, AtomicLongMap<String>> clientIdMetricCounterCache =
CacheBuilder.newBuilder().expireAfterWrite(1, TimeUnit.MINUTES)
.removalListener(RemovalListeners.asynchronous(new SendToDatabase(), executor))
.build(new CacheLoader<String, AtomicLongMap<String>>() {
@Override
public AtomicLongMap<String> load(String key) throws Exception {
return AtomicLongMap.create();
}
});
private static class Holder {
private static final MetricHolder INSTANCE = new MetricHolder();
}
public static MetricHolder getInstance() {
return Holder.INSTANCE;
}
private MetricHolder() {}
public void increment(String clientId, String name) throws ExecutionException {
clientIdMetricCounterCache.get(clientId).incrementAndGet(name);
}
public LoadingCache<String, AtomicLongMap<String>> getClientIdMetricCounterCache() {
return clientIdMetricCounterCache;
}
private static class SendToDatabase implements RemovalListener<String, AtomicLongMap<String>> {
@Override
public void onRemoval(RemovalNotification<String, AtomicLongMap<String>> notification) {
String key = notification.getKey();
AtomicLongMap<String> value = notification.getValue();
System.out.println(key);
System.out.println(value);
// sending these key/value to some other system
}
}
}
我正在以多线程方式从代码中的许多不同位置调用 increment
方法。因此,在 1 分钟的时间内,它将在 clientIdMetricCounterCache
中填充大量指标。现在我想每 1 分钟 可靠地 删除所有这些指标,并将所有这些指标发送到数据库。
在我的例子中,有时写入 increment
方法可能非常慢,但我仍然想每 1 分钟删除所有这些条目并且我根本没有对该缓存进行任何读取,只是写入它然后通过发送到其他系统来删除这些记录。下面是我在Guava看到的wiki
Caches built with CacheBuilder do not perform cleanup and evict values
"automatically," or instantly after a value expires, or anything of
the sort. Instead, it performs small amounts of maintenance during
write operations, or during occasional read operations if writes are
rare.
那么 expireAfterWrite
是如何工作的呢?它是否像调度程序一样工作,每 1 分钟 运行 并删除 clientIdMetricCounterCache
中的所有条目,然后它会在 1 分钟后再次唤醒并从同一缓存中删除所有条目,然后继续这样吗?阅读 wiki 后,我怀疑它是那样工作的。如果没有,那么我如何才能每 1 分钟可靠地删除这些记录并发送到其他系统,因为我的写入在一段时间内可能很少见?
看起来我可能必须使用 Guava TimeLimiter
接口和 SimpleTimeLimiter
或者 ScheduledExecutorService
才能可靠地使调用超时然后删除条目?如果是,谁能提供一个例子,这在我当前的例子中是如何工作的?
对我来说,您似乎在滥用缓存,而 Map 可以做到这一点。您没有使用过期、没有大小限制、没有缓存,您只是在收集统计数据。
您使用的唯一功能是加载方面,这真的不值得。
我建议改用 AtomicReference<ConcurrentHashMap<String, AtomicLongMap>>
:
- 更新时,您通过
AtomicReference::get
获取当前分钟的版本。
- 使用
clientId
,您在 ConcurrentHashMap
中查找一个 AtomicLongMap
,如果找不到则创建一个新的(在 Java 中使用 putIfAbsent
7 或 computeIfAbsent
在 Java 8).
- 使用
name
,您更新 AtomicLongMap
就像您发布的那样。
- 每分钟一次,您通过
AtomicReference::getAndSet
替换所有内容。
更换后,您可以确保您的统计数据不会受到干扰,但是,您应该在 getAndSet
之后稍等片刻,因为可能有线程刚刚获得参考并准备写入。
它会比原来的方法产生更多的垃圾,但所有的垃圾都将是短暂的,所以你实际上可能会让 GC 更快乐。
它很简单,不需要深入了解库或其实现细节。
我猜,volatile
而不是 AtomicReference
也可以。
我正在使用 Guava LoadingCache
向其中填充一些数据,我想每 1 分钟从 LoadingCache
中删除所有条目。
public class MetricHolder {
private final ExecutorService executor = Executors.newFixedThreadPool(2);
private final LoadingCache<String, AtomicLongMap<String>> clientIdMetricCounterCache =
CacheBuilder.newBuilder().expireAfterWrite(1, TimeUnit.MINUTES)
.removalListener(RemovalListeners.asynchronous(new SendToDatabase(), executor))
.build(new CacheLoader<String, AtomicLongMap<String>>() {
@Override
public AtomicLongMap<String> load(String key) throws Exception {
return AtomicLongMap.create();
}
});
private static class Holder {
private static final MetricHolder INSTANCE = new MetricHolder();
}
public static MetricHolder getInstance() {
return Holder.INSTANCE;
}
private MetricHolder() {}
public void increment(String clientId, String name) throws ExecutionException {
clientIdMetricCounterCache.get(clientId).incrementAndGet(name);
}
public LoadingCache<String, AtomicLongMap<String>> getClientIdMetricCounterCache() {
return clientIdMetricCounterCache;
}
private static class SendToDatabase implements RemovalListener<String, AtomicLongMap<String>> {
@Override
public void onRemoval(RemovalNotification<String, AtomicLongMap<String>> notification) {
String key = notification.getKey();
AtomicLongMap<String> value = notification.getValue();
System.out.println(key);
System.out.println(value);
// sending these key/value to some other system
}
}
}
我正在以多线程方式从代码中的许多不同位置调用 increment
方法。因此,在 1 分钟的时间内,它将在 clientIdMetricCounterCache
中填充大量指标。现在我想每 1 分钟 可靠地 删除所有这些指标,并将所有这些指标发送到数据库。
在我的例子中,有时写入 increment
方法可能非常慢,但我仍然想每 1 分钟删除所有这些条目并且我根本没有对该缓存进行任何读取,只是写入它然后通过发送到其他系统来删除这些记录。下面是我在Guava看到的wiki
Caches built with CacheBuilder do not perform cleanup and evict values "automatically," or instantly after a value expires, or anything of the sort. Instead, it performs small amounts of maintenance during write operations, or during occasional read operations if writes are rare.
那么 expireAfterWrite
是如何工作的呢?它是否像调度程序一样工作,每 1 分钟 运行 并删除 clientIdMetricCounterCache
中的所有条目,然后它会在 1 分钟后再次唤醒并从同一缓存中删除所有条目,然后继续这样吗?阅读 wiki 后,我怀疑它是那样工作的。如果没有,那么我如何才能每 1 分钟可靠地删除这些记录并发送到其他系统,因为我的写入在一段时间内可能很少见?
看起来我可能必须使用 Guava TimeLimiter
接口和 SimpleTimeLimiter
或者 ScheduledExecutorService
才能可靠地使调用超时然后删除条目?如果是,谁能提供一个例子,这在我当前的例子中是如何工作的?
对我来说,您似乎在滥用缓存,而 Map 可以做到这一点。您没有使用过期、没有大小限制、没有缓存,您只是在收集统计数据。
您使用的唯一功能是加载方面,这真的不值得。
我建议改用 AtomicReference<ConcurrentHashMap<String, AtomicLongMap>>
:
- 更新时,您通过
AtomicReference::get
获取当前分钟的版本。 - 使用
clientId
,您在ConcurrentHashMap
中查找一个AtomicLongMap
,如果找不到则创建一个新的(在 Java 中使用putIfAbsent
7 或computeIfAbsent
在 Java 8). - 使用
name
,您更新AtomicLongMap
就像您发布的那样。 - 每分钟一次,您通过
AtomicReference::getAndSet
替换所有内容。
更换后,您可以确保您的统计数据不会受到干扰,但是,您应该在 getAndSet
之后稍等片刻,因为可能有线程刚刚获得参考并准备写入。
它会比原来的方法产生更多的垃圾,但所有的垃圾都将是短暂的,所以你实际上可能会让 GC 更快乐。
它很简单,不需要深入了解库或其实现细节。
我猜,volatile
而不是 AtomicReference
也可以。