1、Zookeeper概述
zookeeper是一个开源的分布式协调服务框架,主要用来解决分布式集群中应用系统的一致性问题和数据管理问题。
1.1、Zookeeper的设计目标
1)简单的数据模型:使得分布式程序能通过一个共享的,树形结构的名字空间来相互协调。其数据模型类似于一个文件系统。
2)可以构建集群:集群的每台机器都会在内存中维护服务器状态,并且每台机器互相保持通信,只要有半数以上的机器正常工作,就能正常对外服务。
3)顺序访问:每个更新请求,都有有一个全局唯一的递增编号。
4)高性能:ZooKeeper将全量数据存储在内存中,适用于以读为主的应用场景。
1.2、Zookeeper在保证分布式一致性时,具有以下五点特性:
1)顺序一致性:同一客户端发起的事务请求,会严格地按照其发起的顺序送到ZooKeeper。
2)原子性:要么所有节点都成功执行了事务,要么全都没有执行
3)单一试图:无论客户端连接的是哪个ZooKeeper服务器,看到的数据模型都是一样的。
4)可靠性:事务引起的服务端状态变更会被一直保留下来。
5)实时性:ZooKeeper仅仅保证一定时间内,客户端能读到最新数据。
1.3、Zookeeper中的基本概念
1)会话:会话周期从第一次建立连接开始,客户端能通过心跳检测与服务器保持有效会话。
2)数据节点Znode:
3)Wacher事件监听器:
4)ACL(权限控制策略):
1.4、Zookeeper中的ZAB协议
zookeeper的数据一致性算法所采用的是ZooKeeper Atomic Broadcast(ZAB,Zookeeper原子信息广播协议)。此协议是为分布式协调服务Zookeepe专门设计的一种支持崩溃服务的原子广播协议。
1.4.1、协议中的两种基本模式(后续有机会遇到再说吧)
1)崩溃恢复模式
2)消息广播模式
2、Zookeeper常规操作
2.1、Zookeeper运行环境
1)单机模式:用于开发和测试。
2)伪分布式:一台物理机虚拟机几台虚拟机搭建的Zookeeper集群。
3)分布式:
2.2、客户端脚本的shell操作
2.3、Zookeeper原生JavaAPI操作
2.4、开源客户端zkClient操作
2.5、开源客户端Curator操作
curator客户端创建会话的方法和前两种方法有着一定的不同:
1)使用CuratorFrameworkFactory这个工厂类的两个静态方法(参数不用的newClient方法)来创建一个客户端。
2)使用CuratorFramework中的start()方法来启动方法。
(1)环境搭建(pom.xml)
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>3.2.1</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>3.2.1</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-client</artifactId>
<version>3.2.1</version>
</dependency>
(2)必要的环境变量
public class CommonConfig {
public static final String NAMESPACE = "test";
// retryPolicy 指的是连接过程中的重试策略,参数中的1000指的是响应时间1秒,3指的是重试次数。
public static final ExponentialBackoffRetry retryPolicy = new ExponentialBackoffRetry(1000, 3);
public static final String LOCALHOST = "127.0.0.1:2181";
// 创建client的两种方式:
public static final CuratorFramework defaultClient = CuratorFrameworkFactory.builder()
.connectString(LOCALHOST).retryPolicy(retryPolicy)
.connectionTimeoutMs(1000)
.sessionTimeoutMs(1000)
.namespace(NAMESPACE)//每个对话创建一个隔离的命名空间
.build();
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 1);
CuratorFramework client = CuratorFrameworkFactory.newClient("master:2181,slave1:2181,slave2:2181",1000,1000,retryPolicy);
}
(3)基本操作(这部分都是使用Curator框架中的所提供的同步接口)
/**
* 基本操作:创建会话,创建节点,删除节点,获取节点存储内容,更新节点存储的内容
*/
public class BaseOperation {
public static final String PATH = "/basictest";
public static void main(String[] args) {
//创建会话对象
CuratorFramework client = CommonConfig.defaultClient;
//开始会话
client.start();
try {
//创建一个节点,初始内容为空
client.create().forPath(PATH);
//创建一个节点,附带初始内容
client.create().forPath(PATH, "basictest1".getBytes());
//创建一个临时节点,附带初始内容
client.create().withMode(CreateMode.EPHEMERAL).forPath(PATH, "basictest2".getBytes());
//创建一个临时节点,父节点不存在,递归创建父节点,创建的父节点都是持久节点
client.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).forPath(PATH, "basictest3".getBytes());
//关闭会话
Closeables.close(client, true);
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void delete(CuratorFramework client, String path) throws Exception {
//删除一个节点
client.delete().forPath(path);
//删除一个节点,递归删除子节点
client.delete().deletingChildrenIfNeeded().forPath(path);
//删除一个节点,强制指定版本进行删除
client.delete().withVersion(1).forPath(path);
//删除一个节点,强制保证删除。Curator引入了重试机制,一旦删除失败,会在后台反复重试直到删除成功
client.delete().guaranteed().forPath(path);
return;
}
public static String get(CuratorFramework client, String path) throws Exception {
//读取一个节点的内容
String str = new String(client.getData().forPath(path));
//读取一个节点的内容,同时获取到该节点的stat,stat保存的是从服务器获取到的状态数据
Stat stat = new Stat();
client.getData().storingStatIn(stat).forPath(path);
return str;
}
public static Stat set(CuratorFramework client, String path) throws Exception {
Stat stat = new Stat();
//更新一个节点的数据内容,返回Stat对象
stat = client.setData().forPath(path);
//更新一个节点的数据内容,强制指定版本进行更新,用来实现CAS
stat = client.setData().withVersion(1).forPath(path);
return stat;
}
}
(4)创建节点的异步操作
在Zookeeper中所有的异步通知事件处理,都是由EventThread这个线程来处理的,EventThread采用的是串行处理机制,所以未传入线程池的事件会被EventThread默认处理,也就是主线程。
static String path = "/backgroundtest";
// 所以这个测试的意思就是:使用两个异步创建节点的实例来进行对比实验,一个实例在异步创建的时候传入ExecutorService 线程池,一个不传入。
//创建client
static CuratorFramework client = CommonConfig.defaultClient;
// Java并发工具
static CountDownLatch semaphore = new CountDownLatch(2);
// 创建一个可重用固定线程数的线程池
static ExecutorService tp = Executors.newFixedThreadPool(2);
public static void main(String[] args) {
try {
client.start();
System.out.println("Main thread: " + Thread.currentThread().getName());
// 此处传入了自定义的Executor
client.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).inBackground(new BackgroundCallback() {
@Override
public void processResult(CuratorFramework client, CuratorEvent event) throws Exception {
System.out.println("event[code: " + event.getResultCode() + ", type: " + event.getType() + "]");
System.out.println("Thread of processResult: " + Thread.currentThread().getName());
semaphore.countDown();
}
}, tp).forPath(path, "init1".getBytes());
// 此处没有传入自定义的Executor
client.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).inBackground(new BackgroundCallback() {
@Override
public void processResult(CuratorFramework client, CuratorEvent event) throws Exception {
System.out.println("event[code: " + event.getResultCode() + ", type: " + event.getType() + "]");
System.out.println("Thread of processResult: " + Thread.currentThread().getName());
semaphore.countDown();
}
}).forPath(path, "init2".getBytes());
semaphore.await();
Closeables.close(client, true);
} catch (Exception e) {
e.printStackTrace();
} finally {
tp.shutdown();
}
}
(5)事件监听
Zookeeper原生通过Watcher注册来进行事件监听,但是这需要开发人员反复的注册Watcher,比较繁琐。Curator引入Cache来实现对Zookeeper服务端事件的监听,其对事件的监听可以类比成一个本地缓存视图和远程Zookeeper视图的对比过程。Cache分为两种监听类型:节点监听(NodeCache)和子节点监听(PathChildrenCache)。
节点监听
节点监听用于监听数据节点自身变化。
/**
* @author : HaiLiang Huang
* @date : 2021年4月14日15:18:47
*/
public class CuratorBaseOperator {
public static final String PATH = "/nodeCache";
public static final CuratorFramework client = CommonConfig.defaultClient;
public static void main(String[] args) throws Exception {
client.start();
client.create()
.creatingParentsIfNeeded()
.withMode(CreateMode.EPHEMERAL)
.forPath(PATH,"init".getBytes());
final NodeCache nodeCache = new NodeCache(client, PATH, false);
nodeCache.start(true);
nodeCache.getListenable().addListener(new NodeCacheListener() {
@Override
public void nodeChanged() throws Exception {
System.out.println("Node data update, new data: " +
new String(nodeCache.getCurrentData().getData()));
}
});
client.setData().forPath(PATH, "u".getBytes());
Thread.sleep(1000);
client.delete().deletingChildrenIfNeeded().forPath(PATH);
Thread.sleep(Integer.MAX_VALUE);
}
}
子节点监听
PathChildrenCache用于监听子节点的变化(新增子节点、子节点数据变更、子节点删除)
无法监听二级子节点的变化
public class PathChildrenCacheDemo {
static String path = "/pathChiledrenCache";
static CuratorFramework client = CommonConfig.defaultClient;
static ExecutorService tp = Executors.newFixedThreadPool(2);
public static void main(String[] args) throws Exception {
client.start();
PathChildrenCache cache = new PathChildrenCache(client, path, true, false, tp);
cache.start(PathChildrenCache.StartMode.POST_INITIALIZED_EVENT);
cache.getListenable().addListener(new PathChildrenCacheListener() {
public void childEvent(CuratorFramework client,
PathChildrenCacheEvent event) throws Exception {
switch (event.getType()) {
case CHILD_ADDED:
System.out.println("CHILD_ADDED," + event.getData().getPath());
break;
case CHILD_UPDATED:
System.out.println("CHILD_UPDATED," + event.getData().getPath());
break;
case CHILD_REMOVED:
System.out.println("CHILD_REMOVED," + event.getData().getPath());
break;
default:
break;
}
}
});
client.create().withMode(CreateMode.PERSISTENT).forPath(path);
Thread.sleep(1000);
client.create().withMode(CreateMode.PERSISTENT).forPath(path + "/c1");
Thread.sleep(1000);
client.delete().forPath(path + "/c1");
Thread.sleep(1000);
client.delete().forPath(path);
Thread.sleep(Integer.MAX_VALUE);
}
}
(6)Master选举
/**
* Master选举。选择一个根节点,多台机器同时创建一个自己节点/lock。
* 利用zookeeper的特性,最终只能有一个创建成功
* 成功获取到Master权利之后,会回调监听器的takeLeaderShip()方法,
* 一旦方式执行完毕,会立即释放掉Master权利,监听器需要开发者自定义实现
* 当一个应用实例称为Master之后,其他竞选者会进入等待,
* 直到当前Master挂了或退出后才开始下一轮的竞选
*/
public class LeaderSelectDemo {
static String master_path = "/masterSelect";
static CuratorFramework client = CommonConfig.defaultClient;
public static void main(String[] args) throws Exception {
client.start();
LeaderSelector selector = new LeaderSelector(client,
master_path,
new LeaderSelectorListenerAdapter() {
public void takeLeadership(CuratorFramework client) throws Exception {
System.out.println("成为Master角色");
Thread.sleep(3000);
System.out.println("完成Master操作,释放Master权利");
}
});
selector.autoRequeue();
selector.start();
Thread.sleep(Integer.MAX_VALUE);
}
}
(7)分布式锁
/**
* 分布式锁:
*/
public class Lock {
static String lock_path = "/lock";
static CuratorFramework client = CommonConfig.defaultClient;
public static void main(String[] args) throws Exception {
client.start();
final InterProcessMutex lock = new InterProcessMutex(client, lock_path);
final CountDownLatch down = new CountDownLatch(1);
for (int i = 0; i < 30; i++) {
new Thread(new Runnable() {
public void run() {
try {
down.await();
lock.acquire();
} catch (Exception e) {
}
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss|SSS");
String orderNo = sdf.format(new Date());
System.out.println("生成的订单号是 : " + orderNo);
try {
lock.release();
} catch (Exception e) {
}
}
}).start();
}
down.countDown();
}
}
(8)分布barrier
/**
* Created by wangzhongqiu on 2018/5/6.
* 分布式Barrier:分为主动放开栅栏和达到指定数量自动放开栅栏
*/
public class Barrier {
static String barrier_path = "/barrier";
static DistributedBarrier barrier;
static CuratorFramework client = CommonConfig.defaultClient;
public static void main(String[] args) throws Exception {
atoBarrier();
}
public static void barrier() throws Exception {
barrier = new DistributedBarrier(client, barrier_path);
client.start();
barrier.setBarrier();
for (int i = 0; i < 5; i++) {
new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName() + "号barrier设置");
barrier.waitOnBarrier();
System.err.println("启动...");
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
Thread.sleep(2000);
barrier.removeBarrier();
}
public static void atoBarrier() throws Exception {
client.start();
for (int i = 0; i < 5; i++) {
new Thread(new Runnable() {
@Override
public void run() {
try {
DistributedDoubleBarrier barrier = new DistributedDoubleBarrier(client, barrier_path, 5);
System.out.println(Thread.currentThread().getName() + "号barrier设置");
barrier.enter();
System.err.println("启动...");
barrier.leave();
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
Thread.sleep(2000);
}
}
(9)分布式计数器
/**
* Created by wangzhongqiu on 2018/5/6.
* 分布式计数器:指定一个节点作为计数器,
* 多个机器在分布式锁的控制下,通过更新该节点的数据来实现分布式计数器功能
*/
public class DistAtomicInt {
static String distatomicint_path = "/distAtomicInt";
static CuratorFramework client = CommonConfig.defaultClient;
public static void main(String[] args) throws Exception {
client.start();
DistributedAtomicInteger atomicInteger =
new DistributedAtomicInteger(client, distatomicint_path,
new RetryNTimes(3, 1000));
AtomicValue<Integer> rc = atomicInteger.add(8);
System.out.println("Result: " + rc.succeeded());
}
}
(10)工具–ZKPaths
/**
* @date 2018/5/7.
* 工具类:ZKPaths,提供了一些简答的API来构建Znode路径、递归创建和删除节点等。使用方式非常方便
*/
public class ZKPathsDemo {
static String path = "/zkpath";
static CuratorFramework client = CommonConfig.defaultClient;
public static void main(String[] args) throws Exception {
client.start();
ZooKeeper zookeeper = client.getZookeeperClient().getZooKeeper();
System.out.println(ZKPaths.fixForNamespace(path, "sub"));
System.out.println(ZKPaths.makePath(path, "sub"));
System.out.println(ZKPaths.getNodeFromPath("/curator_zkpath_sample/sub1"));
ZKPaths.PathAndNode pn = ZKPaths.getPathAndNode("/curator_zkpath_sample/sub1");
System.out.println(pn.getPath());
System.out.println(pn.getNode());
String dir1 = path + "/child1";
String dir2 = path + "/child2";
ZKPaths.mkdirs(zookeeper, dir1);
ZKPaths.mkdirs(zookeeper, dir2);
System.out.println(ZKPaths.getSortedChildren(zookeeper, path));
ZKPaths.deleteChildren(client.getZookeeperClient().getZooKeeper(), path, true);
}
}
(11)工具–EnsurePath
/**
* @date 2018/5/7.
* 工具类:能够确保数据节点存在的机制。内部实现了试图创建节点,
* 如果已经存在那么就不进行任何操作,也不对外抛出异常,否则正常创建数据节点
*/
public class EnsurePathDemo {
static String path = "/ensurePath/c1";
static CuratorFramework client = CommonConfig.defaultClient;
public static void main(String[] args) throws Exception {
client.start();
EnsurePath ensurePath = new EnsurePath(path);
ensurePath.ensure(client.getZookeeperClient());
ensurePath.ensure(client.getZookeeperClient());
EnsurePath ensurePath2 = client.newNamespaceAwareEnsurePath("/c1");
ensurePath2.ensure(client.getZookeeperClient());
}
}
(12)TestingCluster
/**
* 测试工具类:是一个可以模拟Zookeeper集群的环境的工具类
*/
public class TestingClusterDemo {
public static void main(String[] args) throws Exception {
TestingCluster cluster = new TestingCluster(3);
cluster.start();
Thread.sleep(2000);
TestingZooKeeperServer leader = null;
for(TestingZooKeeperServer zs : cluster.getServers()){
System.out.print(zs.getInstanceSpec().getServerId()+"-");
System.out.print(zs.getQuorumPeer().getServerState()+"-");
System.out.println(zs.getInstanceSpec().getDataDirectory().getAbsolutePath());
if( zs.getQuorumPeer().getServerState().equals( "leading" )){
leader = zs;
}
}
leader.kill();
System.out.println( "--After leader kill:" );
for(TestingZooKeeperServer zs : cluster.getServers()){
System.out.print(zs.getInstanceSpec().getServerId()+"-");
System.out.print(zs.getQuorumPeer().getServerState()+"-");
System.out.println(zs.getInstanceSpec().getDataDirectory().getAbsolutePath());
}
cluster.stop();
}
}
(13)TestingServer
/**
* @date 2018/5/7.
* 测试工具类:能够启动一个简易的标准的zookeeper服务,以此来进行一系列的单元测试
*/
public class TestingServerDemo {
static String path = "/test";
public static void main(String[] args) throws Exception {
TestingServer server = new TestingServer(2181, new File("/home/admin/zk-test-data"));
CuratorFramework client = CuratorFrameworkFactory.builder()
.connectString(server.getConnectString())
.sessionTimeoutMs(5000)
.retryPolicy(new ExponentialBackoffRetry(1000, 3))
.build();
client.start();
System.out.println(client.getChildren().forPath(path));
server.close();
}
}