Remote Counters

What You Will Learn

How to create and use distributed counters: bounded strong counters with upper limits, unbounded strong counters with compare-and-set operations, and weak counters optimized for fast increments.

Prerequisites

  • Java 17+

  • An Infinispan Server running on localhost:11222 (or Docker/Podman available for Testcontainers)

Start an Infinispan Server with Docker or Podman:

docker run -it --rm -p 11222:11222 -e USER=admin -e PASS=password quay.io/infinispan/server:latest
Tip
You can replace docker with podman in the command above if you use Podman.
Tip
If no server is running, the tutorial code automatically starts an Infinispan Server using Testcontainers.

Step 1: Connect and Create a Bounded Strong Counter

Connect to the server, obtain a CounterManager, and define a counter with an upper bound. Attempts to exceed the bound throw an exception:

      // Connect to the server and obtain the CounterManager
      cacheManager = TutorialsConnectorHelper.connect();
      counterManager = RemoteCounterManagerFactory.asCounterManager(cacheManager);

      // Create a bounded strong counter with an upper bound of 10
      counterManager.defineCounter("counter-1", CounterConfiguration.builder(CounterType.BOUNDED_STRONG)
              .upperBound(10)
              .initialValue(1)
              .build());

      // StrongCounter provides the higher consistency. Its value is known during the increment/decrement and it may be bounded.
      // Bounded counters are aimed for uses cases where a limit is needed.
      counter1 = counterManager.getStrongCounter("counter-1");
      // All methods returns a CompletableFuture. So you can do other work while the counter value is being computed.
      counter1.getValue().thenAccept(value -> System.out.println("Counter-1 initial value is " + value)).get();

Step 2: Create an Unbounded Strong Counter

Unbounded strong counters support compare-and-set, listeners, and reset:

      // Similar to counter-1, counter-2 is a strong counter but it is unbounded. It will never throw the CounterOutOfBoundsException
      counterManager.defineCounter("counter-2", CounterConfiguration.builder(CounterType.UNBOUNDED_STRONG)
              .initialValue(2)
              .build());

      counter2 = counterManager.getStrongCounter("counter-2");

      // All counters allow a listener to be registered.
      // The handle can be used to remove the listener
      counter2.addListener(event -> System.out
              .println("Counter-2 event: oldValue=" + event.getOldValue() + " newValue=" + event.getNewValue()));

      // Adding MAX_VALUE won't throws an exception. But the all the increments won't have any effect since we can store
      //any value larger the MAX_VALUE
      counter2.addAndGet(Long.MAX_VALUE).thenAccept(aLong -> System.out.println("Counter-2 value is " + aLong)).get();

      // Conditional operations are allowed in strong counters
      counter2.compareAndSet(Long.MAX_VALUE, 0)
              .thenAccept(aBoolean -> System.out.println("Counter-2 CAS result is " + aBoolean)).get();
      counter2.getValue().thenAccept(value -> System.out.println("Counter-2 value is " + value)).get();

      // Reset the counter to its initial value (2)
      counter2.reset().get();
      counter2.getValue().thenAccept(value -> System.out.println("Counter-2 initial value is " + value)).get();

Step 3: Create a Weak Counter

Weak counters trade consistency for speed. Their value is computed lazily:

      // And finally, the third counter is a weak counter.
      counterManager.defineCounter("counter-3", CounterConfiguration.builder(CounterType.WEAK)
              .initialValue(3)
              .build());

      // Retrieve counter-3
      counter3 = counterManager.getWeakCounter("counter-3");
      // Weak counter doesn't have its value available during updates. This makes the increment faster than the StrongCounter
      // Its value is computed lazily and stored locally.
      // Its main use case is for uses-case where faster increments are needed.
      counter3.add(5).thenAccept(aVoid -> System.out.println("Adding 5 to counter-3 completed!")).get();

      // Check the counter value.
      System.out.println("Counter-3 value is " + counter3.getValue());

Step 4: Run the Tutorial

mvn package exec:java

You should see output showing counter values, bound exceptions, listener events, and compare-and-set results.

What’s Next