七、Watcher

在ZooKeeper中,接口类Watcher用于表示一个标准的事件处理器,其定义了事件通知相关的逻辑,包含KeeperState和EventType两个枚举类,分别代表了通知状态和事件类型,同时定义了事件的回调方法:process(WatchedEvent event)。

7.1什么是Watcher接口

同一个事件类型在不同的通知状态中代表的含义有所不同,表7-3列举了常见的通知状态和事件类型。

表7-3 Watcher通知状态与事件类型一览

KeeperState

EventType

触发条件

说明

None
(-1)

客户端与服务端成功建立连接

SyncConnected
(0)

NodeCreated
(1)

Watcher监听的对应数据节点被创建

NodeDeleted
(2)

Watcher监听的对应数据节点被删除

此时客户端和服务器处于连接状态

NodeDataChanged
(3)

Watcher监听的对应数据节点的数据内容发生变更

NodeChildChanged
(4)

Wather监听的对应数据节点的子节点列表发生变更

Disconnected
(0)

None
(-1)

客户端与ZooKeeper服务器断开连接

此时客户端和服务器处于断开连接状态

Expired
(-112)

Node
(-1)

会话超时

此时客户端会话失效,通常同时也会受到SessionExpiredException异常

AuthFailed
(4)

None
(-1)

通常有两种情况,1:使用错误的schema进行权限检查 2:SASL权限检查失败

通常同时也会收到AuthFailedException异常

表7-3中列举了ZooKeeper中最常见的几个通知状态和事件类型。

回调方法process()

process方法是Watcher接口中的一个回调方法,当ZooKeeper向客户端发送一个Watcher事件通知时,客户端就会对相应的process方法进行回调,从而实现对事件的处理。process方法的定义如下:

abstract public void process(WatchedEvent event);

这个回调方法的定义非常简单,我们重点看下方法的参数定义:WatchedEvent。

WatchedEvent包含了每一个事件的三个基本属性:通知状态(keeperState),事件类型(EventType)和节点路径(path),其数据结构如图7-5所示。ZooKeeper使用WatchedEvent对象来封装服务端事件并传递给Watcher,从而方便回调方法process对服务端事件进行处理。

提到WatchedEvent,不得不讲下WatcherEvent实体。笼统地讲,两者表示的是同一个事物,都是对一个服务端事件的封装。不同的是,WatchedEvent是一个逻辑事件,用于服务端和客户端程序执行过程中所需的逻辑对象,而WatcherEvent因为实现了序列化接口,因此可以用于网络传输。

服务端在生成WatchedEvent事件之后,会调用getWrapper方法将自己包装成一个可序列化的WatcherEvent事件,以便通过网络传输到客户端。客户端在接收到服务端的这个事件对象后,首先会将WatcherEvent还原成一个WatchedEvent事件,并传递给process方法处理,回调方法process根据入参就能够解析出完整的服务端事件了。

需要注意的一点是,无论是WatchedEvent还是WatcherEvent,其对ZooKeeper服务端事件的封装都是机及其简单的。举个例子来说,当/zk-book这个节点的数据发生变更时,服务端会发送给客户端一个“ZNode数据内容变更”事件,客户端只能够接收到如下信

7.2代码

