Atomix 简介
1. 概述
大多数分布式应用程序需要一些有状态的组件来保持一致和容错。Atomix 是一个嵌入式库,有助于实现分布式资源的容错性和一致性。
它提供了一组丰富的 API 来管理其资源,例如集合、组和并发工具。
首先,我们需要将以下 Maven 依赖项添加到我们的 pom 中:
<dependency>
<groupId>io.atomix</groupId>
<artifactId>atomix-all</artifactId>
<version>1.0.8</version>
</dependency>
这种依赖关系提供了其节点相互通信所需的基于 Netty 的传输。
2. 引导集群
要开始使用 Atomix,我们需要先引导一个集群。
Atomix 由一组副本组成,这些副本用于创建有状态的分布式资源。每个副本都维护集群中存在的每个资源的状态副本。
副本在集群中有两种类型:主动和被动。
分布式资源的状态变化通过主动副本传播,而被动副本保持同步以保持容错。
2.1. 引导嵌入式集群
要引导单节点集群,我们需要先创建一个AtomixReplica实例:
AtomixReplica replica = AtomixReplica.builder(
new Address("localhost", 8700))
.withStorage(storage)
.withTransport(new NettyTransport())
.build();
这里副本配置了Storage和Transport。声明存储的代码片段:
Storage storage = Storage.builder()
.withDirectory(new File("logs"))
.withStorageLevel(StorageLevel.DISK)
.build();
一旦副本被声明并配置了存储和传输,我们可以通过简单地调用bootstrap()来引导它——它返回一个CompletableFuture可以用来阻塞,直到通过调用相关的阻塞*join()*方法引导服务器:
CompletableFuture<AtomixReplica> future = replica.bootstrap();
future.join();
到目前为止,我们已经构建了一个单节点集群。现在我们可以向它添加更多节点。
为此,我们需要创建其他副本并将它们加入现有集群;我们需要生成一个新线程来调用*join(Address)*方法:
AtomixReplica replica2 = AtomixReplica.builder(
new Address("localhost", 8701))
.withStorage(storage)
.withTransport(new NettyTransport())
.build();
replica2
.join(new Address("localhost", 8700))
.join();
AtomixReplica replica3 = AtomixReplica.builder(
new Address("localhost", 8702))
.withStorage(storage)
.withTransport(new NettyTransport())
.build();
replica3.join(
new Address("localhost", 8700),
new Address("localhost", 8701))
.join();
现在我们有一个自举的三节点集群。或者,我们可以通过在bootstrap(List)方法中传递地址列表来引导集群:
List<Address> cluster = Arrays.asList(
new Address("localhost", 8700),
new Address("localhost", 8701),
new Address("localhsot", 8702));
AtomixReplica replica1 = AtomixReplica
.builder(cluster.get(0))
.build();
replica1.bootstrap(cluster).join();
AtomixReplica replica2 = AtomixReplica
.builder(cluster.get(1))
.build();
replica2.bootstrap(cluster).join();
AtomixReplica replica3 = AtomixReplica
.builder(cluster.get(2))
.build();
replica3.bootstrap(cluster).join();
我们需要为每个副本生成一个新线程。
2.2. 引导独立集群
Atomix 服务器可以作为独立服务器运行,可以从 Maven Central 下载。简而言之——它是一个 Java 存档,可以通过提供
简单地说,它是一个 Java 存档,可以通过在地址标志中提供host:port 参数并使用*-bootstrap*标志来通过终端运行。
这是引导集群的命令:
java -jar atomix-standalone-server.jar
-address 127.0.0.1:8700 -bootstrap -config atomix.properties
这里atomix.properties是配置存储和传输的配置文件。要创建多节点集群,我们可以使用-join标志将节点添加到现有集群。
它的格式是:
java -jar atomix-standalone-server.jar
-address 127.0.0.1:8701 -join 127.0.0.1:8700
3. 客户端
Atomix 支持通过AtomixClient API创建客户端以远程访问其集群。
由于客户端不需要有状态,AtomixClient没有任何存储。我们只需要在创建客户端时配置传输,因为传输将用于与集群通信。
让我们创建一个带有传输的客户端:
AtomixClient client = AtomixClient.builder()
.withTransport(new NettyTransport())
.build();
我们现在需要将客户端连接到集群。
我们可以声明一个地址列表并将该列表作为参数传递给客户端的*connect()*方法:
client.connect(cluster)
.thenRun(() -> {
System.out.println("Client is connected to the cluster!");
});
4. 处理资源
Atomix 的真正强大之处在于其强大的 API 集,用于创建和管理分布式资源。资源在集群中被复制和持久化,并由复制的状态机支持——由其底层实现的 Raft 共识协议管理。
可以通过其get()方法之一创建和管理分布式资源。我们可以从AtomixReplica创建一个分布式资源实例。
考虑到replica是AtomixReplica的一个实例,创建分布式地图资源并为其设置值的代码片段:
replica.getMap("map")
.thenCompose(m -> m.put("bar", "Hello world!"))
.thenRun(() -> System.out.println("Value is set in Distributed Map"))
.join();
这里的join()方法将阻塞程序,直到创建资源并为其设置值。我们可以使用AtomixClient获取相同的对象并使用*get(“bar”)*方法检索值。
我们可以在最后使用*get()*方法等待结果:
String value = client.getMap("map"))
.thenCompose(m -> m.get("bar"))
.thenApply(a -> (String) a)
.get();
5. 一致性和容错性
Atomix 用于关键任务的小规模数据集,其一致性比可用性更重要。
它通过读写的线性化提供了强大的可配置一致性。在线性化中,一旦提交了写操作,就保证所有客户端都知道结果状态。
Atomix 集群中的一致性是由底层 Raft 共识算法保证的,其中选出的领导者将拥有之前成功的所有写入。
所有新的写入都将通过集群领导并在完成之前同步复制到大多数服务器。
为了保持容错,集群的大多数服务器需要处于活动状态。如果少数节点发生故障,节点将被标记为非活动节点,并由被动节点或备用节点替换。
如果leader失败,集群中剩余的服务器将开始新的leader选举。同时,集群将不可用。
在分区的情况下,如果一个领导者在分区的非仲裁侧,它会下台,并在有仲裁的一侧选举一个新的领导者。
而且,如果领导者是多数派,它将继续没有变化。分区解决后,非仲裁端的节点将加入仲裁并相应地更新其日志。