前のRedisを学習するとき、我々はまた、分散ロックの概念を述べました。
しかし、また、分散ロック・Redisのの複雑さについて話しました本当に速いのRedisものの、達成したいが、それは、あなたが信頼性、および信頼性が達成したい永続性床を必要とするが、パフォーマンスが低下することはありません。。。
そして、ベースの飼育係は、実際にはより信頼性の高い分散ロックを達成するために、比較的シンプルにすることができます。
レッツ・チャット、我々は何に注意を払う必要があるのZooKeeperベースの分散ロックを実装したいですか?
1は、ロックとして、これは間違いなく一人だけがロックを取得することができることを保証する必要があることです。
図2は、人々は問題があるロックを取得し、我々は一時的なノードの飼育係は非常に適切であるので、それは、デッドロックが発生しないようにする必要があります。
3は、操作が完了した後に、人々はロックを取得し、ロックが解除されますが、他の人々は、それがそれを解放されたときに知っている方法ですか?ポーリングのタイミング取得?いや、いや、適時性があります。時計のモニターコールバック:より効率的なアプローチを飼育係。
4、それを見なければならない誰が見て?あなたは1000スレッドにロックを取得したい場合は、ロックは、スレッドがロックを解除する場合、スレッドは1000に群がっ得られるだろう必然的にサービスZooKeeperの大きな圧力の原因。
その高密度の競争を避けるためにどのように?選択キューロック。ロックが解除されたときに規則的な配列は、各スレッドを許可する場合、別途の前に各スレッドは、スレッドを監視します。最初のスレッドキューによってロックされることはありません飽きません。
この場合、スレッドがロックを解除するたびに、ロックは唯一このスレッドのスレッドにリッスンロックピックを取得し、他のスレッドのための競争を覚ますしないように通知されます。
大雑把モデル:
コードの実装
私は最初のZooKeeper接続ツールを準備します
public class ZkUtil {
//使用/testConfig作为我们的配置工作路径
private static final String CONNECT_ZK_URL = "192.168.221.66:2181,192.168.221.68:2181,192.168.221.70:2181,192.168.221.72:2181/testConfig";
private static final Integer SESSION_TIME_OUT = 3000; //会话超时时间
private static final DefaultWatcher watcher = new DefaultWatcher(); //会话级别watcher
private static ZooKeeper zkInstance;
static {
try {
zkInstance = new ZooKeeper(CONNECT_ZK_URL,SESSION_TIME_OUT,watcher);
} catch (IOException e) {
e.printStackTrace();
}
}
public static ZooKeeper getZkInstance() {
try {
watcher.countDownLatch.await();//阻塞直到zookeeper连接成功后
return zkInstance;
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
//会话级别watcher
private static class DefaultWatcher implements Watcher{
private CountDownLatch countDownLatch = new CountDownLatch(1);
@Override
public void process(WatchedEvent event) {
Event.KeeperState state = event.getState();
switch (state) {
case Unknown:
break;
case Disconnected:
break;
case NoSyncConnected:
break;
case SyncConnected:
System.out.println("连接ZK成功。。");
countDownLatch.countDown();
break;
case AuthFailed:
break;
case ConnectedReadOnly:
break;
case SaslAuthenticated:
break;
case Expired:
break;
}
}
}
}
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
/**
* 使用zk监听器实现分布式锁
*/
public class LockWatcher implements Watcher, AsyncCallback.StringCallback, AsyncCallback.Children2Callback, AsyncCallback.StatCallback {
private ZooKeeper zk;
private String pathName;
private String threadName;
private CountDownLatch countDownLatch = new CountDownLatch(1);
public LockWatcher(ZooKeeper zk) {
this.zk = zk;
}
public String getPathName() {
return pathName;
}
public void setPathName(String pathName) {
this.pathName = pathName;
}
public String getThreadName() {
return threadName;
}
public void setThreadName(String threadName) {
this.threadName = threadName;
}
/**
* watcher自身监听事件
* @param event
*/
@Override
public void process(WatchedEvent event) {
Event.EventType type = event.getType();
switch (type) {
case None:
break;
case NodeCreated:
break;
case NodeDeleted:
//当有节点挂了的时候,会触发监听这个节点的监听器,然后重新获取当前zk该路径下的所有节点,
// 1.如果此时当前节点是第一个节点,则可以得到执行机会进行处理
// 2.否则的话,将当前节点的监听改为之前监听节点的前一个节点
zk.getChildren("/",false,this,"getChildrenNodes");
break;
case NodeDataChanged:
break;
case NodeChildrenChanged:
break;
}
}
public void tryLock() {
// if(zk.getData("/")) //此处可获取当前工作根节点的数据,如果和当前线程是一样的,直接通过。 也就是实现了重入锁的概念
zk.create("/lock","zk_lock".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL,this,"tryToLock");
try {
this.countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void unLock() {
try {
zk.delete("/"+pathName,-1);
this.countDownLatch = new CountDownLatch(1);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (KeeperException e) {
e.printStackTrace();
}
}
/**
* 创建节点回调
* @param rc
* @param path
* @param ctx
* @param name
*/
@Override
public void processResult(int rc, String path, Object ctx, String name) {
System.out.println(threadName);
this.pathName = name.substring(1);
zk.getChildren("/",false,this,"getChildrenNodes");
}
/**
* 获取子节点列表回调
* @param rc
* @param path
* @param ctx
* @param children
* @param stat
*/
@Override
public void processResult(int rc, String path, Object ctx, List<String> children, Stat stat) {
//后面监听前面的 例如有三个zk节点同时被创建(代表三个同时的事务线程), 创建的顺序是 a,b,c 那 每一个后面的会依次监听前面的
// 如: a <- b <- c 只有第一个节点才是可以进行操作的。这样的话,前面的节点删除了(锁释放),后面的可以立马监听到,这样就可以保证事务锁的效果
Collections.sort(children);
int indexOf = children.indexOf(pathName);
if(indexOf==0){
try {
//TODO 此处可记录当前持有锁的线程信息,进而实现重入锁
zk.setData("/",pathName.getBytes(),-1);
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
countDownLatch.countDown();
return;
}
zk.exists("/"+children.get(indexOf-1),this,this,"exist");
}
/**
* exist回调
* @param rc
* @param path
* @param ctx
* @param stat
*/
@Override
public void processResult(int rc, String path, Object ctx, Stat stat) {
//防止监听的前一个节点还未存在,或者已经挂掉, 此时需要重新获取节点
if(stat==null){
zk.getChildren("/",false,this,"getChildrenNodes");
}
}
}
プログラムのエントリー:
@Test
public void test1(){
ZooKeeper zkInstance = ZkUtil.getZkInstance();
//模拟10个线程抢锁的过程
for (int i = 0; i < 10; i++) {
new Thread(()->{
LockWatcher lockWatcher = new LockWatcher(zkInstance);
lockWatcher.setThreadName(Thread.currentThread().getName());
lockWatcher.tryLock();
System.out.println(lockWatcher.getThreadName()+":to do something~");
// System.out.println(lockWatcher.getThreadName()+":to do something2~");
lockWatcher.unLock();
}).start();
}
//同步阻塞住
while (true){
}
}