分佈式鎖實現:
1.根據zkserver地址實例化zk客戶端.
2.創建目錄"/platservice",和"/platservice/_locknode_"節點類型爲PERSISTENT
3.創建臨時節點"/platservice/_locknode_"+"/lock-" + zk.getSessionId() + "-".節點類型爲EPHEMERAL_SEQUENTIAL
4.根據剛剛的臨時節點路徑作爲參數,調用getChildren("/platservice/_locknode_"+"/lock-" + zk.getSessionId() + "-")方法來獲取所有已經創建的子節點,注意,這裏不註冊任何Watcher。
5.客戶端獲取到所有子節點path之後,如果發現自己在步驟3中創建的節點序號最小,那麼就認爲這個客戶端獲得了鎖。設置
holdLock爲true,否則設置爲false.
6.如果在步驟5中發現自己並非所有子節點中最小的,說明自己還沒有獲取到鎖。此時客戶端需要給所有子節點排序,找到比自己小的那個節點,然後對其調用exist()方法,同時註冊事件監聽。
7.之後當這個被關注的節點被移除了,客戶端會收到相應的通知。這個時候客戶端需要再次調用getChildren("/platservice/_locknode_"+"/lock-" + zk.getSessionId() + "-")方法來獲取所有已經創建的子節點,確保自己確實是最小的節點了,然後進入步驟3
改進後的分佈式鎖代碼實現:
1.分佈式鎖DistributedLock
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooDefs.Ids;
import org.apache.zookeeper.data.Stat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DistributedLock extends ConnectionWatcher {
private static Logger logger = LoggerFactory.getLogger(DistributedLock.class);
public static boolean holdLock = false;
private static DistributedLock instance;
public static DistributedLock getInstance(String zkAddrs) {
if (instance == null) {
synchronized(DistributedLock.class) {
if (instance == null) {
instance = new DistributedLock(zkAddrs);
}
}
}
return instance;
}
private DistributedLock(String zkAddrs) {
init(zkAddrs);
}
public void init(String zkAddr) {
logger.info("zk DistributedLock init ----------------------");
try {
initDistLock(zkAddr);
} catch (Exception e) {
logger.error("zk init error:",e);
throw new RuntimeException(e);
}
}
private void initDistLock(String zkAddr) throws IOException,
InterruptedException, KeeperException {
connect(zkAddr);
// zookeeper的根節點;運行本程序前,需要提前生成
// String host = InetAddressUtil.getHostName();
// 預先創建目錄
String[] groupPaths = { "/platservice", "/platservice/_locknode_"};
createPath(groupPaths);
String path = "/platservice/_locknode_" ;//+ host;
String myName = join(path);
if (!checkState(path, myName)) {
listenNode(path, myName);
}
}
private void createPath(String[] groupPaths) throws KeeperException,
InterruptedException {
for (int i = 0; i < groupPaths.length; i++) {
try {
String path = groupPaths[i];
String createdPath = zk.create(path, null/* data */,
Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
logger.info("zk created:" + createdPath);
} catch (Exception e) {
// e.printStackTrace();
}
}
}
private String join(String groupPath) throws KeeperException,
InterruptedException {
String path = groupPath + "/lock-" + zk.getSessionId() + "-";
// 建立一個順序臨時節點
String createdPath = zk.create(path, null/* data */,
Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
logger.info("zk created:" + createdPath);
return createdPath;
}
/**
* 檢查本客戶端是否得到了分佈式鎖
*
* @param groupPath
* @param myName
* @return
* @throws KeeperException
* @throws InterruptedException
*/
private boolean checkState(String groupPath, String myName)
throws KeeperException, InterruptedException {
List<String> childList = zk.getChildren(groupPath, false);
String[] myStr = myName.split("-");
long myId = Long.parseLong(myStr[myStr.length-1]);
boolean minId = true;
for (String childName : childList) {
String[] str = childName.split("-");
long id = Long.parseLong(str[2]);
if (id < myId) {
minId = false;
break;
}
}
if (minId) {
holdLock = true;
logger.info("zk "+new Date() + " has get lock! myId:" + myId);
return true;
} else {
holdLock = false;
logger.info("zk "+new Date() + " go on, myId:" + myId);
return false;
}
}
/**
* 若本客戶端沒有得到分佈式鎖,則進行監聽本節點前面的節點(避免羊羣效應)
*
* @param groupPath
* @param myName
* @throws KeeperException
* @throws InterruptedException
*/
private void listenNode(final String groupPath, final String myName)
throws KeeperException, InterruptedException {
List<String> childList = zk.getChildren(groupPath, false);
String[] myStr = myName.split("-");
long myId = Long.parseLong(myStr[myStr.length-1]);
List<Long> idList = new ArrayList<Long>();
Map<Long, String> sessionMap = new HashMap<Long, String>();
for (String childName : childList) {
String[] str = childName.split("-");
long id = Long.parseLong(str[2]);
idList.add(id);
sessionMap.put(id, str[1] + "-" + str[2]);
}
Collections.sort(idList);
int i = idList.indexOf(myId);
if (i <= 0) {
throw new IllegalArgumentException("zk data error!");
}
// 得到前面的一個節點
long headId = idList.get(i - 1);
String headPath = groupPath + "/lock-" + sessionMap.get(headId);
logger.info("zk added watcher:" + headPath);
Stat stat = zk.exists(headPath, new Watcher() {
@Override
public void process(WatchedEvent event) {
logger.info("zk had fired " + event.getType() + "event !");
try {
while (true) {
if (checkState(groupPath, myName)) {
Thread.sleep(10000);
//break;
}
Thread.sleep(10000);
}
} catch (KeeperException e) {
logger.error("zk listenNode error1:",e);
} catch (InterruptedException e) {
logger.error("zk listenNode error2:",e);
}
}
});
logger.info("zk " +stat+"");
}
public static void main(String[] args) throws Exception {
DistributedLock joinGroup = new DistributedLock("192.168.1.94:2181");
//joinGroup.connect("192.168.1.94:2181");
// 預先創建目錄
String[] groupPaths = { "/platservice", "/platservice/_locknode_" };
joinGroup.createPath(groupPaths);
String path = "/platservice/_locknode_/" ;
String myName = joinGroup.join(path);
if (!joinGroup.checkState(path, myName)) {
joinGroup.listenNode(path, myName);
}
Thread.sleep(Integer.MAX_VALUE);
joinGroup.close();
}
}
2.實現Watcher接口.ConnectionWatcher implements Watcher
import java.io.IOException;
import java.util.concurrent.CountDownLatch;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.Watcher.Event.KeeperState;
public class ConnectionWatcher implements Watcher {
private static final int SESSION_TIMEOUT = 5000*2;
protected ZooKeeper zk;
private CountDownLatch connectedSignal = new CountDownLatch(1);
public void connect(String hosts) throws IOException, InterruptedException {
zk = new ZooKeeper(hosts, SESSION_TIMEOUT, this);
connectedSignal.await();
}
@Override
public void process(WatchedEvent event) {
if (event.getState() == KeeperState.SyncConnected) {
connectedSignal.countDown();
}
}
public void close() throws InterruptedException {
zk.close();
}
}
使用方式:如果沒拿到鎖就返回.
DistributedLock locker = DistributedLock.getInstance(ResourceUtils.get("zookeeper.servers")) ;
if(!locker.holdLock){
return;
}
參考來源:
1.zookeeper分佈式鎖避免羊羣效應(Herd Effect)(這個是阿里 Java中間件部門的文章)