Zookeeper是一个开源的分布式协调服务,由雅虎公司开发,用于解决分布式应用中的一致性问题。它提供了一个分布式的协调服务,可以实现分布式应用的元数据管理、分布式锁、分布式队列、节点选举、集群管理等功能。
ZooKeeper 提供的名称空间与标准文件系统的名称空间非常相似。名称是由斜杠 (/) 分隔的一系列路径元素。ZooKeeper 命名空间中的每个节点都由路径标识。
上图中每一个节点可以称之为znode,每个znode都有如下属性:
znode 的数据操作具有原子性,读操作将获取与节点相关的所有数据,写操作也将替换掉节点的所有数据。znode 可存储的最大数据量是 1MB ,但实际上我们在 znode 的数据量应该尽可能小,因为数据过大会导致 zk 的性能明显下降。每个 ZNode 都对应一个唯一的路径。
znode节点根据生命周期的不同可以划分为持久节点和临时节点。持久节点的存活时间不依赖于客户端会话,只有客户端在显式执行删除节点操作时,节点才消失;临时节点的存活时间依赖于客户端会话,当会话结束,临时节点将会被自动删除。(当然也可以手动删除临时节点)注意:临时节点不能拥有子节点。通过create /zk_test1 data1就创建了一个数据为data1的持久节点zk_test1,通过create -e /zk_test2 data2就创建了一个数据为data2的临时节点zk_test2。同时,create命令还可以加 -s 参数指定节点是否有序,创建顺序节点时,zk 会在路径后面自动追加一个递增的序列号 ,这个序列号可以保证在同一个父节点下是唯一的,具体有如下四种节点:
Zookeeper的Watcher是一种事件监听机制,用于监视Zookeeper上节点的状态变化。当某个节点发生变化时,Zookeeper会向客户端发送通知,从而触发客户端的Watcher回调函数。
具体实现步骤如下:
Watcher具有以下特点:
数据复制机制
Zookeeper采用了一种高度可靠的数据复制机制,即ZAB(Zookeeper Atomic Broadcast)协议,用于在多个服务器之间同步数据。在ZAB协议中,每个服务器都可以扮演Leader和Follower两个角色。Leader负责处理客户端请求,Follower负责从Leader处同步数据,并保持数据的一致性。当Leader失效时,Follower会发起选举,选出新的Leader来处理客户端请求。ZAB协议保证了数据的一致性和可靠性,即使有多个服务器失效,也能保证数据的安全。
严格的数据版本控制
Zookeeper采用了严格的数据版本控制机制,每个节点都有一个版本号,每次更新节点数据时,版本号都会自增。客户端在修改节点数据时,需要指定版本号,只有当版本号与当前节点的版本号匹配时,才能成功修改节点数据。这种版本控制机制可以防止多个客户端同时修改同一个节点数据的问题,保证了数据的一致性和可靠性。
Zookeeper实现分布式锁主要基于其节点操作和Watcher机制来实现。具体过程如下:
上述过程中,通过创建临时有序节点,Zookeeper保证了节点的唯一性和顺序性,通过Watcher机制可以实现节点状态的监听和回调。这种分布式锁机制能够有效避免分布式环境下的资源竞争问题,保证了分布式应用的数据一致性和可靠性。
安装包下载地址,这里选择最新的稳定版本3.7.1,然后下载后缀为bin.tar.gz的压缩包。
上传到服务器某位置,解压tar -zxvf apache-zookeeper-3.7.1-bin.tar.gz
,进入conf目录,将zoo_simple.cfg文件重命名为zoo.cfg,mv zoo_sample.cfg zoo.cfg
,高版本的zookeeper默认加载conf/zoo.cfg,修改配置文件如下
#ZooKeeper 使用的基本时间单位,以毫秒为单位。它用于执行心跳,最小会话超时将是 tickTime 的两倍
tickTime=2000
#initLimit和syncLimit是针对集群的参数,分别为通信时限和同步时限
initLimit=10
syncLimit=5
#存储内存数据库快照的位置,除非另有说明,否则存储更新数据库的事务日志
dataDir=/usr/local/zookeeper
#客户端连接的端口
clientPort=2181
执行bin/zkServer.sh start
命令启动zookeeper服务
执行bin/zkCli.sh -server 127.0.0.1:2181
连接到zookeeper服务端
可以根据官方文档查看命令,比如:
version
查看版本信息
create /test_node test_data
创建节点,且存储数据为字符串test_data
get /test_node
获取节点数据
将上述单机步骤的配置文件修改为zoo-1.cfg,mv zoo.cfg zoo-1.cfg
,修改配置文件
#ZooKeeper 使用的基本时间单位,以毫秒为单位。它用于执行心跳,最小会话超时将是 tickTime 的两倍
tickTime=2000
#initLimit和syncLimit是针对集群的参数,分别为通信时限和同步时限
initLimit=10
syncLimit=5
#存储内存数据库快照的位置,除非另有说明,否则存储更新数据库的事务日志
dataDir=/usr/local/zookeeper1
#客户端连接的端口
clientPort=2181
#server.<节点ID>=:<数据同步端口>:<选举端口>
#<节点ID>:代表第几号服务器
#:这个服务器的IP
#<数据同步端口>:这个服务器与集群中的Leader服务器交换信息的端口
#<选举端口>:如果Leader挂了,需要一个新的端口进行选举,选出的新Leader的端口
server.1=127.0.0.1:2888:3888
server.2=127.0.0.1:2889:3889
server.3=127.0.0.1:2890:3890
接下来我们复制zoo-1.cfg,复制出2和3,修改clientPort和dataDir即可。然后,在前面创建的三个存储目录编辑myid文件,分别输入1,2,3,接下来通过使用不同配置文件启动
bin/zkServer.sh start conf/zoo-1.cfg
bin/zkServer.sh start conf/zoo-2.cfg
bin/zkServer.sh start conf/zoo-3.cfg
通过查看各个节点状态,可以发现1和3是从节点,2是主节点
bin/zkServer.sh status conf/zoo-1.cfg
bin/zkServer.sh status conf/zoo-2.cfg
bin/zkServer.sh status conf/zoo-3.cfg
那么接下来说一说leader的选举机制
第一次启动: 目前有1、2、3三台机器,依次启动
非第一次启动:
在Zookeeper官网提供了Java Api的示例,但是原生的Api需要开发人员创建对象、注册watcher等,因此这里使用一个封装好的curator,它提供了各种应用场景,实现了Fluent风格的Api接口,是操作zk的最好的框架。
<dependency>
<groupId>org.apache.zookeepergroupId>
<artifactId>zookeeperartifactId>
<version>3.7.1version>
dependency>
<dependency>
<groupId>org.apache.curatorgroupId>
<artifactId>curator-frameworkartifactId>
<version>4.0.1version>
dependency>
<dependency>
<groupId>org.apache.curatorgroupId>
<artifactId>curator-recipesartifactId>
<version>4.0.1version>
dependency>
server.port=8002
server.servlet.context-path=/zk
#zk地址
curator.connectString=192.168.1.1:2181,192.168.1.1:2182,192.168.1.1:2183
#重试次数
curator.retryCount=1
#重试时间间隔
curator.elapsedTimeMs=2000
#session超时时间
curator.sessionTimeoutMs=6000
#连接超时时间
curator.connectionTimeoutMs=10000
配置类
package com.example.zookeeper.zookeeper;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.RetryNTimes;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ZKConfig {
@Value("${curator.connectString}")
private String connectString;
@Value("${curator.retryCount}")
private String retryCount;
@Value("${curator.elapsedTimeMs}")
private String elapsedTimeMs;
@Value("${curator.sessionTimeoutMs}")
private String sessionTimeoutMs;
@Value("${curator.connectionTimeoutMs}")
private String connectionTimeoutMs;
@Bean("CuratorFramework")
public CuratorFramework curatorFramework() throws InterruptedException {
CuratorFramework curatorFramework = CuratorFrameworkFactory.newClient(
connectString,
Integer.valueOf(sessionTimeoutMs),
Integer.valueOf(connectionTimeoutMs),
new RetryNTimes(Integer.valueOf(retryCount),Integer.valueOf(elapsedTimeMs))
);
curatorFramework.start();
curatorFramework.blockUntilConnected();
return curatorFramework;
}
}
测试类
package com.example.zookeeper.zookeeper;
import org.apache.curator.framework.CuratorFramework;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.nio.charset.StandardCharsets;
@RestController
public class TestController {
@Autowired
private CuratorFramework client;
@RequestMapping("/test")
public void test(){
System.out.println(client.toString());
}
@RequestMapping("/query")
public String query(@RequestParam String url) throws Exception{
byte[] data = client.getData().forPath(url);
return new String(data);
}
@RequestMapping("/put")
public String put(@RequestParam String url,String value) throws Exception{
String path = client.create().creatingParentsIfNeeded().forPath(url, value.getBytes(StandardCharsets.UTF_8));
return path;
}
}