Apache ZooKeeper
Apache Zookeeper是一个分布式开源框架,提供了协调分布式应用的基本服务,它向外部应用暴露一组通用服务——分布式同步(Distributed Synchronization)、命名服务(Naming Service)、集群维护(Group Maintenance) 等,简化分布式应用协调及其管理的难度,提供高性能的分布式服务。ZooKeeper本身可以以单机模式安装运行,不过它的长处在于通过分布式ZooKeeper集群(一个Leader,多个Follower),基于一定的策略来保证ZooKeeper集群的稳定性和可用性,从而实现分布式应用的可靠性。核心实现了分布式CAP原理的CP特性关于分布式CAP原理参考如下文章:分布式CAP原理
1. 安装与运行
要安装 Apache ZooKeeper,请从官方网站 下载 并解压缩 ZooKeeper 发行版。然后,按照以下步骤操作:
- 创建一个名为
zoo.cfg
的配置文件,并将其放在 conf 目录下。在zoo.cfg
文件中添加以下内容:
tickTime=2000
dataDir=/path/to/data/directory
clientPort=2181
- 运行 ZooKeeper 服务器
bin/zkServer.sh start
- 运行 ZooKeeper 命令行客户端:
bin/zkCli.sh -server 127.0.0.1:2181
2. 基本概念
Zookeeper的架构
ZooKeeper 的架构可以分为三个部分:客户端
、服务端
和集合体
。
1. 客户端
ZooKeeper 客户端是一个库,可以在应用程序中使用。它提供了一组 API,允许应用程序与 ZooKeeper 集群交互。客户端可以向集群注册感兴趣的 znode,以便在 znode 上发生变化时接收通知。客户端可以向 znode 写入数据,以及从 znode 读取数据。
2. 服务端
ZooKeeper 服务端是一个分布式应用程序,可以在多个服务器上运行。每个服务器上的服务端进程都可以接收客户端请求,并将它们路由到负责处理请求的领导者或跟随者。服务端还负责将数据存储在 znode 中,并在 znode 上发生变化时通知客户端。
3. 集合体
ZooKeeper 集合体由多个服务端组成,可以协同工作以提供高可用性和性能。在集合体中,有一个服务器被选为领导者,其他服务器称为跟随者。领导者负责处理所有写操作,跟随者负责处理读操作。当领导者发生故障时,跟随者会自动选举新的领导者。
ZooKeeper 的集合体还包括一个称为 quorum
的概念。quorum 是一个由多个服务器组成的子集,可以用于确保在集合体中多数派的一致性。在 quorum 中,有一半以上的服务器必须同意写操作,才能使写操作生效。这种机制可以确保在发生故障或网络分区时,集合体仍然可以达成共识。
ZooKeeper 的架构提供了一种可靠且高效的方式,用于协调分布式应用程序中的任务和数据。它通过将数据存储在 znode 中,并使用 quorum 和领导者选举机制来确保高可用性和一致性。客户端和服务端的分离也使得应用程序能够更加灵活地使用 ZooKeeper。
2.2 Zookeeper集群原理
2.3 Zookeeper 集群中的角色
Zookeeper中,能改变ZooKeeper服务器状态的操作称为事务操作。一般包括数据节点创建与删除、数据内容更新和客户端会话创建与失效等操作
- Leader领导者 :Leader节点负责Zookeeper集群内部投票的发起和决议(一次事务操作),更新系统的状态;同时它也能接收并且响应Client端发送的请求
- Follower 跟随者:Follower节点用于接收并且响应Client端的请求,如果是事务操作,会将请求转发给Leader节点,发起投票,参与集群的内部投票
- Observer 观察者:Observer节点功能和Follower相同,只是Observer节点不参与投票过程,只会同步Leader节点的状态。
Zookeeper 通过复制来实现高可用,以Leader节点为准,Zookeeper的ZNode树上面的每一个修改都会被同步(复制)到其他的Server 节点上面
2.4 Zookeeper读写机制
如上图所示,每个Zookeeper Server节点除了包含一个请求处理器来处理请求以外,都会有一个内存数据库(ReplicatedDatabase) 用于持久化数据。ReplicatedDatabase 包含了整个Data Tree
- 来自于Client的读服务(Read Requst),是直接由对应Server的本地副本来进行服务的
- 至于来自于Client的写服务(Write Requst),因为Zookeeper要保证每台Server的本地副本是一致的(单一系统映像),需要通过一致性协议(后文提到的ZAB协议)来处理,成功处理的写请求(数据更新)会先序列化到每个Server节点的本地磁盘(为了再次启动的数据恢复)再保存到内存数据库中
集群模式下,Zookeeper使用简单的同步策略,通过以下三条基本保证来实现数据的一致性: - 全局串行化所有的写操作
串行化可以把变量包括对象,转化成连续bytes数据. 你可以将串行化后的变量存在一个文件里或在网络上传输. 然后再反串行化还原为原来的数据。
- 保证同一客户端的指令被FIFO执行(以及消息通知的FIFO)
- 自定义的原子性消息协议
简单来说,对数据的写请求,都会被转发到Leader节点来处理,Leader节点会对这次的更新发起投票,并且发送提议消息给集群中的其他节点,当半数以上的Follower节点将本次修改持久化之后,Leader 节点会认为这次写请求处理成功了,提交本次的事务
2.5 znode
ZooKeeper 中的基本数据单元是 znode
,它是一个类似于文件系统的节点。znode 可以存储少量数据(通常小于 1MB)并具有访问权限控制。znode 有两种类型:普通 znode 和临时 znode。普通 znode 在创建后一直存在,直到显式删除。临时 znode 在创建它的客户端断开连接后自动删除。
2.6 事件和监视
ZooKeeper 客户端可以通过监视(watch
)特定 znode 以接收事件通知。当 znode 发生变化(如创建、删除或更新数据)时,ZooKeeper 会向监视该 znode 的客户端发送通知。
2.7 一致性
ZooKeeper 保证所有客户端看到的 znode 数据是一致的。当一个客户端更改 znode 数据时,ZooKeeper 会将更改广播给所有其他客户端。
2.8 乐观锁
Zookeeper 的核心思想就是,提供一个非锁机制的Wait Free的用于分布式系统同步的核心服务。其核心对于文件、数据的读写服务,并不提供加锁互斥的服务
但是由于Zookeeper的每次更新操作都会更新ZNode的版本(详见第一章),也就是客户端可以自己基于版本的对比,来实现更新数据时的加锁逻辑,就像我们更新数据库时,会新增一个version字段,通过更新前后的版本对比来实现乐观锁
2.9 ZAB协议
ZAB协议是为分布式协调服务ZooKeeper专门设计的一种支持崩溃恢复的一致性协议,这个机制保证了各个server之间的同步。全称 Zookeeper Atomic Broadcast Protocol - Zookeeper 原子广播协议,Zab协议有两种模式,它们分别是恢复模式和广播模式。
2.10 广播模式
广播模式类似于分布式事务中的 Two-phase commit (两阶段式提交),因为Zookeeper中一次写操作就是被当做一个事务,所以这实际上本质是相同的
步骤:
- ZooKeeper Server接受到Client的写请求 写请求都被转发给Leader节点 Leader节点先更新持久化到本地
- Leader节点将此次更新提议(propose)给Followers,进入收集选票的流程
- Follower节点接收请求,成功修改持久化到本地,发送一个ACK给Leader
- Leader接收到半数以上的ACK时,Leader将广播commit消息并在本地deliver该消息
- 当收到Leader发来的commit消息时,Follower也会deliver该消息
- 广播协议在所有的通讯过程使用TCP的FIFO信道中,通过使用该信道,使保持有序性变得非常的容易。通过FIFO信道,消息被有序的deliver。只要收到的消息一被处理,其顺序就会被保存下来。
但是这种模式下,如果Leader自身发生了故障,Zookeeper的集群不就提供不了写服务了吗? 这就引入了下面的恢复模式
2.11 恢复模式
简单点来说,当集群中的Leader故障或者服务启动的时候,ZAB就会进入恢复模式,其中包括Leader选举和完成其他Server和Leader之间的状态同步
3. Java API
要使用 ZooKeeper Java API,请将 zookeeper-x.y.z.jar
添加到项目的类路径中。以下是一个简单的示例,展示了如何使用 Java API 创建一个 znode:
import org.apache.zookeeper.*;
import java.util.concurrent.CountDownLatch;
public class ZooKeeperDemo {
public static void main(String[] args) throws Exception {
CountDownLatch connectedSignal = new CountDownLatch(1);
ZooKeeper zooKeeper = new ZooKeeper("localhost:2181", 5000, new Watcher() {
public void process(WatchedEvent event) {
if (event.getState() == Event.KeeperState.SyncConnected) {
connectedSignal.countDown();
}
}
});
connectedSignal.await();
String path = "/my_znode";
String data = "Hello, ZooKeeper!";
zooKeeper.create(path, data.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
System.out.println("创建了一个新的 znode: " + path);
}
}
4. 使用场景
以下是一些使用 ZooKeeper 的常见场景:
4.1 分布式锁
分布式锁可以用于在分布式环境中协调对共享资源的访问。ZooKeeper 可以用于实现分布式锁,以确保在同一时刻只有一个客户端可以访问共享资源。
4.2 配置管理
在分布式系统中,配置信息的一致性和实时更新非常重要。ZooKeeper 可以用于存储和分发配置信息,以确保所有客户端都能获取到最新的配置。
4.3 服务发现
服务发现是分布式系统中的一个关键功能。ZooKeeper 可以用于跟踪可用服务的信息,以便客户端能够发现和使用这些服务。
4.4 集群管理
ZooKeeper 可以用于跟踪集群中的成员信息,以及成员的状态和元数据。当集群中的节点发生故障或离开时,ZooKeeper 可以协调其他节点来重新分配任务。
5. 高可用性和性能
ZooKeeper 是一个高可用性和高性能的服务。它通过以下方式实现高可用性和性能:
- 复制:ZooKeeper 通过在多个服务器上复制数据来提供高可用性。这些服务器组成一个称为 ensemble 的集群。
- 领导者选举:在 ensemble 中,一个服务器被选为领导者,其他服务器称为跟随者。领导者负责处理所有写操作,跟随者负责处理读操作。当领导者发生故障时,跟随者会自动选举新的领导者。
要设置一个高可用的 ZooKeeper ensemble,请在 zoo.cfg
配置文件中指定多个服务器地址,如下所示:
server.1=zk1.example.com:2888:3888
server.2=zk2.example.com:2888:3888
server.3=zk3.example.com:2888:3888
请注意,ZooKeeper ensemble 的大小应为奇数,以便在发生故障时仍然可以达成多数派。
6. 安全性
ZooKeeper 提供了一些安全功能,以保护 znode 的数据和访问权限。以下是一些关键的安全特性:
6.1 ACL(访问控制列表)
ZooKeeper 使用 ACL 对 znode 的访问进行授权。每个 znode 都有一个与之关联的 ACL 列表,包含一组授权规则。每个授权规则由一个身份标识(如 IP 地址或用户名)和一组权限(如读、写、删除等)组成。
例如,以下代码创建一个只允许 IP 地址为 192.168.1.100 的客户端读取的 znode:
import org.apache.zookeeper.data.ACL;
import org.apache.zookeeper.ZooDefs;
import java.util.Arrays;
List<ACL> readOnlyAcl = Arrays.asList(new ACL(ZooDefs.Perms.READ, new Id("ip", "192.168.1.100")));
zooKeeper.create("/secure_znode", "Secret data".getBytes(), readOnlyAcl, CreateMode.PERSISTENT);
6.2 SASL(Simple Authentication and Security Layer)
ZooKeeper 支持使用 SASL 进行客户端身份验证。要启用 SASL,需要在 zoo.cfg 文件中添加以下配置:
authProvider.1=org.apache.zookeeper.server.auth.SASLAuthenticationProvider
然后,配置客户端和服务器的 JAAS(Java Authentication and Authorization Service)配置文件。以下是一个使用 DIGEST-MD5 机制的示例:
客户端配置文件 jaas_client.conf
Client {
org.apache.zookeeper.server.auth.DigestLoginModule required
username="alice"
password="aliceSecret";
};
服务器配置文件 jaas_server.conf
Server {
org.apache.zookeeper.server.auth.DigestLoginModule required
user_alice="aliceSecret";
};
设置环境变量并启动 ZooKeeper 服务器和客户端:
export SERVER_JVMFLAGS="-Djava.security.auth.login.config=/path/to/jaas_server.conf"
export CLIENT_JVMFLAGS="-Djava.security.auth.login.config=/path/to/jaas_client.conf"
bin/zkServer.sh start
bin/zkCli.sh -server 127.0.0.1:2181
7. 监控和运维
ZooKeeper 提供了一些工具和接口,用于监控集群的健康状况和性能指标。以下是一些常用的监控和运维工具:
7.1 四字命令
ZooKeeper 支持一组称为“四字命令”的管理命令。这些命令可以通过 telnet 或 nc 工具向 ZooKeeper 服务器发送。例如,要获取服务器状态信息,请执行以下命令:
echo "stat" | nc localhost 2181
7.2 JMX(Java Management Extensions)
ZooKeeper 通过 JMX 暴露了一组 MBean,用于监控服务器的性能指标和状态信息。要启用 JMX,请在启动 ZooKeeper 服务器时添加以下Java 系统属性:
export SERVER_JVMFLAGS="-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.local.only=false -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false"
bin/zkServer.sh start
然后,您可以使用 JMX 客户端(如 JConsole 或 VisualVM)连接到 ZooKeeper 服务器并监控这些 MBean。
7.3 日志分析
ZooKeeper 服务器生成的日志文件包含有关服务器操作和性能的详细信息。分析这些日志文件有助于诊断问题和优化集群性能。默认情况下,ZooKeeper 使用 Log4j 进行日志记录。您可以通过修改 conf/log4j.properties
文件来自定义日志配置。
7.4 备份和恢复
为了确保数据安全,您应该定期备份 ZooKeeper 数据。要备份 ZooKeeper 数据,只需复制 dataDir
(在 zoo.cfg
文件中指定)中的所有文件。要恢复数据,只需将备份的文件复制回 dataDir。
请注意,在恢复数据之前,务必停止所有 ZooKeeper 服务器。此外,确保在所有服务器上使用相同的数据快照,以避免不一致。