Friday, 21 July 2017

Cluster Counter

In Infinispan 9.1 we introduce the clustered counters. It is a counter distributed and shared among all nodes in the cluster and today we are going to know more about it.

Installation

To use a counter in your Infinispan cluster, first you have to add the maven dependency to your project. As you can see, it is simple as doing:

<dependency>
<groupId>org.infinispan</groupId>
<artifactId>infinispan-clustered-counter</artifactId>
<version>9.1.0.Final</version>
</dependency>
view raw pom.xml hosted with ❤ by GitHub
After adding the module, you can retrieve the CounterManager and start creating and using counters.

CounterManager

Each CounterManager is associated to a CacheManager. But, before showing how to use it, first we have some configuration to be done.

There are two attributes that you can configure: The num-owner - that represents the number of copies of the counter's value in a cluster; and the reliability - that represents the behavior of the counters in case of partitions.

Below, is the configuration example with their default values.
XML:
<?xml version="1.0" encoding="UTF-8"?>
<infinispan>
<cache-container ...>
<!-- if you need to persist counter (i.e. the counter's value survive cluster restarts), global state needs to be configured -->
<global-state>
...
</global-state>
<!-- your caches configuration goes here -->
<counters xmlns="urn:infinispan:config:counters:9.1" num-owners="2" reliability="AVAILABLE"/>
</cache-container>
</infinispan>
Programatically:
// Setup up a clustered cache manager
GlobalConfigurationBuilder global = GlobalConfigurationBuilder.defaultClusteredBuilder();
// Create the counter configuration manager builder
CounterManagerConfigurationBuilder counterBuilder = global.addModule(CounterManagerConfigurationBuilder.class);
counterBuilder.numOwner(2).reliability(Reliability.AVAILABLE);
Then, you can retrieve the CounterManager from the CacheManager, as shown below, and start using the counters!
// Initialize the cache manager
DefaultCacheManager cacheManager = new DefaultCacheManager(/*your configuration*/);
// Retrieve the CounterManager from the CacheManager. Each CacheManager has it own CounterManager
CounterManager counterManager = EmbeddedCounterManagerFactory.asCounterManager(cacheManager);

Counter

A counter is identified by its name. Also, it is initialized with an initial value (by default 0) and it can be persisted, if the value needs to survive a cluster restart.

There are 2 types of counters: strong and weak counters.

Strong Counters

The strong counter provides higher consistency. Its value is known during the update and its updates are applied atomically. This allows to set boundaries and provides conditional operation (as compare-and-set).

Configuration

A strong counter can be configured in the configuration file or programatically. They can be also created dynamically at runtime. Below shows us how it can be done:

XML:
<counters xmlns="urn:infinispan:config:counters:9.1" ...>
<!-- only the name is mandatory! -->
<strong-counter name="my-counter" initial-value="0" storage="VOLATILE">
<!-- only set an upper-bound or lower-bound if you want to apply boundaries! -->
<lower-bound value="0"/>
<upper-bound value="10"/>
</strong-counter>
</counters>
Programatically:
// Create the counter configuration builder
CounterManagerConfigurationBuilder counterBuilder = ...
// Bounded counter from 0 to 10.
// Only the name() is mandatory.
// Do not invoke the lowerBound() or upperBound() if you want an unbounded counter.
counterBuilder.addStrongCounter().name("my-counter").initialValue(0).storage(Storage.VOLATILE).lowerBound(0).upperBound(10);
Runtime:
// Retrieve the CounterManager
CounterManager counterManager = ,..;
// Defines an unbounded counter.
// initialValue() and storage() are optional.
manager.defineCounter("my-counter", CounterConfiguration.builder(CounterType.UNBOUNDED_STRONG).initialValue(0).storage(Storage.VOLATILE)build());
// Defines a bounded counter.
// Sets the boundaries with lowerBound() and/or upperBound(). If they are not set, Long.MIN_VALUE and Long.MAX_VALUE will be used.
// initialValue() and storage() are optional.
manager.defineCounter("my-bounded-counter", CounterConfiguration.builder(CounterType.BOUNDED_STRONG).initialValue(0).lowerBound(0).upperBound(10).storage(Storage.VOLATILE).build());

Use Case

The strong counter fits the following uses cases:
  • Global Id Generator
Due to its strong consistency, it can be used as a global identifier generator, as in the example below:
public long generateId() throws ExecutionException, InterruptedException {
return strongCounter.incrementAndGet().get();
}

  • Rate Limiter
If bounded, it can be used as a simple rate limiter. Just don't forget to invoke reset()...
public void handleRequest() throws InterruptedException, ExecutionException {
try {
strongCounter.incrementAndGet().get();
// you can proceed with the request
} catch (ExecutionException e) {
if (e.getCause() instanceof CounterOutOfBoundsException) {
// unable to handle request. Limit reached.
} else {
throw e;
}
}
}
// Invoked every X seconds
public void reset() {
strongCounter.reset();
}

  • Simply count "stuff"
Well, it is a counter after all...
public long receiveApple() throws ExecutionException, InterruptedException {
return appleCounter.incrementAndGet().whenComplete((nApples, throwable) -> System.out.println("Now I have " + nApples + " apples!")).get();
}
public long countApples() throws ExecutionException, InterruptedException {
return appleCounter.getValue().get();
}

Weak Counters

The weak counter provides eventual consistency and its value is not known during updates. It provides faster writes when comparing with the strong counter.

Configuration

As in strong counter, the weak counter can be configure its name and its initial value. In addition, a concurrency-level can be configure to set the number of concurrent updates that can be handled in parallel. Below shows us how to configure it:

XML:
<counters xmlns="urn:infinispan:config:counters:9.1" ...>
<!-- only the name is mandatory! -->
<weak-counter name="my-counter" initial-value="5" storage="VOLATILE" concurrency-level="1024"/>
</counters>
Programatically:
// Create CounterManagerConfigurationBuilder
CounterManagerConfigurationBuilder counterBuilder = ...
// Only name() is mandatory
counterBuilder.addWeakCounter().name("my-counter").storage(Storage.VOLATILE).concurrencyLevel(1024);
Runtime:
// Retrieve CounterManager
CounterManager counterManager = ...
// initialValue(), storage() and concurrencyLevel() are optional.
counterManager.defineCounter("my-counter", CounterConfiguration.builder(CounterType.WEAK).initialValue(5).storage(Storage.VOLATILE).concurrencyLevel(1024).build());

Use Case

The main use case for the weak counter includes all scenarios where its value isn't needed while updating the counter. For example, it can be used to count the number of visits to some resource:
public void visitPage() {
visitorCounter.increment();
}
public long getNumberOfVisitors() {
return visitorCounter.getValue();
}


For more information, take a look at the documentation. If you have any feedback, or would like to request some new features, or found some issue, let us know via the forumissue tracker or the #infinispan channel on Freenode.

No comments:

Post a Comment