Generate Sequences with Infinispan Clustered Counters

In this tutorial we will learn how to use Clustered Counters with Infinispan 9.1.0. Cluster Counters are an useful feature of Infinispan which lets you generate unique identifiers in a cluster.

Typically reliable counters are generated through a Database's Sequence schema but this requires a connection to a Database limiting how the application scales for high throughputs. An Infinispan counter can be a light and reliable alternative. First of all let's start from the configuration.

Configuring a Counter in Infinispan

In this example, we will show an application which runs Infinispan in Library mode, that is, ships the Infinispan libraries as part of the application and configures Infinispan programmatically. Check the documentation (http://infinispan.org/docs/stable/user_guide/user_guide.html#clustered_counters) for an example of configuring Counters into the server's configuration file.

Here is the relevant part of our example application:

public void getCacheContainer() {

	GlobalConfigurationBuilder global = new GlobalConfigurationBuilder().clusteredDefault();

	CounterManagerConfigurationBuilder counterBuilder = global.addModule(CounterManagerConfigurationBuilder.class);
	counterBuilder.numOwner(2).reliability(Reliability.AVAILABLE);

	Configuration loc = new ConfigurationBuilder().jmxStatistics().enable().clustering().cacheMode(CacheMode.LOCAL)
			.locking().isolationLevel(IsolationLevel.REPEATABLE_READ).persistence().passivation(false)
			.addSingleFileStore().purgeOnStartup(true)

			.build(); // Builds the Configuration object

	manager = new DefaultCacheManager(global.build(), loc, true);
	System.out.println("Created DefaultCacheManager using library mode");
	cm = EmbeddedCounterManagerFactory.asCounterManager(manager);
	cm.defineCounter("my-counter", CounterConfiguration.builder(CounterType.BOUNDED_STRONG).initialValue(0)
			.lowerBound(0).upperBound(100).storage(Storage.VOLATILE).build());

}

As you can see, in order to bootstrap our counters we need to add to our Clustered configuration an instance of CounterManagerConfigurationBuilder. Through the CounterManagerConfigurationBuilder you will be able to configure:

  • The number of owners which sets the number of counter’s copies to keep cluster-wide.
  • The Reliability which sets the counter’s update behavior in a network partition. Default value is AVAILABLE which means all partitions are able to read and update the counter’s value. The other option is CONSISTENT which means that only the primary partition will be able to Read/Write the counter’s value.

Strong Counters: which performs update locks on the key therefore it lets retrieve the counter’s value after each update. Example:

StrongCounter aCounter = cm.getStrongCounter("strong-counter");
aCounter.incrementAndGet();

Weak Counters: don't performs update locks on the key therefore it can be used when the result of the update operation is not needed. Example:

WeakCounter aCounter = cm.getWeakCounter("weak-counter");
aCounter.increment().get();

Finally, depending on where the counter is stored, you can have two possible values for Storage:

  • VOLATILE: the counter’s value is only available in memory. The value will be lost when a cluster is shutdown.
  • PERSISTENT: the counter’s value is stored in a private and local persistent store.

Here is the full example of a Servlet, which when invoked generates 10 Strong Counters from a Cache:

package com.mastertheboss.infinispan;

import java.io.IOException;
import java.util.concurrent.ExecutionException;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.infinispan.commons.api.BasicCacheContainer;
import org.infinispan.configuration.cache.CacheMode;
import org.infinispan.configuration.cache.Configuration;
import org.infinispan.configuration.cache.ConfigurationBuilder;
import org.infinispan.configuration.global.GlobalConfigurationBuilder;
import org.infinispan.counter.EmbeddedCounterManagerFactory;
import org.infinispan.counter.api.CounterConfiguration;
import org.infinispan.counter.api.CounterManager;
import org.infinispan.counter.api.CounterType;
import org.infinispan.counter.api.Storage;
import org.infinispan.counter.api.StrongCounter;
import org.infinispan.counter.configuration.CounterManagerConfigurationBuilder;
import org.infinispan.counter.configuration.Reliability;
import org.infinispan.manager.DefaultCacheManager;
import org.infinispan.util.concurrent.IsolationLevel;

@WebServlet("/CounterServlet")
public class CounterServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
	CounterManager cm;


	public CounterServlet() {
	}

	public void init() throws ServletException {
		getCacheContainer();
	}

	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {

		StrongCounter aCounter = cm.getStrongCounter("my-counter");
		try {
			for (int ii = 0; ii < 10; ii++)
				response.getWriter().append("Counter is " + aCounter.incrementAndGet().get());
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (ExecutionException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		// TODO Auto-generated method stub
		doGet(request, response);
	}

	private DefaultCacheManager manager;

	public void getCacheContainer() {

		GlobalConfigurationBuilder global = new GlobalConfigurationBuilder().clusteredDefault();

		CounterManagerConfigurationBuilder counterBuilder = global.addModule(CounterManagerConfigurationBuilder.class);
		counterBuilder.numOwner(2).reliability(Reliability.AVAILABLE);

		Configuration loc = new ConfigurationBuilder().jmxStatistics().enable().clustering().cacheMode(CacheMode.LOCAL)
				.locking().isolationLevel(IsolationLevel.REPEATABLE_READ).persistence().passivation(false)
				.addSingleFileStore().purgeOnStartup(true)

				.build(); // Builds the Configuration object

		manager = new DefaultCacheManager(global.build(), loc, true);
		System.out.println("Created DefaultCacheManager using library mode");
		cm = EmbeddedCounterManagerFactory.asCounterManager(manager);
		cm.defineCounter("my-counter", CounterConfiguration.builder(CounterType.BOUNDED_STRONG).initialValue(0)
				.lowerBound(0).upperBound(100).storage(Storage.VOLATILE).build());

	}
}

Finally, in order to be able to use Counter, you will need to add, besides the Infinispan core dependencies, also the infinispan-clustered-counter in your project:

<dependency>
   <groupId>org.infinispan</groupId>
   <artifactId>infinispan-clustered-counter</artifactId>
   <version>9.1.0.Final</version>
</dependency>

That's all! enjoy using Clustered counters with Infinispan!

Related articles available on mastertheboss.com

Follow us on Twitter