Zookeeper是一种分布式,开源的应用协调服务也即是“服务中间件”;主要提供的功能包括:“配置管理”,“分布式锁”,“集群管理”。
1、配置管理:管理服务对象的信息(IP、端口、服务提供功能接口)。
2、分布式锁:控制服务对象的资源(锁住之后,只能有一个服务对象可操作,其它服务对象等待)。
3、集群管理:管理服务对象的节点(每个节点提供的服务相同,则组成一个集群),组成高可用
Zookeeper原理
Zookeeper是一个树形目录服务,其数据模型拥有一个层次化的结构;从图中可看出:一个节点可以保存自己的数据和节点信息,也可以拥有子节点。
Zookeeper指令
1.服务端指令
linux指令 | 说明 |
---|---|
./zkServer.sh.start | 启动服务 |
./zkServer.sh.status | 服务状态 |
./zkServer.sh.stop | 停止服务 |
./zkServer.sh.restart | 重启服务 |
2.客户端命令
linux指令 | 说明 |
---|---|
./zkcli.sh -server ip:port | 连接服务端 |
quit | 断开连接 |
ls path | 显示指定目录下节点 |
create -[e/s/es] / path value | 创建单个节点 |
set /path value | 设置节点 |
get /path | 获取节点值 |
delete /path | 删除单个节点 |
deleteall /path | 删除所有节点 |
Curator 客户端
curator是Zookeeper的java客户端工具,也是阿帕奇基金组织的顶级项目;他封装了Zookeeper一些javaAPI的方法来对节点进行操作。
1.建立连接
public class CuratorTest {
//==============================Connect:建立连接========================================================================
private CuratorFramework client;
/**
* 建立连接
* 第一种:CuratorFrameworkFactory.newClient
* 第二种:CuratorFrameworkFactory.builder()
*/
@Before
public void testConnect() {
/*
* 第一种连接方式
* CuratorFrameworkFactory.newClient
* @param connectString 连接字符串。zk server 地址和端口 "192.168.149.135:2181"
* @param sessionTimeoutMs 会话超时时间 单位ms
* @param connectionTimeoutMs 连接超时时间 单位ms
* @param retryPolicy 重试策略
*/
// 重试策略
// RetryPolicy retryPolicy = new ExponentialBackoffRetry(3000, 10);
// 创建连接对象
// CuratorFramework client = CuratorFrameworkFactory.newClient("ZKIP:2181",
// 60 * 1000, 15 * 1000, retryPolicy);
// 开启连接
// client.start();
/**
* 第二种连接方式
* CuratorFrameworkFactory.builder()
* namespace:/myCurator/创建的节点
*/
//重试策略
RetryPolicy retryPolicy = new ExponentialBackoffRetry(3000, 10);
//创建连接对象
client = CuratorFrameworkFactory.builder()
.connectString("ZKIP:2181")
.sessionTimeoutMs(60 * 1000)
.connectionTimeoutMs(15 * 1000)
.retryPolicy(retryPolicy)
.namespace("myCurator")
.build();
//开启连接
client.start();
}
2.创建节点
public class CuratorTest {
//==============================Create:创建节点========================================================================
/**
* 创建节点:create
* 节点类型:持久,临时,持久顺序,临时顺序节点
* 1.基本创建 :create().forPath("")
* 2.创建节点 带有数据:create().forPath("",data)
* 3.设置节点的类型:create().withMode().forPath("",data)
* 4.创建多级节点 /app1/p1 :create().creatingParentsIfNeeded().forPath("",data)
*/
@Test
public void testCreate1() throws Exception {
//1.基本创建:如果创建节点,没有指定数据,则默认将当前客户端的ip作为数据存储
String s = client.create().forPath("/app1");
System.out.println(s);
}
@Test
public void testCreate2() throws Exception {
//2.创建节点,带数据
String path = client.create().forPath("/app2", "大王来巡山".getBytes());
System.out.println(path);
}
@Test
public void testCreate3() throws Exception {
//3.设置节点类型
// PERSISTENT:持久化节点
// PERSISTENT_SEQUENTIAL:持久化顺序节点
// EPHEMERAL:临时节点
// EPHEMERAL_SEQUENTIAL:临时顺序节点
// CONTAINER(4, false, false, true, false),
// PERSISTENT_WITH_TTL(5, false, false, false, true),
// PERSISTENT_SEQUENTIAL_WITH_TTL(6, false, true, false, true);
String path = client.create().withMode(CreateMode.EPHEMERAL).forPath("/app3");
System.out.println(path);
}
@Test
public void testCreate4() throws Exception {
//4.创建多级节点
//creatingParentsIfNeeded():如果父节点不存在,则创建父节点
String path = client.create().creatingParentContainersIfNeeded().forPath("/app4/p1");
System.out.println(path);
}}
3.查询节点
public class CuratorTest {
//==============================Cet:查询节点============================================================================
/**
* 查询节点:
* 1. 查询数据:get: getData().forPath()
* 2. 查询子节点: ls: getChildren().forPath()
* 3. 查询节点状态信息:ls -s:getData().storingStatIn(状态对象).forPath()
*/
@Test
public void testGet1() throws Exception {
//1.查询数据
byte[] value = client.getData().forPath("/app2");
System.out.println(new String(value));
}
@Test
public void testGet2() throws Exception {
//2.查询子节点
List<String> path = client.getChildren().forPath("/app4");
System.out.println(path);
}
@Test
public void testGet3() throws Exception {
//3.查询节点状态信息
Stat status = new Stat();
client.getData().storingStatIn(status).forPath("/app2");
System.out.println(status);
}}
4.修改节点
public class CuratorTest {
//==============================Set:修改节点============================================================================
/**
* 修改数据
* 1. 基本修改数据:setData().forPath()
* 2. 根据版本修改: setData().withVersion().forPath()
* version 是通过查询出来的。目的就是为了让其他客户端或者线程不干扰我。
*/
@Test
public void testSet() throws Exception {
//1.修改基本数据
client.setData().forPath("/app2", "芒果".getBytes());
}
@Test
public void testSetVersion() throws Exception {
//1.修改节点版本
//1.查询当前节点版本
Stat status = new Stat();
client.getData().storingStatIn(status).forPath("/app2");
int vesion = status.getVersion();
System.out.println(vesion);
//1.2修改当前节点版本
client.setData().withVersion(vesion).forPath("/app2", "王老吉".getBytes());
}}
5.删除节点
public class CuratorTest {
//==============================delete:删除节点=========================================================================
/**
* 删除节点: delete deleteall
* 1. 删除单个节点:delete().forPath("/app1");
* 2. 删除带有子节点的节点:delete().deletingChildrenIfNeeded().forPath("/app1");
* 3. 必须成功的删除:为了防止网络抖动。本质就是重试。 client.delete().guaranteed().forPath("/app2");
* 4. 回调:inBackground
*/
@Test
public void testDelete1() throws Exception {
// 1. 删除单个节点
client.delete().forPath("/app1");
}
@Test
public void testDelete2() throws Exception {
// 2. 删除带有子节点的节点
client.delete().deletingChildrenIfNeeded().forPath("/app4");
}
@Test
public void testDelete13() throws Exception {
// 3. 必须成功的删除
client.delete().guaranteed().forPath("/app2");
}
@Test
public void testDelete14() throws Exception {
// 4. 回调
client.delete().guaranteed().inBackground(new BackgroundCallback() {
@Override
public void processResult(CuratorFramework client, CuratorEvent event) throws Exception {
System.out.println("节点被删除");
System.out.println(event);
}
}).forPath("/app2");
}}
6.关闭连接
public class CuratorTest {
//==============================close:关闭连接======================================================================
@After
public void close() {
if (client != null) {
client.close();
}
}}
Watch事件监听
Zookeeper的监听机制是指在节点上绑定监听器,当特定事件触发时,Zookeeper服务端会将事件状态变化通知到订阅了该节点的客户端上去。
Watcher实现了发布/订阅功能,能让订阅者(客户端)同时监听某一个节点,当订阅对象自身状态变化时,Zookeeper服务器会通知所有的订阅者。
1.监听分类
watcher的监听实现,是借助于Curator的Cache来实现对Zookeeper服务端事件的监听。而Zookeeper提供了以下三种Watcher:
1.1、NodeCache:监听某个特定节点。
1.2、PathChildrenCache:监听ZNode的子节点。
1.3、TreeCache:监控整个树上的所有节点(类似于NodeCache+PathChildrenCache的集合)。
2.监听实现
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.cache.*;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
public class WatcherTest {
private CuratorFramework client;
//==============================start:开启连接==========================================================================
//建立连接
@Before
public void testConnection() {
//重试策略
RetryPolicy retryPolicy = new ExponentialBackoffRetry(3000, 10);
//创建连接对象
client = CuratorFrameworkFactory.builder()
.connectString("192.168.213.1:2181")
.sessionTimeoutMs(60 * 1000)
.connectionTimeoutMs(15 * 1000)
.retryPolicy(retryPolicy)
.namespace("myCurator")
.build();
//开启连接
client.start();
}
//==============================Watcher:开启监听========================================================================
@Test
public void testWatcher1() throws Exception {
//1.监听一个特定的节点:当节点发生增删改变化时,会将监听到的变化反馈给订阅者
//1.1创建监听对象
NodeCache nodeCache = new NodeCache(client, "/app2");
//1.2注册监听事件
nodeCache.getListenable().addListener(new NodeCacheListener() {
@Override
public void nodeChanged() throws Exception {
System.out.println("节点发生变化了");
//1.3获取节点变化后的数据
byte[] data = nodeCache.getCurrentData().getData();
System.out.println(new String(data));
}
});
//1.4 开启监听
nodeCache.start(true);
}
@Test
public void testWatcher2() throws Exception {
//2.监听Znode节点的子节点
//2.1 创建监听对象
PathChildrenCache childrenCache = new PathChildrenCache(client, "/app2", true);
//2.2 注册监听事件
childrenCache.getListenable().addListener(new PathChildrenCacheListener() {
@Override
public void childEvent(CuratorFramework client, PathChildrenCacheEvent event) throws Exception {
System.out.println("子节点发生变化了");
System.out.println(event);
//2.3 监听子节点的数据变更,并且获取变更后的数据
//2.3.1 获取数据类型
PathChildrenCacheEvent.Type type = event.getType();
//2.3.2 判断数据类型是否变更
if (type.equals(PathChildrenCacheEvent.Type.CHILD_UPDATED)) {
System.out.println("数据发生变更");
byte[] data = event.getData().getData();
System.out.println(new String(data));
}
}
});
//2.4 开启监听
childrenCache.start();
}
@Test
public void testWatcher3() throws Exception {
//3. 监听某个节点自己和所有子节点们
//3.1创建监听对象
TreeCache treeCache = new TreeCache(client, "/app2");
//3.2注册监听事件
treeCache.getListenable().addListener(new TreeCacheListener() {
@Override
public void childEvent(CuratorFramework client, TreeCacheEvent event) throws Exception {
System.out.println("节点变化了");
System.out.println(event);
}
});
//3.3开启监听
treeCache.start();
}
//==============================close:关闭连接==========================================================================
//关闭连接
@After
public void close() {
if (client != null) {
client.close();
}
}
分布式锁的机制
分布式锁是协调跨机器的进程之间的数据同步的一种机制。
1.分布式锁原理
当客户端要获取锁,则会创建一个临时顺序节点,使用完锁后,就删除该节点;临时顺序节点有两点好处:
1)临时:为了防止客户端偶发宕机情况下,lock对象不能释放,形成死锁。
2)顺序:为客户端提供了排队功能,以便于注册监听删除事件,实现子节点序号的比对。
2.锁的实现过程
客户端在lock节点下创建"临时顺序节点"–>获取所有创建的"临时顺序节点"–>"临时顺序节点"进行比对:
1)当客户端发现自己创建的子节点序号是最小的,就获取锁,使用完锁后就删除该节点。
2)当客户端发现自己创建的子节点序号不是最小,不能获取锁,当前客户端会找比自己小的节点,同时对该节点绑定监听器,监听删除事件;如果客户端被通知该节点被删除,当前客户端会再一次比对自己创建的节点序号是否最小:是,获取锁;不是,重复监听,再次比对直到获取锁。
Zookeeper集群
1. 集群的角色
1.1、leader(领导者):处理事务请求和集群内部服务器协调(事务处理后的变化结果同步到各内部服务器)。
1.2、Follower(跟随者):处理客户端非事务请求,并将事务请求转发给leader; 参与leader选举投票。
1.3、Observer(观察者): 处理器客户端非事务请求,转发事务请求给leader.
2. 领导者选取
领导者的选取:Zookeeper集群每个服务器都有自己的Serverid,如果某台Zookeeper获得了超过半数选票,此台Zookeeper就成为领导者。(比如:三台Zookeeper,编号分别为1,2,3,根据半数原则编号为2的Zookeeper成为leader)。