# Zookeeper
概述:
1. ZooKeeper 是 Apache 软件基金会的一个软件项目,它为大型分布式计算提供开源的`分布式配置服务`、同步服务和命名注册。其架构通过冗余服务实现高可用性。
2. Zookeeper 的设计目标是将那些复杂且容易出错的分布式一致性服务封装起来,构成一个高效可靠的原语集,并以一系列简单易用的接口提供给用户使用。
3. Zookeeper的数据保存在内存中,性能高,可以实现高吞吐量和低延迟量。
4. ZooKeeper可以用于解决分布式数据一致性的问题,常用在数据发布/订阅、负载均衡、命名服务、集群管理、Master选举、`分布式锁`、`分布式配置服务`和分布式队列等功能中。
**总之:Zookeeper提供分布式协调服务。**
## 理论补充
**CAP理论**
CAP 理论指出对于一个分布式计算系统来说,不可能同时满足以下三点:
* **一致性**:Consistency 在分布式环境中,一致性是指数据在多个副本之间能够保持一致的特性,等同于所有节点访问`同一份最新的`数据副本。在一致性的需求下,当一个系统在数据一致的状态下执行更新操作后,应该保证系统的数据仍然处于一致的状态。
* **可用性**:Availability 每次请求都能获取到正确的响应,但是不保证获取的数据为最新数据。
* **分区容错性**:Partition Tolerance 分布式系统在遇到任何网络分区故障的时候,仍然需要能够保证对外提供满足一致性和可用性的服务,除非是整个网络环境都发生了故障。
一个分布式系统最多只能同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance)这三项中的两项。
在这三个基本需求中,最多只能同时满足其中的两项,P 是必须的,因此只能在 CP 和 AP 中选择,`zookeeper 保证的是 CP`,更准确讲ZooKeeper会实现最终的一致性,但是并不能保证强一致性;对比 spring cloud 系统中的注册中心 `eruka 实现的是 AP`。
![img](https://www.runoob.com/wp-content/uploads/2020/09/cap-theorem-diagram.png)
**BASE理论**
BASE 是 Basically Available(基本可用)、Soft-state(软状态) 和 Eventually Consistent(最终一致性) 三个短语的缩写。
* **基本可用**:在分布式系统出现故障,允许损失部分可用性(服务降级、页面降级)。
* **软状态**:允许分布式系统出现中间状态。而且中间状态不影响系统的可用性。这里的中间状态是指不同的 data replication(数据备份节点)之间的数据更新可以出现延时的最终一致性。
* **最终一致性**:data replications 经过一段时间达到一致性。
BASE 理论是对 CAP 中的一致性和可用性进行一个权衡的结果,理论的核心思想就是:`我们无法做到强一致,但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性。`ZooKeeper可以实现系统的最终一致性。
## 安装
## 安装
1. Linux的话去[官网](https://zookeeper.apache.org/releases.html)找到最新的稳定版本的下载路径
:-: ![](https://img.kancloud.cn/67/6f/676f462f7fe264a4ddd015523f4dbacc_1081x666.png)
注意下载下来的要带有bin命名的才是打包好的。
2. Linux中创建文件夹下载安装包
~~~
wget https://dlcdn.apache.org/zookeeper/zookeeper-3.6.3/apache-zookeeper-3.6.3-bin.tar.gz
~~~
在下载目录中解压
~~~
tar -zxvf apache-zookeeper-3.6.3-bin.tar.gz
~~~
3. 复制配置文件,在安装目录的conf目录下
~~~
cp zoo_camp.cfg zoo.cfg
~~~
zoo.cfg是zookeeper的默认启动配置文件
3. 配置bin环境
~~~
vim /etc/profile
# 添加上如下内容
export ZOOKEEPER_HOME=/下载目录/apache-zookeeper-3.6.3-bin
export PATH=$ZOOKEEPER_HOME/bin:$PATH
# 使其生效
source /etc/profile
~~~
最好是保证JDK在profile中配置了!
4. 打开Zookeeper服务
:-: ![](https://img.kancloud.cn/70/ea/70ea1bbddbf9f845e864acd19f6b83d8_771x89.png)
如果不能正确启动成功,有很大的可能是JDK的问题,注意查看JDK是否已经配置了环境变量了。
### 集群搭建
ZooKeeper一般都会搭建集群来使用,当然使用单机也行,但是这样就不能很好的发挥其高可用的特性了。ZooKeeper需要至少3台服务器形成一个集群。
1. 准备至少三台服务器(由于ZooKeeper的Leader选举至少要过半才能通过,因此ZooKeeper的集群至少要三台服务器才行)。
2. 在各个服务器中安装相同的ZooKeeper版本。并修改`/etc/profile`,将ZooKeeper的相关命令添加到环境变量中。同时要确保服务器中至少安装了JDK1.8+。
:-: ![](https://img.kancloud.cn/2a/d8/2ad86261fe1dbef35620c9b818cb43bb_510x65.png)
3. 修改每台服务器的ZooKeeper的配置文件,在配置文件的末尾添加上类似如下内容:
:-: ![](https://img.kancloud.cn/47/a4/47a43962ef301ca7a83ded18516cb262_459x117.png)
及其书写格式为:
~~~
server.myid=ip:2888:3888
~~~
2888集群内各台机器之间通信使用,3888用于投票选举时使用。
4. 每台服务器创建myid文件,需要创建在zoo.cfg配置文件`dataDir`目录下的;假如这里dataDir=/var/zookeeper,则可以通过如下命令创建myid文件,设置每台服务器的myid
~~~
echo 1 > /tmp/zookeeper/myid
~~~
'1'表示的就是该服务器的myid,需要和配置文件中server.myid配置的相对应。
5. 启动各台服务器的zkServer.sh。
~~~
# 前台启动
zkServer.sh start-foreground
# 后台启动
zkServer.sh start
~~~
可以使用命令`zkServer.sh status`查看哪台服务器是Leader节点。
## 相关元素
1. 架构
![ZooKeeper的架构](https://atts.w3cschool.cn/attachments/day_161229/201612291344222238.jpg)
* Client
客户端,分布式应用集群中的一个节点,从ZooKeeper服务器访问信息。客户端和ZooKeeper服务器之间会维持心跳连接。
* Server
服务器,ZooKeeper集群中的一个节点,为客户端提供所有的服务。在客户端连接的时候,客户端发送确认码以告知服务器是活跃的。
* Ensemble
ZooKeeper服务器组。形成ensemble所需的最小节点数为3。
* Leader
Zookeeper是主从集群,和Reids类似。Leader表示中心节点,在集群启动的时候被选举。如果集群中Leader服务宕机了,则执行自动选举过程。注意增删改只能发送在Leader中,查询可以发生在其他节点中。
当Leader 节点宕机的时候,官方的压测给出了大约在200ms左右可以选举一个新的节点作为leader节点。即从不可用状态恢复到可用状态大约需要200ms。
* Follower
ZooKeeper集群的节点之一,属于Leader节点的从节点,可以给客户端提供查询功能;每个Follower节点在Leader宕机的时候都有机会被选择成新的Leader的。
2. 层次命名空间
下图描述了用于内存表示的ZooKeeper文件系统的`树结构`。ZooKeeper节点称为 **znode** 。每个znode由一个名称标识,并用路径(/)序列分隔。
* 在图中,首先有一个由“/”分隔的znode。在根目录下,两个逻辑命名空间 **config** 和 **workers** 。
* **config** 命名空间用于集中式配置管理,**workers** 命名空间用于命名。
* 在 **config** 命名空间下,每个znode最多可存储1MB的数据。这种结构的主要目的是存储同步数据并描述znode的元数据。此结构称为 **ZooKeeper数据模型**。
![分层命名空间](https://atts.w3cschool.cn/attachments/day_161229/201612291345162031.jpg)
Znode兼具文件和目录两种特点。既像文件一样维护着*数据长度、元信息、ACL、时间戳*等数据结构,又像目录一样可以作为路径标识的一部分。每个Znode由三个部分组成:
* **stat**:此为状态信息,描述该Znode版本、权限等信息。
* **data**:与该Znode关联的数据。
* c**hildren**:该Znode下的节点。
其他信息:
* **版本号** - 每个znode都有版本号,这意味着每当与znode相关联的数据发生变化时,其对应的版本号也会增加。当多个zookeeper客户端尝试在同一znode上执行操作时,版本号的使用就很重要。
* **操作控制列表(ACL)** - ACL基本上是访问znode的认证机制。管理所有znode读取和写入操作。
* **时间戳** - 时间戳表示创建和修改znode所经过的时间。通常以毫秒为单位。ZooKeeper从“事务ID"(zxid)标识znode的每个更改。**Zxid** 是唯一的,并且为每个事务保留时间,可以轻松地确定从一个请求到另一个请求所经过的时间。
* **数据长度** - 存储在znode中的数据总量是数据长度。最多可以存储1MB的数据。
3. Znode的类型
Znode分为持久(persistent)节点,顺序(sequential)节点和临时(ephemeral)节点。
* **持久节点**
即使在创建该特定znode的客户端断开连接后,持久节点仍然存在。默认情况下,除非另有说明,否则所有znode都是持久的。大小为1MB。
* **临时节点**
客户端活跃时,临时节点就是有效的。当客户端与ZooKeeper集群的连接断开时,临时节点会自动删除。因此,只有临时节点不允许有子节点。如果临时节点被删除,则下一个合适的节点将填充其位置。临时节点在leader选举中起着重要作用。也可以用于Redis中的分布式锁。
* **顺序节点**
顺序节点可以是持久的或临时的。当一个新的znode被创建为一个顺序节点时,ZooKeeper通过将10位的序列号附加到原始名称来设置znode的路径。
例如,如果将具有路径 **/myapp** 的znode创建为顺序节点,则ZooKeeper会将路径更改为 **/myapp0000000001** ,并将下一个序列号设置为0000000002。如果两个顺序节点是同时创建的,那么ZooKeeper不会对每个znode使用相同的数字。顺序节点在锁定和同步中起重要作用。
![](https://img.kancloud.cn/8e/e3/8ee38623b75d56b7d9ef346613161d9a_698x271.png)
4. Sessions(会话)
会话对于ZooKeeper的操作非常重要。会话中的请求按FIFO顺序执行。一旦客户端连接到服务器,将建立会话并向客户端分配**会话ID** 。
客户端以特定的时间间隔发送**心跳**以保持会话有效。如果ZooKeeper集合在超过服务器开启时指定的期间(会话超时)都没有从客户端接收到心跳,则它会判定客户端死机。
会话超时通常以毫秒为单位。当会话由于任何原因结束时,在该会话期间创建的临时节点也会被删除。
5. Watches(监视)
监视是一种简单的机制,使客户端收到关于ZooKeeper中znode的更改的通知。客户端可以在读取特定znode时设置Watches。Watches会向注册的客户端发送任何znode(客户端注册表)更改的通知。
Znode更改是与znode相关的数据的修改或znode的子项中的更改。只触发一次watches。如果客户端想要再次通知,则必须通过另一个读取操作来完成。当连接会话过期时,客户端将与服务器断开连接,相关的watches也将被删除。
## 客户端使用
### 常用命令
1. 查看目录下的zNode
~~~
ls [-s] [-w] [-R] path
~~~
2. 创建节点
~~~
create [-s] [-e] path data
# 例如:创建aabb节点
create /aabb "hello world"
~~~
选项说明:
* \-s:创建序列化节点,节点名称相同时数据不会覆盖,会自动添加个版本号。可以使每个客户端都有自己的一个同名节点。序列化数字由leader维护,会一直递增。可以用于并发的场景中。
例如:
~~~
[zk: localhost:2181(CONNECTED) 9] create /aabb -s "01"
Created /aabb0000000001
[zk: localhost:2181(CONNECTED) 10] create /aabb -s "02"
Created /aabb0000000002
[zk: localhost:2181(CONNECTED) 11] ls /
[aabb0000000001, aabb0000000002, test, zookeeper]
~~~
* \-e:创建临时节点。
3. 获取节点的数据
~~~
get [-s] [-w] path
~~~
选项说明:
* \-s:获取节点的详细数据,包括元数据
~~~
[zk: localhost:2181(CONNECTED) 15] get -s /test
hello world # data内容
cZxid = 0x2
ctime = Mon Jan 17 11:05:13 HKT 2022
mZxid = 0x2
mtime = Mon Jan 17 11:05:13 HKT 2022
pZxid = 0x2
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 11
numChildren = 0
~~~
cZxid:创建事务id,64位,由Leader节点维护。
mZxid:修改的事务id。
pZxid:最后一个节点创建的事务id。
ephemeralOwner:session id,用于临时节点。创建节点的时候可以使用-e选项。
* \-w:只获取data的内容。
### API使用
1. 导入依赖
~~~
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.6.3</version>
</dependency>
~~~
注意要和集群的版本一致。
2. 创建ZooKeeper对象
~~~
final CountDownLatch countDownLatch = new CountDownLatch(1);
ZooKeeper zk = new ZooKeeper("103.118.42.131:2181", 3000, watchedEvent -> {
System.out.println("new zk watch:" + watchedEvent);
Watcher.Event.KeeperState state = watchedEvent.getState();
Watcher.Event.EventType type = watchedEvent.getType();
String path = watchedEvent.getPath();
System.out.println("path:" + path);
// 连接状态的监控
switch (state) {
case Unknown:
case NoSyncConnected:
break;
case Disconnected:
System.out.println("disconnected...");
break;
case SyncConnected:
System.out.println("SyncConnected...");
countDownLatch.countDown();
break;
case AuthFailed:
break;
case ConnectedReadOnly:
break;
case SaslAuthenticated:
break;
case Expired:
break;
case Closed:
System.out.println("closed...");
break;
default:
break;
}
// 节点的相关状态
switch (type) {
case None:
break;
case NodeCreated:
System.out.println("NodeCreated...");
break;
case NodeDeleted:
System.out.println("NodeDeleted...");
break;
case NodeDataChanged:
System.out.println("NodeDataChanged...");
break;
case NodeChildrenChanged:
System.out.println("NodeChildrenChanged...");
break;
case DataWatchRemoved:
System.out.println("DataWatchRemoved...");
break;
case ChildWatchRemoved:
System.out.println("ChildWatchRemoved...");
break;
case PersistentWatchRemoved:
System.out.println("PersistentWatchRemoved...");
break;
default:
break;
}
});
countDownLatch.await();
~~~
由于是异步连接的,所以要用CountDownLatch进行等待。
2. 创建节点
~~~
String path = zk.create("/test1", "hello world".getBytes(StandardCharsets.UTF_8), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
~~~
3. 设置数据
~~~
Stat stat = zk.setData(path, "hello test".getBytes(StandardCharsets.UTF_8), -1);
~~~
\-1表示自动匹配数据的版本,否则要和集群中的znode节点一致,不然的话会报错。
4. 获取数据,同时设置watch
~~~
byte[] data = zk.getData("/test", new Watcher() {
@Override
public void process(WatchedEvent watchedEvent) {
System.out.println("getData watch:" + watchedEvent);
try {
//可以重新注册回调
zk.getData("/test", this, new Stat());
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, new Stat());
~~~
在getData中设置的Watch会在给znode设置数据或者删除节点的时候触发,注意一个watch只能触发一次,所以一般都会重复设置。
异步回调方式:
~~~
zk.getData(path, false, new AsyncCallback.DataCallback() {
@Override
public void processResult(int rc, String path, Object ctx, byte[] data, Stat stat) {
System.out.println("rc:" + rc);
System.out.println("-------async call back----------");
System.out.println(ctx.toString());
System.out.println(new String(data));
countDownLatch1.countDown();
}
}, "abc");
~~~
这种方式会在读取到数据的时候触发。
- 第一章 Java基础
- ThreadLocal
- Java异常体系
- Java集合框架
- List接口及其实现类
- Queue接口及其实现类
- Set接口及其实现类
- Map接口及其实现类
- JDK1.8新特性
- Lambda表达式
- 常用函数式接口
- stream流
- 面试
- 第二章 Java虚拟机
- 第一节、运行时数据区
- 第二节、垃圾回收
- 第三节、类加载机制
- 第四节、类文件与字节码指令
- 第五节、语法糖
- 第六节、运行期优化
- 面试常见问题
- 第三章 并发编程
- 第一节、Java中的线程
- 第二节、Java中的锁
- 第三节、线程池
- 第四节、并发工具类
- AQS
- 第四章 网络编程
- WebSocket协议
- Netty
- Netty入门
- Netty-自定义协议
- 面试题
- IO
- 网络IO模型
- 第五章 操作系统
- IO
- 文件系统的相关概念
- Java几种文件读写方式性能对比
- Socket
- 内存管理
- 进程、线程、协程
- IO模型的演化过程
- 第六章 计算机网络
- 第七章 消息队列
- RabbitMQ
- 第八章 开发框架
- Spring
- Spring事务
- Spring MVC
- Spring Boot
- Mybatis
- Mybatis-Plus
- Shiro
- 第九章 数据库
- Mysql
- Mysql中的索引
- Mysql中的锁
- 面试常见问题
- Mysql中的日志
- InnoDB存储引擎
- 事务
- Redis
- redis的数据类型
- redis数据结构
- Redis主从复制
- 哨兵模式
- 面试题
- Spring Boot整合Lettuce+Redisson实现布隆过滤器
- 集群
- Redis网络IO模型
- 第十章 设计模式
- 设计模式-七大原则
- 设计模式-单例模式
- 设计模式-备忘录模式
- 设计模式-原型模式
- 设计模式-责任链模式
- 设计模式-过滤模式
- 设计模式-观察者模式
- 设计模式-工厂方法模式
- 设计模式-抽象工厂模式
- 设计模式-代理模式
- 第十一章 后端开发常用工具、库
- Docker
- Docker安装Mysql
- 第十二章 中间件
- ZooKeeper