The Coordinator
API provides a simple way to coordinate services that are utilizing Terracotta.
For instance, in a microservice architecture, the services may deployed redundantly (for HA) and/or for horizontal scale. As the service instances receive "work" to perform, it may be necessary to ensure work is not performced redundantly by multiple service instances, or that units of work are performed in proper sequencing.
The Coordinator API provides a way for arbitrary groups of services to coordate with each other as a "logical cluster" by assuming particular roles with the group or "scope".
Within the Coordinate API, groupings are created by Coordinator instances using the same group/scope "token". The token is an arbitrary text string as definied/agreed by the services that want to coordinate together. This might be a string such as "PO_Processing_Services" or "message_receivers" or "worker_nodes" or "foo".
Once one or more services have joined the group by creating a Coordinator using the given token, the serivce can utilize the Coordinator service to do things such as getting a list of all other members of the group or to attempt to assume a special role withing the group.
Roles represent exclusive responsibilities that only one member may hold at a time. Roles are also arbitrary text strings and defined/agreed by the services that want to coordinate together. This might be a string such as "master", or "leader" or "standby" or etc. Only one group member can "acquire" a given role at any given time, and other must wait for the role owner to relinquish it before they can assume the role. There can be many different roles within the same group, as coordination needs may require.
The Coordinator API also provides a Listener
interface, implementors of which are notified of events such as other
members joining or leaving the group, and roles being acquired or relinquished. This provides a way ensure other
services instances are aware of the need to take over a role if the previous owner of the role has left (e.g. crashed).
Example Usage
URI clusterUri = URI.create("terracotta://localhost:9410");
// Create an executor for handling reconnection tasks
ExecutorService executor = Executors.newFixedThreadPool(1, Thread::new);
try {
// Create a coordination group with token "sample-group"
// Each member of the group must use the same token to join
System.out.println("Creating coordinator for 'sample-group'...");
// Create a coordinator listener to receive notifications
SampleCoordinationListener listener = new SampleCoordinationListener();
// Build the coordinator with a unique member name "myservice-instance-1"
try (Coordinator coordinator = ClusteredCoordinator.coordinator(clusterUri, "sample-group", "myservice-instance-1", executor)
.withListener(listener)
.build()) {
System.out.println("Successfully joined the coordination group.");
System.out.println("Current members: " + coordinator.getMembers());
// Try to acquire the "primary" role
System.out.println("Attempting to acquire 'primary' role...");
try (Role primaryRole = coordinator.acquireRole("primary", Duration.ofSeconds(10))) {
System.out.println("Successfully acquired 'primary' role.");
// Execute a task as the role holder with a lease time
String result = primaryRole.execute(() -> {
System.out.println("Executing task as the 'primary' role holder...");
// Simulate some work
try { Thread.sleep(1000); } catch (InterruptedException ignore) { Thread.currentThread().interrupt(); }
return "Task completed successfully";
}, Duration.ofSeconds(10));
System.out.println("Task result: " + result);
// Check who holds the role
System.out.println("Current holder of 'primary' role: " + coordinator.holder("primary").orElse("None"));
// Wait a bit to observe the coordination
System.out.println("Holding the role for 5 seconds...");
try { Thread.sleep(5000); } catch (InterruptedException ignore) { Thread.currentThread().interrupt(); }
}