目录
zookeeper场景
两个小例子:实现获取分布式配置,实现分布式锁
1. 分布式配置
当在zk中维护一个配置时,每个服务器是怎么知道zk中配置发生了变化呢。
原理: 从zk中获取path的配置,并且监控这个配置的path,如果该path发生变化,则触发监控事件,重新从zk中获取数据。所以配置总是动态的,只有path发生修改,就会触发事件通知配置信息发生修改。
1. 分布式配置 类(用来接收配置信息的)
package com.hww.confcenter;
/**
* 用来存储配置信息的实体类
*/
public class MyConfig {
//配置信息,例如ip,端口等一些配置信息的string 串
public String config;
public String getConfig() {
return config;
}
public void setConfig(String config) {
this.config = config;
}
}
2. 使用配置的类。也就是真正的服务类,在这个类中我们获取配置,使用配置。
由于没有实际的应用场景,我们这里打印一下配置信息就可以了
(1)获取zk连接
(2)将所有获取数据的工作交给CallbackWatch类,在这个类中我们只需要await,等待获取数据。而真正的获取数据,并修改配置类的事情就交给callbackWatch的类,一旦数据赋值到配置对象中,则使用配置即可。
(3)从MyConfig对象中获取配置信息,并打印。
(4)一旦配置信息发生变化,触发Watch事件,重新get数据,并赋值给MyConfig对象。然后从MyConfig对象中获取配置使用。
package com.hww.confcenter;
import org.apache.zookeeper.ZooKeeper;
import java.util.concurrent.CountDownLatch;
import static java.lang.Thread.sleep;
/**
* 模拟场景:分布式配置
*
* 真正我们使用的类,工作的类。在该类中获取配置信息,做业务。
*
*
* 1.获取zk
*
* 2. 等一会
* 2.1判断目录是否存在,回调获取数据,并将数据set到MyConfig对象中
* 2.2如果目录发生变化,触发事件,再次获取数据赋值给MyConfig中。
*
*
* 3.从发MyConfig对象中取出值
*
*
*
*
*/
public class MyService {
public static void main(String[] args) {
ZkUtils zkUtils = new ZkUtils();
MyConfig myconfig = new MyConfig();
ZooKeeper zk = zkUtils.getZk();
CallbackWatch callbackWatch = new CallbackWatch();
callbackWatch.setZk(zk);
callbackWatch.setMyConfig(myconfig);
callbackWatch.await();
while (true){
try {
sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
String config = myconfig.getConfig();
System.out.println(config);
}
}
}
3. 真正从zk中获取配置的类
在这个类中,我们需要从zk中获取数据并赋值到配置类,
同时监控path,当数据发生变化时,触发事件
(一下用异步回调的方式实现)
package com.hww.confcenter;
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import java.util.concurrent.CountDownLatch;
public class CallbackWatch implements Watcher, AsyncCallback.DataCallback, AsyncCallback.StatCallback {
private ZooKeeper zk;
private MyConfig myConfig;
private CountDownLatch countDownLatch = new CountDownLatch(1);
public ZooKeeper getZk() {
return zk;
}
public void setZk(ZooKeeper zk) {
this.zk = zk;
}
public MyConfig getMyConfig() {
return myConfig;
}
public void setMyConfig(MyConfig myConfig) {
this.myConfig = myConfig;
}
public CountDownLatch getCountDownLatch() {
return countDownLatch;
}
public void setCountDownLatch(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
/**
*
* @param event
* 触发事件
*
*/
@Override
public void process(WatchedEvent event) {
switch (event.getType()) {
case None:
break;
case NodeCreated:
break;
case NodeDeleted:
break;
case NodeDataChanged:
zk.getData("/testConfig",this,this,"123");
// getData();
break;
case NodeChildrenChanged:
break;
case DataWatchRemoved:
break;
case ChildWatchRemoved:
break;
case PersistentWatchRemoved:
break;
}
}
/**
*
* @param rc
* @param path
* @param ctx
* @param data
* @param stat
*
* 获取数据回调
*/
@Override
public void processResult(int rc, String path, Object ctx, byte[] data, Stat stat) {
String s = new String(data);
System.out.println("回调返回"+s);
myConfig.setConfig(s);
countDownLatch.countDown();
}
/**
*
* @param rc
* @param path
* @param ctx
* @param stat
*
* exits异步回调
*
*/
@Override
public void processResult(int rc, String path, Object ctx, Stat stat) {
if(stat!=null){
zk.getData("/testConfig",this,this,"123");
}
}
public void await(){
zk.exists("/testConfig",this,this,"123");
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
4. 用到的工具类
获取zk连接的工具类
package com.hww.confcenter;
import org.apache.zookeeper.AsyncCallback;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;
import javax.swing.*;
import java.io.IOException;
public class ZkUtils {
ZooKeeper zk;
String connect = "127.0.0.1:2181";
public ZooKeeper getZk(){
try {
zk = new ZooKeeper(connect,3000,new DefaultWatcher());
} catch (IOException e) {
e.printStackTrace();
}
return zk;
}
}
defaultWatch 用来监控Session级别的事件。用于创建zk连接时的watcher。当创建zk连接时,会有一个defaultWatcher存储到WatcherManage中,作为全局的默认watcher。
package com.hww.confcenter;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
public class DefaultWatcher implements Watcher {
@Override
public void process(WatchedEvent event) {
switch (event.getState()) {
case Unknown:
break;
case Disconnected:
break;
case NoSyncConnected:
break;
case SyncConnected:
break;
case AuthFailed:
break;
case ConnectedReadOnly:
break;
case SaslAuthenticated:
break;
case Expired:
break;
case Closed:
break;
}
switch (event.getType()) {
case None:
break;
case NodeCreated:
break;
case NodeDeleted:
break;
case NodeDataChanged:
break;
case NodeChildrenChanged:
break;
case DataWatchRemoved:
break;
case ChildWatchRemoved:
break;
case PersistentWatchRemoved:
break;
}
}
}
2. 分布式锁
分布式锁,主要是为了解决并发问题。
例如,比如我们在修改数据的时候,如果当前值i= 0,我们期望每次拿到数据的时候进行加1操作,A线程拿到i加1变成1,B线程拿到i(此时i=1), 然后加1,变为2。
但是如果并发的情况,A线程和B线程都拿到i=0,此时A和B都加1,i=1。
所以我们更期望的是线程一个一个的通过。比如100个人过独木桥,希望是一个人过去,下一个人在过。
主要原理:利用的zk中临时序列节点,每一个线程(或者客户端)在zk中创建一个临时序列节点,然后阻塞,只能让这些线程对应的节点序列最小的那个节点通过(也就是说只能让最小的节点的线程获取到锁),其他线程都等着,
但是这些阻塞的线程对应临时序列节点需要做件事,就是监控序列中上一个序列值对应的节点(由于临时节点是有顺序的,所以监控上一个节点 4监控3,3监控2,2监控1.....)。
直到上个线程工作完成后,释放锁(实际操作就是删除上个线程对应的那个临时节点),当删除这个节点的时候,监控这节点的线程(或者客户端)就能收到监控事件,在一次去找当前最小序列的那个节点,让其通过。以此类推,实现分布式锁。
详细说明
1.多个线程(或者客户端)同时创建带序列的临时节点,然后都await(等待)。
eg:3个客户端(a,b,c)同时获取锁,都创建有序列的临时节点 a->/lock1 b->/lock2 c->/lock3,并且等待2.异步创建成功回调时,获取children节点(获取当前目录的所有临时节点)/
3.异步获取children节点成功,进行回调时,识别出最小的序列节点,进行countDown,让最小的序列节点对应的线程继续往下执行
其他线程需要进行watcher,序列大的节点对应的线程(客户端) 去监控(watcher)前一个序列节点。
eg:此时lock1最小,所以lock1进行countDown,lock1对应的线程a跳过await继续执行。而b线程监控(watcher) a线程创建的临时节点,c线程
监控b线程创建的临时节点4.当第一个线程执行完后,进行unlock解锁,也就是删除临时节点,那么监控这个被删除的临时节点的线程就会收到事件通知,在通知里再一次
去回调,识别当前最小的序列节点对应的线程进行countDown,跳出等待,往下执行,以此类推。eg:当a线程执行完成,此时删除a线程的临时节点/lock1,删除/lock1会触发事件,b线程就知道/lock1被删除了,重新进行识别最小序列,以此类推。
总结:
1. 要注意这里只是模拟高并发,这里的线程相当于客户端的请求,可能同一时间有多个客户同时访问造成并发。
2. 这里要注意watcher是客户端去监控zk中的节点,一定要分清楚watch是谁与谁的关系,是存在客户端和zk节点之间的关系。
3. 临时节点和session会话相关,如果客户端关闭,临时节点也会消失。
1. 业务逻辑类
package com.hww.lock;
import org.apache.zookeeper.ZKUtil;
import org.apache.zookeeper.ZooKeeper;
import static java.lang.Thread.sleep;
/**
*
* 实现分布式锁
*
* 模拟多个客户端的并发情况
*
*
*
*
*/
public class LockService {
public static void main(String[] args) {
ZkUtils zkUtils = new ZkUtils();
final ZooKeeper zk = zkUtils.getZk();
for (int i = 0; i < 10; i++) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
//真正工作的代码
Thread thread1 = Thread.currentThread();
String thread1Name = thread1.getName();
LockUtils lockUtils = new LockUtils();
lockUtils.setZk(zk);
lockUtils.setThreaName(thread1Name);
//上锁
lockUtils.tryLock();
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//获取到锁,开始工作
System.out.println(thread1Name+":工作。。。。。。。");
//解锁
lockUtils.unLock();
}
});
thread.start();
}
while(true){
}
}
}
2. 实现锁的工具类
package com.hww.lock;
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.ACL;
import org.apache.zookeeper.data.Id;
import org.apache.zookeeper.data.Stat;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
public class LockUtils implements AsyncCallback.StringCallback , AsyncCallback.ChildrenCallback , Watcher, AsyncCallback.StatCallback {
private ZooKeeper zk;
private CountDownLatch countDownLatch=new CountDownLatch(1);
private String pathName;
private String threaName;
public ZooKeeper getZk() {
return zk;
}
public void setZk(ZooKeeper zk) {
this.zk = zk;
}
public CountDownLatch getCountDownLatch() {
return countDownLatch;
}
public void setCountDownLatch(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
public String getThreaName() {
return threaName;
}
public void setThreaName(String threaName) {
this.threaName = threaName;
}
/**
* 上锁
*
* 1.多个线程(或者客户端)同时创建带序列的临时节点,然后都await(等待)。
* eg:3个客户端(a,b,c)同时获取锁,都创建有序列的临时节点 a->/lock1 b->/lock2 c->/lock3,并且等待
*
* 2.异步创建成功回调时,获取children节点(获取当前目录的所有临时节点)/
*
* 3.异步获取children节点成功,进行回调时,识别出最小的序列节点,进行countDown,让最小的序列节点对应的线程继续往下执行
* 其他线程需要进行watcher,序列大的节点对应的线程(客户端) 去监控(watcher)前一个序列节点。
* eg:此时lock1最小,所以lock1进行countDown,lock1对应的线程a跳过await继续执行。而b线程监控(watcher) a线程创建的临时节点,c线程
* 监控b线程创建的临时节点
*
* 4.当第一个线程执行完后,进行unlock解锁,也就是删除临时节点,那么监控这个被删除的临时节点的线程就会收到事件通知,在通知里再一次
* 去回调,识别当前最小的序列节点对应的线程进行countDown,跳出等待,往下执行,以此类推。
*
* eg:当a线程执行完成,此时删除a线程的临时节点/lock1,删除/lock1会触发事件,b线程就知道/lock1被删除了,重新进行识别最小序列,以此类推。
*
* 总结:
* 1. 要注意这里只是模拟高并发,这里的线程相当于客户端的请求,可能同一时间有多个客户同时访问造成并发。
* 2. 这里要注意watcher是客户端去监控zk中的节点,一定要分清楚watch是谁与谁的关系,是存在客户端和zk节点之间的关系。
* 3. 临时节点和session会话相关,如果客户端关闭,临时节点也会消失。
*
*/
public void tryLock() {
//创建临时序列节点
//多线程过来,所有线程都创建
zk.create("/lock","aaaa".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL,this,"124");
//所以线程都等着
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* 解锁
*/
public void unLock(){
try {
zk.delete(pathName,-1);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (KeeperException e) {
e.printStackTrace();
}
}
/**
*
* @param rc
* @param path
* @param ctx
* @param name
*
* 创建的Callback
*
*
*/
@Override
public void processResult(int rc, String path, Object ctx, String name) {
if(name!=null){
//获取临时节点目录下的同级的其他的节点,不需要watch
zk.getChildren("/",false,this,"123");
pathName=name;
System.out.println(threaName+"创建"+pathName);
}
}
/**
*
* @param rc
* @param path
* @param ctx
* @param children
*
* children的callback
*
*/
@Override
public void processResult(int rc, String path, Object ctx, List<String> children) {
//获取临时节点,进行排序
Collections.sort(children);
//如果是最小的那个节点就shutdown
//如果这个节点名称在list中的位置为0,就认为是最小的那个
int i = children.indexOf(pathName.substring(1));
if(i==0){
countDownLatch.countDown();
}else{
//当前线程去watch前一个节点
zk.exists("/"+children.get(i-1),this,this,"123");
}
}
/**
*
* @param rc
* @param path
* @param ctx
* @param stat
*
* exists callback
*
*/
@Override
public void processResult(int rc, String path, Object ctx, Stat stat) {
}
/**
*
* @param event
* watcher
*/
@Override
public void process(WatchedEvent event) {
switch (event.getType()) {
case None:
break;
case NodeCreated:
break;
case NodeDeleted:
//当节点删除,则在再次判断children中那个是最下的。
zk.getChildren("/",false,this,"123");
break;
case NodeDataChanged:
break;
case NodeChildrenChanged:
break;
case DataWatchRemoved:
break;
case ChildWatchRemoved:
break;
case PersistentWatchRemoved:
break;
}
}
}
3. 工具类,获取zk等
package com.hww.lock;
import com.hww.confcenter.DefaultWatcher;
import org.apache.zookeeper.ZooKeeper;
import java.io.IOException;
public class ZkUtils {
ZooKeeper zk;
String connect = "127.0.0.1:2181";
public ZooKeeper getZk(){
try {
zk = new ZooKeeper(connect,3000,new DefaultWatcher());
} catch (IOException e) {
e.printStackTrace();
}
return zk;
}
}