public class ZkClientWatcher implements Watcher {

// 集群连接地址

private static final String CONNECT_ADDRES = "192.168.110.159:2181,192.168.110.160:2181,192.168.110.162:2181";

// 会话超时时间

private static final int SESSIONTIME = 2000;

// 信号量,让zk在连接之前等待,连接成功后才能往下走.

private static final CountDownLatch countDownLatch = new CountDownLatch(1);

private static String LOG_MAIN = "【main】 ";

private ZooKeeper zk;

 

public void createConnection(String connectAddres, int sessionTimeOut) {

try {

zk = new ZooKeeper(connectAddres, sessionTimeOut, this);

System.out.println(LOG_MAIN + "zk 开始启动连接服务器....");

countDownLatch.await();

} catch (Exception e) {

e.printStackTrace();

}

}

 

public boolean createPath(String path, String data) {

try {

this.exists(path, true);

this.zk.create(path, data.getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);

System.out.println(LOG_MAIN + "节点创建成功, Path:" + path + ",data:" + data);

} catch (Exception e) {

e.printStackTrace();

return false;

}

return true;

}

 

/**

 * 判断指定节点是否存在

 *

 * @param path

 *            节点路径

 */

public Stat exists(String path, boolean needWatch) {

try {

return this.zk.exists(path, needWatch);

} catch (Exception e) {

e.printStackTrace();

return null;

}

}

 

public boolean updateNode(String path,String data) throws KeeperException, InterruptedException {

exists(path, true);

this.zk.setData(path, data.getBytes(), -1);

return false;

}

 

public void process(WatchedEvent watchedEvent) {

 

// 获取事件状态

KeeperState keeperState = watchedEvent.getState();

// 获取事件类型

EventType eventType = watchedEvent.getType();

// zk 路径

String path = watchedEvent.getPath();

System.out.println("进入到 process() keeperState:" + keeperState + ", eventType:" + eventType + ", path:" + path);

// 判断是否建立连接

if (KeeperState.SyncConnected == keeperState) {

if (EventType.None == eventType) {

// 如果建立建立成功,让后程序往下走

System.out.println(LOG_MAIN + "zk 建立连接成功!");

countDownLatch.countDown();

} else if (EventType.NodeCreated == eventType) {

System.out.println(LOG_MAIN + "事件通知,新增node节点" + path);

} else if (EventType.NodeDataChanged == eventType) {

System.out.println(LOG_MAIN + "事件通知,当前node节点" + path + "被修改....");

}

else if (EventType.NodeDeleted == eventType) {

System.out.println(LOG_MAIN + "事件通知,当前node节点" + path + "被删除....");

}

 

}

System.out.println("--------------------------------------------------------");

}

 

public static void main(String[] args) throws KeeperException, InterruptedException {

ZkClientWatcher zkClientWatcher = new ZkClientWatcher();

zkClientWatcher.createConnection(CONNECT_ADDRES, SESSIONTIME);

//boolean createResult = zkClientWatcher.createPath("/p15", "pa-644064");

zkClientWatcher.updateNode("/pa2","7894561");

}

 

}

、Zookeeper实战分布式锁

8.1线程进程资源竞争

线程进程资源竞争
当有一个线程或进程在对资源进行操作时,其他线程或进程都不可以对这个资
原进行操作,直到该线程或进程完成操作,其他线程或进程才能对该资源进
行操作,而其他线程或进程又处于等待状态。

8.2线程进程同步方式和机制

临界区

通过对多线程的串行化来访问公共资源或一段代码
synchronized 修饰的java方法
仅用于线程同步

互斥量

采用互斥对象机制。
只有拥有互斥对象的线程才有访问公共资
源竞争
的问题
源的权限
synchronized 修饰的代码块
java.util.concurrent.locks.Lock
分布式锁的主要实现机制

号量

它允许多个任务在同一时刻访问同一资源,但是需要限制在同一
时刻访问此资源的最大线程数目;
解决执
行顺序
CountDownLatch,CyclicBarrier和Semaphore
的问题

事件

通过通知操作的方式来保持任务的同步,还可以方便实现对多个
任务的优先级比较的操作

8.3分布式锁实现技术

基于数据实现分布式锁

性能较差,容易出现单点故障

锁没有失效事件,容易死锁。

非阻塞式

不可重入

基于缓存实现分布式锁

锁没有失效事件,容易死锁

非阻塞式

不可重入

基于Zookeeper实现分布式锁

实现相对简单

可靠性高

性能较好

8.4Zookeeper应用场景

数据发布订阅

负载均衡

命名服务

分布式协调

集群管理

配置管理

分布式队列

分布式

8.5Zookeeper实战分布式锁

场景描述

在线程高并发场景下,生成唯一的订单编号

如:2017-10-14-20-52-33-01

年 月 日 时 分 秒 序号

代码:

 

#####生成订单号######

import java.text.SimpleDateFormat;

import java.util.Date;

 

//生成订单号

public class OrderNumGenerator {

private static int count = 0;

    //生成订单号

public String getOrderNumber() {

SimpleDateFormat smt = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");

return smt.format(new Date()) + "-" + ++count;

}

 

}

