配置中心案例
场景: 数据库用户信息密码放在一个配置文件中,应该读取配置文件信息,放入缓存。信息改变时需要重新加载,通过ZooKeeper
自动完成缓存同步。
- 连接zookeeper服务器
- 读取zookeeper中的配置信息,注册watcher监听器,存入本地变量
- 当zookeeper中的配置信息发生变化时,通过watcher的回调方法捕获变化事件
- 重新获取配置信息
public class AutoSyncConfigInfo {
private String username;
private ZooKeeper zooKeeper;
public AutoSyncConfigInfo() throws KeeperException, InterruptedException {
zooKeeper = ZooKeeperUtils.genZkConn();
getInfoFromZk();
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
//watcher内部类
class ConfigWatcher implements Watcher{
@Override
public void process(WatchedEvent event) {
System.out.println("---ConfigWatcher---");
System.out.println("Path = " + event.getPath());
System.out.println("EventType = "+event.getType());
if (event.getType() == Watcher.Event.EventType.None){
if (event.getState() == Event.KeeperState.SyncConnected){
System.out.println("连接成功!");
//手动减1
count.countDown();
} else if (event.getState() == Event.KeeperState.Disconnected){
System.out.println("连接断开!");
} else if (event.getState() == Event.KeeperState.Expired){
System.out.println("连接超时!自动重新连接。。。");
try {
zooKeeper = new ZooKeeper("hadoop102",5000,this);
} catch (IOException e) {
e.printStackTrace();
}
} else if (event.getState() == Event.KeeperState.AuthFailed){
System.out.println("验证失败!");
}
}
if (event.getType() == Event.EventType.NodeDataChanged){
//重新从zookeeper获取数据
getInfoFromZk();
}
}
}
/**
* 从zookeeper读取数据
*/
private void getInfoFromZk(){
try {
this.username = new String(zooKeeper.getData("/config/username", new ConfigWatcher(), null));
} catch (KeeperException | InterruptedException e) {
e.printStackTrace();
}
}
private void closeZkConn(){
//zooKeeper.close();
}
public static void main(String[] args) throws KeeperException, InterruptedException {
AutoSyncConfigInfo autoSyncConfigInfo = new AutoSyncConfigInfo();
for (int i = 0; i < 10; i++) {
Thread.sleep(3000);
System.out.println(autoSyncConfigInfo.getUsername());
System.out.println("-----------------------");
}
}
}
分布式唯一ID
在过去的单库单表型系统中,通常第可以使用数据库字段自带的auto_ increment
属性来自动为每条记录生成个唯一的ID
。但是分库分表后,就无法在依靠数据库的auto_ increment
属性来唯一标识一条记录了。此时我们就可以用zookeeper
在分布式环境下生成全局唯一ID
- 连接zk服务器
- 指定路径生成临时有序节点
- 取序列号及分布式环境下的唯一ID
public class GlobalUUID {
private ZooKeeper zooKeeper;
private String IP = "hadoop102:2181";
private String path = "/uniqueId";
private CountDownLatch count = new CountDownLatch(1);
public GlobalUUID() {
try {
zooKeeper = new ZooKeeper(IP,5000,new GlobalIDWatcher());
//阻塞线程,等待连接成功执行
count.await();
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
/**
* 生成分布式唯一ID
* @return 返回ID
*/
private String getUUID(){
String tmpPath = "";
//创建临时有序节点
try {
tmpPath = zooKeeper.create(path,new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
} catch (KeeperException | InterruptedException e) {
e.printStackTrace();
}
return tmpPath.substring(path.length());
}
public static void main(String[] args) throws InterruptedException {
GlobalUUID globalUUID = new GlobalUUID();
//线程池
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5,5, 60,TimeUnit.SECONDS,new LinkedBlockingDeque<>());
String lock = new String("");
for (int i = 0; i < 10; i++) {
threadPoolExecutor.execute(()->{
//增加同步
synchronized (lock) {
//System.out.println(Thread.currentThread());
System.out.println(globalUUID.getUUID());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
TimeUnit.SECONDS.sleep(50);
threadPoolExecutor.shutdown();
}
//watcher内部类
class GlobalIDWatcher implements Watcher {
@Override
public void process(WatchedEvent event) {
System.out.println("---GlobalWatcher---");
System.out.println("Path = " + event.getPath());
System.out.println("EventType = " + event.getType());
if (event.getType() == Watcher.Event.EventType.None) {
if (event.getState() == Event.KeeperState.SyncConnected) {
System.out.println("连接成功!");
//手动减1
count.countDown();
} else if (event.getState() == Event.KeeperState.Disconnected) {
System.out.println("连接断开!");
} else if (event.getState() == Event.KeeperState.Expired) {
System.out.println("连接超时!自动重新连接。。。");
try {
zooKeeper = new ZooKeeper("hadoop102", 5000, this);
} catch (IOException e) {
e.printStackTrace();
}
} else if (event.getState() == Event.KeeperState.AuthFailed) {
System.out.println("验证失败!");
}
}
}
}
}
结果:
分布式锁
分布式锁有多种实现方式,比如通过数据库、redis都可实现。作为分布式协同工具Zookeeper
,当然也有着标准的实现方式。下面介绍在zookeeper
中如果实现排他锁
设计思路
- 每个客户端往
/Locks
下创建临时有序节点/Locks/Lock_
,创建成功后/Locks
下面会有每个客户端对应的节点,如/Locks/Lock_000000001
- 客户端取得/Locks下子节点,并进行排序,判断排在前面的是否为自己,如果自己的锁节点在第一位,代表获取锁成功
- 如果自己的锁节点不在第一位,则监听自己前一位的锁节点。例如,自己锁节点
Lock_000000002
,那么则监听Lock_000000001
- 当前一位锁节点
(Lock_000000001)
对应的客户端执行完成,释放了锁,将会触发监听客户端(Lock_000000002)
的逻辑 - 监听客户端重新执行第
2
步逻辑,判断自己是否获得了锁 - zookeeper是有工具包的(这里采用手写)
DistributedLock分布式锁对象
package myself.zookeeper.anli;
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
/**
* @program: JavaAPIsLearning
* @description: 分布式锁
* @author: CccRJ
* @create: 2020-09-05 16:39
**/
public class DistributedLock {
private String IP = "hadoop102:2181";
private final String ROOT_LOCK = "/Root_LOCK";
private final String LOCK_PREFIX = "/LOCK_";
private final CountDownLatch count = new CountDownLatch(1);
private final byte[] DATA = new byte[0];
private ZooKeeper zooKeeper;
private String path;
//用来监视上一个节点
private final Watcher watcher = new Watcher() {
@Override
public void process(WatchedEvent event) {
//节点被删除
if (event.getType() == Event.EventType.NodeDeleted) {
//前一个节点被删除,唤醒
synchronized (this) {
notifyAll();
}
}
}
};
public DistributedLock() {
init();
}
/**
* 初始化连接
*/
private void init() {
try {
zooKeeper = new ZooKeeper(IP, 5000, event -> {
if (event.getState() == Watcher.Event.KeeperState.SyncConnected) {
System.out.println("Zookeeper连接成功");
}
count.countDown();
});
count.await();
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
/**
* 创建临时有序节点当作锁
*/
private void createLock() {
try {
//创建目录节点
Stat stat = zooKeeper.exists(ROOT_LOCK, false);
if (stat == null) {
//创建根节点
zooKeeper.create(ROOT_LOCK, DATA, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
//创建临时锁节点
path = zooKeeper.create(ROOT_LOCK + LOCK_PREFIX, DATA, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
System.out.println("成功创建节点: " + path);
} catch (KeeperException | InterruptedException e) {
e.printStackTrace();
}
}
/**
* 初始化链接,创建目录和节点
*/
public void getLock() {
init();
createLock();
attemptLock();
}
/**
* 获取锁
*/
public void attemptLock() {
try {
//获取所有锁节点
List<String> children = zooKeeper.getChildren(ROOT_LOCK, false);
//排序字节点
Collections.sort(children);
System.out.println(path + "--当前锁节点:" + children);
//查看当前节点排名
int i = children.indexOf(path.substring(ROOT_LOCK.length() + 1));
if (i == 0) {
//首位,可以获取
System.out.println(path.substring(ROOT_LOCK.length() + 1) + "获取锁成功");
return;
} else {
//不是第一位,前面还有锁
//获取上一个节点路径,进行监视
String prePath = children.get(i - 1);
Stat stat = zooKeeper.exists(ROOT_LOCK + "/" + prePath, watcher);
//获取状态过程中可能前一个节点已经使用完并被删除了,所以需要判空
if (stat == null) {
attemptLock();
} else {
synchronized (watcher) {
//等待监视器唤醒
watcher.wait();
}
attemptLock();
}
}
} catch (KeeperException | InterruptedException e) {
e.printStackTrace();
}
}
public void releaseLock() throws KeeperException, InterruptedException {
//删除临时有序节点
zooKeeper.delete(this.path, -1);
zooKeeper.close();
System.out.println("锁已经释放:" + this.path);
}
public static void main(String[] args) throws InterruptedException {
DistributedLock distributedLock = new DistributedLock();
distributedLock.createLock();
distributedLock.getLock();
}
}
测试对象
package myself.zookeeper.anli;
import org.apache.zookeeper.KeeperException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @program: JavaAPIsLearning
* @description: 测试分布式锁
* @author: CccRJ
* @create: 2020-09-05 18:56
**/
public class TestDistributedLock{
private void sell(){
System.out.println("---售票开始---");
int sleepMillis = 1000;
try {
Thread.sleep(sleepMillis);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("---结束---");
}
public void sellTicketWithLock() throws KeeperException, InterruptedException {
DistributedLock dLock = new DistributedLock();
dLock.getLock();
sell();
dLock.releaseLock();
}
public void test1() throws KeeperException, InterruptedException {
for (int i = 0; i < 10; i++) {
sellTicketWithLock();
}
}
public void test2(){
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2,2,100,TimeUnit.SECONDS,new ArrayBlockingQueue<>(10));
for (int i = 0; i < 5; i++) {
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
try {
sellTicketWithLock();
} catch (KeeperException | InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
public static void main(String[] args) throws KeeperException, InterruptedException {
TestDistributedLock test = new TestDistributedLock();
test.test2();
}
}