zookeeper的补充

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关闭,释放锁,其他节点就可以重新生成订单号。

posted @ 2019-11-27 16:20  琴昕LNS~  阅读(140)  评论(0编辑  收藏  举报