#####订单业务逻辑######

public class OrderService implements Runnable {

private OrderNumGenerator orderNumGenerator = new OrderNumGenerator();

private static Object oj = new Object();

private Lock lock = new ZookeeperDistrbuteLock();

 

public void run() {

getNumber();

}

 

public void getNumber() {

// synchronized (oj) {

lock.getLock();

String orderNumber = orderNumGenerator.getOrderNumber();

System.out.println("获取订单号:" + orderNumber);

lock.unLock();

// }

 

}

 

public static void main(String[] args) {

for (int i = 0; i < 100; i++) {

new Thread(new OrderService()).start();

}

}

 

}

 

#####lock接口 ######

public interface Lock {

// 获取锁

public void getLock();

    // 释放锁

public void unLock();

}

 

#####ZookeeperAbstractLock抽象类接口 ######

public abstract class ZookeeperAbstractLock implements Lock {

private static final String CONNECT_ADDRES = "192.168.110.159:2181,192.168.110.160:2181,192.168.110.162:2181";

 

protected ZkClient zkClient = new ZkClient(CONNECT_ADDRES);

protected String PATH = "/lock";

 

public void getLock() {

// 如果当前节点已经存在,则等待

if (tryLock()) {

System.out.println("获取到锁 get");

} else {

// 等待

waitLock();

// 重新获取锁

getLock();

}

}

 

protected abstract void waitLock();

 

protected abstract boolean tryLock();

 

public void unLock() {

if (zkClient != null) {

zkClient.close();

}

System.out.println("已经释放锁...");

}

#####ZookeeperAbstractLock抽象类接口 ######

//实现锁

public class ZookeeperDistrbuteLock extends ZookeeperAbstractLock {

private CountDownLatch countDownLatch = new CountDownLatch(1);

 

@Override

protected boolean tryLock() {

try {

zkClient.createEphemeral(PATH);

// 创建成功

return true;

} catch (Exception e) {

// 创建失败

return false;

}

 

}

 

@Override

protected void waitLock() {

try {

IZkDataListener iZkDataListener = new IZkDataListener() {

 

public void handleDataDeleted(String path) throws Exception {

// 唤醒等待线程, 继续往下走.

if (countDownLatch != null) {

countDownLatch.countDown();

}

}

 

public void handleDataChange(String path, Object data) throws Exception {

 

}

};

// 注册到zk监听中

zkClient.subscribeDataChanges(PATH, iZkDataListener);

if (zkClient.exists(PATH)) {

countDownLatch = new CountDownLatch(1);

 

// 等待

countDownLatch.await();

 

}

// 删除事件通知

zkClient.unsubscribeDataChanges(PATH, iZkDataListener);

} catch (Exception e) {

// TODO: handle exception

}

}

 

}

分布式锁解决思路

分布式锁使用zk,在zk上创建一个临时节点(有效期)  ,使用临时节点作为锁,因为节点不允许重复。

如果能创建节点成功,生成订单号,如果创建节点失败,等待。临时节点zk关闭,释放锁,其他节点就可以重新生成订单号。

zookeeper的补充的更多相关文章

  1. Dubbo+zookeeper面试题补充

    什么是分布式?什么是集群?主要区别 分布式是将一个服务分个部分,然后通过远程调用方式进行.远程调用框架RPC框架,spring cloud,dubbo.集群是将同一个服务的多个副本部署在不同的集群上, ...

  2. 架构设计:远程调用服务架构设计及zookeeper技术详解(下篇)

    一.下篇开头的废话 终于开写下篇了,这也是我写远程调用框架的第三篇文章,前两篇都被博客园作为[编辑推荐]的文章,很兴奋哦,嘿嘿~~~~,本人是个很臭美的人,一定得要截图为证: 今天是2014年的第一天 ...

  3. ZooKeeper安装与配置

    一. 单机安装.配置: 1. 下载zookeeper二进制安装包下载地址:http://apache.dataguru.cn/zookeeper/zookeeper-3.4.3/zookeeper-3 ...

  4. ZooKeeper原理及使用

