Getting Started

Introduction

Atomix is an easy to use, zero-dependency, embeddable library that provides strong, fault-tolerant consistency for stateful resources in your distributed application. Some of the resources provided by Atomix include:

  • Distributed variables
  • Distributed collections such as maps, multi-maps, sets, and queues
  • Distributed groups tools such as group membership, leader election, messaging and more
  • Distributed concurrency tools such as locks

Atomix also provides a high-level API for creating and managing custom user-defined resources where fault-tolerance and consistency is provided automatically with the help of Copycat, a sophisticated implementation of the Raft consensus algorithm which Atomix is built upon.

Setup

To get started, add the atomix-all Maven artifact to your project:

<dependency>
  <groupId>io.atomix</groupId>
  <artifactId>atomix-all</artifactId>
  <version>1.0.0-rc9</version>
</dependency>

This dependency provides you with all of the Atomix resources along with a Netty based transport that Atomix nodes can use to communicate with each other.

Bootstrapping a new Cluster

The first step with Atomix is to bootstrap a cluster. An atomix cluster consists of stateful distributed resources such as maps, queues, and groups, and a set of replicas through which resources are created and operated on. Each replica maintains a copy of the state of each resource created in the cluster. State is stored according to a configurable StorageLevel and state changes are replicated to a majority of the cluster.

Clusters can contain both active and passive replicas. Active replicas take part in the processing of state changes while passive replicas are kept in sync in order to replace active replicas when a fault occurs. Typically, an Atomix cluster consists of 3 or 5 active replicas. While Atomix embeds inside your clustered application, the number of nodes participating in the Atomix cluster does not need to match that of your application, allowing your application to scale independant of Atomix.

For more information on node types see the clustering documentation.

AtomixReplicas are created using a builder pattern. To create a new replica, create a replica Builder via the AtomixReplica.builder() static factory method, passing the replica address to the builder factory:

AtomixReplica.Builder builder = AtomixReplica.builder(new Address("localhost", 8700));

The builder can be configured with a number of properties that define how the replica stores state and communicates with other replicas in the cluster. The most critical of these configurations are the Storage and Transport.

AtomixReplica replica = AtomixReplica.builder(new Address("localhost", 8700))
  .withStorage(storage)
  .withTransport(transport)
  .build();

Once we’ve constructed a replica, we can bootstrap a single node cluster by simply calling the bootstrap() method:

CompletableFuture<Atomix> future = replica.bootstrap();

The bootstrap() method returns a CompletableFuture that can be used to block until the replica is bootstrapped or call a completion callback once complete.

future.join();

All of the Atomix APIs are fully asynchronous, allowing users to perform multiple operations concurrently. The CompletableFuture API can still be used in a synchronous manner by using the blocking on the get or join methods, such as above.

Joining an existing cluster

Once a single replica has been bootstrapped, additional replicas can be added to the cluster via the join(Address...) method:

AtomixReplica replica2 = AtomixReplica.builder(new Address("localhost", 8701))
  .withStorage(storage)
  .withTransport(transport)
  .build();

replica2.join(new Address("localhost", 8700)).join();

AtomixReplica replica3 = AtomixReplica.builder(new Address("localhost", 8701))
  .withStorage(storage)
  .withTransport(transport)
  .build();

replica2.join(new Address("localhost", 8700), new Address("localhost", 8701)).join();

Multiple replicas can bootstrap a full cluster by providing the complete bootstrap cluster configuration to the bootstrap(Address...) method. See the clustering documentation for more info.

Creating Distributed Resources

With our AtomixReplica cluster bootstrapped, we can create some distributed resources. To get or create a distributed resource, use one of the Atomix get* methods. Let’s create and acquire a DistributedLock:

DistributedLock lock = replica.getLock("my-lock").join();
lock.lock().thenRun(() -> System.out.println("Acquired a lock!"));
DistributedLock lock = replica.getLock("my-lock").join();
lock.lock().join();
System.out.println("Acquired a lock!");

Each resource in the cluster must be assigned a unique String name. If multiple Atomix instances get the same resource type with the same name, they will all reference the same resource stored in the cluster.

Creating a Client

In addition to creating and acessing resources directly through an AtomixReplica, Atomix also supports clients which can be used to remotely access and operate on resources stored in a cluster. The AtomixClient API is very similar to AtomixReplica, but contains no storage since clients are stateless.

Creating a client is similar to creating a replica:

AtomixClient client = AtomixClient.builder()
  .withTransport(new NettyTransport())
  .build();

The provided Address list does not have to be representative of the full list of active replicas. Users must simply provide enough Addresses to be able to successfully connect to at least one replica.

Once the client is created, call connect(Address...) to establish a connection to the cluster:

List<Address> cluster = Arrays.asList(
  new Address("123.456.789.0", 8700),
  new Address("123.456.789.1", 8700),
  new Address("123.456.789.2", 8700)
);

client.connect(cluster).thenRun(() -> {
  System.out.println("Client connected!");
});
List<Address> cluster = Arrays.asList(
  new Address("123.456.789.0", 8700),
  new Address("123.456.789.1", 8700),
  new Address("123.456.789.2", 8700)
);

client.connect(cluster).join();

System.out.println("Client connected!");

Once the connect operation is complete, we can get or create distributed resources in the same way as with a replica:

DistributedValue<String> value = client.getValue("value").join();
value.set("Hello world!");