基於Zookeeper的分佈式鎖實現,已經測試並上線

分佈式鎖實現:

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中間件部門的文章)

2.zookeeper分佈式鎖的實現

 

 

 

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章