6.3. Counter
计数器可能非常强大(StrongCounter)或弱一致(WeakCounter),它们都由一个名称来标识。它们有一个特定的接口,但它们共享一些逻辑,即它们都是异步的(每个操作会返回一个 CompletableFuture ),提供一个 update 事件,并可以重置为初始值。
如果您不想使用 async API,可以通过 sync () 方法返回同步计数器。API 相同,但没有 CompletableFuture 返回值。
以下方法对两个接口都很常见:
String getName();
CompletableFuture<Long> getValue();
CompletableFuture<Void> reset();
<T extends CounterListener> Handle<T> addListener(T listener);
CounterConfiguration getConfiguration();
CompletableFuture<Void> remove();
SyncStrongCounter sync(); //SyncWeakCounter for WeakCounter
-
getName ()返回计数器名称(identifier)。 -
getValue ()返回当前计数器的值。 -
reset ()允许将计数器的值重置为其初始值。 -
添加Listener ()注册监听程序以接收更新事件。在 Notification 和 Events 部分中有关它的更多详细信息。 -
getConfiguration ()返回计数器所使用的配置。 -
remove ()从集群中移除计数器值。仍可使用实例,并保留监听器。 -
sync ()创建一个同步计数器。
如果在删除后访问计数器,则会重新创建该计数器。
6.3.1. StrongCounter 接口:一致性或绑定很重要时。 复制链接链接已复制到粘贴板!
强大的计数器使用存储在 Data Grid 缓存中的单个密钥来提供所需的一致性。所有更新都在键锁定下执行,以更新其值。另一方面,读取不会获取任何锁定并读取当前值。另外,在这个方案中,它允许绑定计数器值,并提供 compare-and-set/swap 等原子操作。
StrongCounter 可以通过使用 getStrongCounter () 方法从 CounterManager 中检索。例如:
CounterManager counterManager = ...
StrongCounter aCounter = counterManager.getStrongCounter("my-counter");
由于每个操作都会命中一个密钥,因此 StrongCounter 具有更高的争用率。
StrongCounter 接口添加了以下方法:
default CompletableFuture<Long> incrementAndGet() {
return addAndGet(1L);
}
default CompletableFuture<Long> decrementAndGet() {
return addAndGet(-1L);
}
CompletableFuture<Long> addAndGet(long delta);
CompletableFuture<Boolean> compareAndSet(long expect, long update);
CompletableFuture<Long> compareAndSwap(long expect, long update);
-
incrementAndGet ()将计数器递增,并返回新值。 -
将计数器缩减为
decrementAndGet (),并返回新值。 -
addAndGet ()将 delta 添加到计数器的值中,并返回新值。 -
比较AndSet ()和比较AndSwap (),如果当前值是预期的值,则以原子方式设置计数器的值。
完成 CompletableFuture 时,操作被视为已完成。
compare-and-set 和 compare-and-swap 之间的区别在于,如果操作成功,则前者会返回 true,同时稍后返回前面的值。如果返回值与预期相同,则 compare-and-swap 可以成功。
6.3.1.1. bounded StrongCounter 复制链接链接已复制到粘贴板!
绑定后,上述所有更新方法将在达到较低或上限的 CounterOutOfBoundsException 时抛出一个 CounterOutOfBoundsException。例外方法可以检查已达到哪个侧绑定:
public boolean isUpperBoundReached();
public boolean isLowerBoundReached();
6.3.1.2. 使用案例 复制链接链接已复制到粘贴板!
强大的计数器在以下用例中更适合:
- 当每次更新后都需要计数器的值(例如,cluster-wise ids 生成器或序列)
- 当需要绑定的计数器(例如,速率限制器)
6.3.1.3. 使用示例 复制链接链接已复制到粘贴板!
StrongCounter counter = counterManager.getStrongCounter("unbounded_counter");
// incrementing the counter
System.out.println("new value is " + counter.incrementAndGet().get());
// decrement the counter's value by 100 using the functional API
counter.addAndGet(-100).thenApply(v -> {
System.out.println("new value is " + v);
return null;
}).get();
// alternative, you can do some work while the counter is updated
CompletableFuture<Long> f = counter.addAndGet(10);
// ... do some work ...
System.out.println("new value is " + f.get());
// and then, check the current value
System.out.println("current value is " + counter.getValue().get());
// finally, reset to initial value
counter.reset().get();
System.out.println("current value is " + counter.getValue().get());
// or set to a new value if zero
System.out.println("compare and set succeeded? " + counter.compareAndSet(0, 1));
另外,还有另一个使用绑定计数器的示例:
StrongCounter counter = counterManager.getStrongCounter("bounded_counter");
// incrementing the counter
try {
System.out.println("new value is " + counter.addAndGet(100).get());
} catch (ExecutionException e) {
Throwable cause = e.getCause();
if (cause instanceof CounterOutOfBoundsException) {
if (((CounterOutOfBoundsException) cause).isUpperBoundReached()) {
System.out.println("ops, upper bound reached.");
} else if (((CounterOutOfBoundsException) cause).isLowerBoundReached()) {
System.out.println("ops, lower bound reached.");
}
}
}
// now using the functional API
counter.addAndGet(-100).handle((v, throwable) -> {
if (throwable != null) {
Throwable cause = throwable.getCause();
if (cause instanceof CounterOutOfBoundsException) {
if (((CounterOutOfBoundsException) cause).isUpperBoundReached()) {
System.out.println("ops, upper bound reached.");
} else if (((CounterOutOfBoundsException) cause).isLowerBoundReached()) {
System.out.println("ops, lower bound reached.");
}
}
return null;
}
System.out.println("new value is " + v);
return null;
}).get();
compare-and-set vs Compare-and-swap 示例:
StrongCounter counter = counterManager.getStrongCounter("my-counter");
long oldValue, newValue;
do {
oldValue = counter.getValue().get();
newValue = someLogic(oldValue);
} while (!counter.compareAndSet(oldValue, newValue).get());
使用 compare-and-swap,它会保存一个调用计数器调用(counter.getValue())
StrongCounter counter = counterManager.getStrongCounter("my-counter");
long oldValue = counter.getValue().get();
long currentValue, newValue;
do {
currentValue = oldValue;
newValue = someLogic(oldValue);
} while ((oldValue = counter.compareAndSwap(oldValue, newValue).get()) != currentValue);
6.3.2. WeakCounter 接口:需要速度时 复制链接链接已复制到粘贴板!
WeakCounter 将计数器的值存储在 Data Grid 缓存中的多个键中。创建的键数量由 concurrency-level 属性配置。每个键存储计数器值的部分状态,并可同时更新。相对于 StrongCounter 的主要优点是缓存中的竞争较低。另一方面,读取其值更为昂贵,不允许绑定。
应谨慎处理重置操作。它不是 原子的,它会生成中间值。读取操作以及注册的任何监听器都可以看到这些值。
可以使用 get 方法从 WeakCounter ()CounterManager 中检索 WeakCounter。例如:
CounterManager counterManager = ...
StrongCounter aCounter = counterManager.getWeakCounter("my-counter);
6.3.2.1. 弱计数器接口 复制链接链接已复制到粘贴板!
WeakCounter 添加以下方法:
default CompletableFuture<Void> increment() {
return add(1L);
}
default CompletableFuture<Void> decrement() {
return add(-1L);
}
CompletableFuture<Void> add(long delta);
它们与 'StrongCounter 的方法类似,但它们不会返回新值。
6.3.2.2. 使用案例 复制链接链接已复制到粘贴板!
当不需要更新操作的结果或不需要计数器的值时,弱计数器最适合于用例中。收集统计数据是此类用例的良好示例。
6.3.2.3. 例子 复制链接链接已复制到粘贴板!
以下是弱计数器用法的示例。
WeakCounter counter = counterManager.getWeakCounter("my_counter");
// increment the counter and check its result
counter.increment().get();
System.out.println("current value is " + counter.getValue());
CompletableFuture<Void> f = counter.add(-100);
//do some work
f.get(); //wait until finished
System.out.println("current value is " + counter.getValue().get());
//using the functional API
counter.reset().whenComplete((aVoid, throwable) -> System.out.println("Reset done " + (throwable == null ? "successfully" : "unsuccessfully"))).get();
System.out.println("current value is " + counter.getValue().get());