    ZooKeeper是Hadoop Ecosystem中非常重要的组件,它的主要功能是为分布式系统提供一致性协调(Coordination)服务,与之对应的Google的类似服务叫Chubby.今天这篇 ...

  5. zookeeper原理及作用

    ZooKeeper是Hadoop Ecosystem中非常重要的组件,它的主要功能是为分布式系统提供一致性协调(Coordination)服务,与之对应的Google的类似服务叫Chubby.今天这篇 ...

  6. zookeeper能做什么?

    Zookeeper是Hadoop的一个子项目,虽然源自hadoop,但是我发现zookeeper脱离hadoop的范畴开发分布式框架的运用越来越多.今天我想谈谈zookeeper,本文不谈如何使用zo ...

  7. solrCloud+tomcat+zookeeper集群配置

    solrcolud安装solrCloud+tomcat+zookeeper部署  转载请出自出处:http://eksliang.iteye.com/blog/2107002 http://eksli ...

  8. ZooKeeper学习第一期---Zookeeper简单介绍

    一.分布式协调技术 在给大家介绍ZooKeeper之前先来给大家介绍一种技术——分布式协调技术.那么什么是分布式协调技术?那么我来告诉大家,其实分布式协调技术主要用来解决分布式环境当中多个进程之间的同 ...

  9. Mac OS Storm+Kafka+Zookeeper配置

    先补充一个前两天mac配置的文档. 首先确定由jdk scala环境 JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/Cu ...

随机推荐

  1. python使用matplotlib在一个图形中绘制多个子图以及一个子图中绘制多条动态折线问题

    在讲解绘制多个子图之前先简单了解一下使用matplotlib绘制一个图,导入绘图所需库matplotlib并创建一个等间隔的列表x,将[0,2*pi]等分为50等份,绘制函数sin(x).当没有给定x ...

  2. A Simple Question of Chemistry

    #include<stdio.h> int main() { int i, l; ]; ]; l = ; ) { l++; } ; a[i]!= && i<l; i+ ...

  3. 有助于改善性能的Java代码技巧

    前言 程序的性能受到代码质量的直接影响.这次主要介绍一些代码编写的小技巧和惯例.虽然看起来有些是微不足道的编程技巧,却可能为系统性能带来成倍的提升,因此还是值得关注的. 慎用异常 在Java开发中,经 ...

  4. 如何更精准地设置 C# / .NET Core 项目的输出路径?(包括添加和删除各种前后缀)

    原文:如何更精准地设置 C# / .NET Core 项目的输出路径?(包括添加和删除各种前后缀) 我们都知道可以通过在 Visual Studio 中设置输出路径(OutputPath)来更改项目输 ...

  5. wpf 把两个Bitmap 合并为一个

    水平合并 /// <summary> /// 将两个Bitmap水平合并为一个 /// </summary> /// <param name="first&qu ...

  6. 未检测到.NET CORE SDK 或者 新建项目没有.NET CORE 3.0选择项

    终于解决了 首先先看自己的VS2019版本 由于楼主下载的 .NET CORE SDK 3.0.100-preview8-013656 焕然大悟 原来是版本不符合,需要用vs 2019 preview ...

  7. ADO.NET 五(DataAdapter 与 DataSet)

    在执行对表中数据的查询时还能将数据保存到 DataSet 中,但需要借助 DataAdapter 类来实现. 在实际应用中,DataAdapter 与 DataSet 是在查询操作中使用最多的类. 此 ...

  8. NIO开发Http服务器(2):项目结构

    最近学习了Java NIO技术,觉得不能再去写一些Hello World的学习demo了,而且也不想再像学习IO时那样编写一个控制台(或者带界面)聊天室.我们是做WEB开发的,整天围着tomcat.n ...

  9. 第三章:JavaScript选择元素

    我们使用jQuery时,很常用的套路是“两步”第一步:选取元素第二步:对选中的元素执行需要的操作这一章我们重点研究第一步,如何使用jQuery选取元素以及对选取的结果进行“各种筛选”以满足我们的需求. ...

  10. 各种GAN的学习和总结

    GAN: https://www.cnblogs.com/kk17/p/10046884.html WGAN: https://www.cnblogs.com/Allen-rg/p/10305125. ...