分析ZooKeeper分布式鎖的實現(xiàn)
一、分布式鎖方案比較
方案 | 實現(xiàn)思路 | 優(yōu)點 | 缺點 |
---|---|---|---|
利用 MySQL 的實現(xiàn)方案 | 利用數(shù)據(jù)庫自身提供的鎖機制實現(xiàn),要求數(shù)據(jù)庫支持行級鎖 | 實現(xiàn)簡單 | 性能差,無法適應高并發(fā)場景;容易出現(xiàn)死鎖的情況;無法優(yōu)雅的實現(xiàn)阻塞式鎖 |
利用 Redis 的實現(xiàn)方案 | 使用 Setnx 和 lua 腳本機制實現(xiàn),保證對緩存操作序列的原子性 | 性能好 | 實現(xiàn)相對復雜,有可能出現(xiàn)死鎖;無法優(yōu)雅的實現(xiàn)阻塞式鎖 |
利用 ZooKeeper 的實現(xiàn)方案 | 基于 ZooKeeper 節(jié)點特性及 watch 機制實現(xiàn) | 性能好,穩(wěn)定可靠性高,能較好地實現(xiàn)阻塞式鎖 | 實現(xiàn)相對復雜 |
二、ZooKeeper實現(xiàn)分布式鎖
這里使用 ZooKeeper 來實現(xiàn)分布式鎖,以50個并發(fā)請求來獲取訂單編號為例,描述兩種方案,第一種為基礎實現(xiàn),第二種在第一種基礎上進行了優(yōu)化。
2.1、方案一
流程描述:
具體代碼:
OrderNumGenerator:
/** * @Description 生成隨機訂單號 */ public class OrderNumGenerator { private static long count = 0; /** * 使用日期加數(shù)值拼接成訂單號 */ public String getOrderNumber() throws Exception { String date = DateTimeFormatter.ofPattern("yyyyMMddHHmmss").format(LocalDateTime.now()); String number = new DecimalFormat("000000").format(count++); return date + number; } }
Lock:
/** * @Description 自定義鎖接口 */ public interface Lock { /** * 獲取鎖 */ public void getLock(); /** * 釋放鎖 */ public void unLock(); }
AbstractLock:
/** * @Description 定義一個模板,具體的方法由子類來實現(xiàn) */ public abstract class AbstractLock implements Lock { /** * 獲取鎖 */ @Override public void getLock() { if (tryLock()) { System.out.println("--------獲取到了自定義Lock鎖的資源--------"); } else { // 沒拿到鎖則阻塞,等待拿鎖 waitLock(); getLock(); } } /** * 嘗試獲取鎖,如果拿到了鎖返回true,沒有拿到則返回false */ public abstract boolean tryLock(); /** * 阻塞,等待獲取鎖 */ public abstract void waitLock(); }
ZooKeeperAbstractLock:
/** * @Description 定義需要的服務連接 */ public abstract class ZooKeeperAbstractLock extends AbstractLock { private static final String SERVER_ADDR = "192.168.182.130:2181,192.168.182.131:2181,192.168.182.132:2181"; protected ZkClient zkClient = new ZkClient(SERVER_ADDR); protected static final String PATH = "/lock"; }
ZooKeeperDistrbuteLock:
/** * @Description 真正實現(xiàn)鎖的細節(jié) */ public class ZooKeeperDistrbuteLock extends ZooKeeperAbstractLock { private CountDownLatch countDownLatch = null; /** * 嘗試拿鎖 */ @Override public boolean tryLock() { try { // 創(chuàng)建臨時節(jié)點 zkClient.createEphemeral(PATH); return true; } catch (Exception e) { // 創(chuàng)建失敗報異常 return false; } } /** * 阻塞,等待獲取鎖 */ @Override public void waitLock() { // 創(chuàng)建監(jiān)聽 IZkDataListener iZkDataListener = new IZkDataListener() { @Override public void handleDataChange(String s, Object o) throws Exception { } @Override public void handleDataDeleted(String s) throws Exception { // 釋放鎖,刪除節(jié)點時喚醒等待的線程 if (countDownLatch != null) { countDownLatch.countDown(); } } }; // 注冊監(jiān)聽 zkClient.subscribeDataChanges(PATH, iZkDataListener); // 節(jié)點存在時,等待節(jié)點刪除喚醒 if (zkClient.exists(PATH)) { countDownLatch = new CountDownLatch(1); try { countDownLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } } // 刪除監(jiān)聽 zkClient.unsubscribeDataChanges(PATH, iZkDataListener); } /** * 釋放鎖 */ @Override public void unLock() { if (zkClient != null) { System.out.println("釋放鎖資源"); zkClient.delete(PATH); zkClient.close(); } } }
測試效果:使用50個線程來并發(fā)測試ZooKeeper實現(xiàn)的分布式鎖
/** * @Description 使用50個線程來并發(fā)測試ZooKeeper實現(xiàn)的分布式鎖 */ public class OrderService { private static class OrderNumGeneratorService implements Runnable { private OrderNumGenerator orderNumGenerator = new OrderNumGenerator();; private Lock lock = new ZooKeeperDistrbuteLock(); @Override public void run() { lock.getLock(); try { System.out.println(Thread.currentThread().getName() + ", 生成訂單編號:" + orderNumGenerator.getOrderNumber()); } catch (Exception e) { e.printStackTrace(); } finally { lock.unLock(); } } } public static void main(String[] args) { System.out.println("----------生成唯一訂單號----------"); for (int i = 0; i < 50; i++) { new Thread(new OrderNumGeneratorService()).start(); } } }
2.2、方案二
方案二在方案一的基礎上進行優(yōu)化,避免產(chǎn)生“羊群效應”,方案一一旦臨時節(jié)點刪除,釋放鎖,那么其他在監(jiān)聽這個節(jié)點變化的線程,就會去競爭鎖,同時訪問 ZooKeeper,那么怎么更好的避免各線程的競爭現(xiàn)象呢,就是使用臨時順序節(jié)點,臨時順序節(jié)點排序,每個臨時順序節(jié)點只監(jiān)聽它本身的前一個節(jié)點變化。
流程描述:
具體代碼
具體只需要將方案一中的 ZooKeeperDistrbuteLock 改變,增加一個 ZooKeeperDistrbuteLock2,測試代碼中使用 ZooKeeperDistrbuteLock2 即可測試,其他代碼都不需要改變。
/** * @Description 真正實現(xiàn)鎖的細節(jié) */ public class ZooKeeperDistrbuteLock2 extends ZooKeeperAbstractLock { private CountDownLatch countDownLatch = null; /** * 當前請求節(jié)點的前一個節(jié)點 */ private String beforePath; /** * 當前請求的節(jié)點 */ private String currentPath; public ZooKeeperDistrbuteLock2() { if (!zkClient.exists(PATH)) { // 創(chuàng)建持久節(jié)點,保存臨時順序節(jié)點 zkClient.createPersistent(PATH); } } @Override public boolean tryLock() { // 如果currentPath為空則為第一次嘗試拿鎖,第一次拿鎖賦值currentPath if (currentPath == null || currentPath.length() == 0) { // 在指定的持久節(jié)點下創(chuàng)建臨時順序節(jié)點 currentPath = zkClient.createEphemeralSequential(PATH + "/", "lock"); } // 獲取所有臨時節(jié)點并排序,例如:000044 List<String> childrenList = zkClient.getChildren(PATH); Collections.sort(childrenList); if (currentPath.equals(PATH + "/" + childrenList.get(0))) { // 如果當前節(jié)點在所有節(jié)點中排名第一則獲取鎖成功 return true; } else { int wz = Collections.binarySearch(childrenList, currentPath.substring(6)); beforePath = PATH + "/" + childrenList.get(wz - 1); } return false; } @Override public void waitLock() { // 創(chuàng)建監(jiān)聽 IZkDataListener iZkDataListener = new IZkDataListener() { @Override public void handleDataChange(String s, Object o) throws Exception { } @Override public void handleDataDeleted(String s) throws Exception { // 釋放鎖,刪除節(jié)點時喚醒等待的線程 if (countDownLatch != null) { countDownLatch.countDown(); } } }; // 注冊監(jiān)聽,這里是給排在當前節(jié)點前面的節(jié)點增加(刪除數(shù)據(jù)的)監(jiān)聽,本質是啟動另外一個線程去監(jiān)聽前置節(jié)點 zkClient.subscribeDataChanges(beforePath, iZkDataListener); // 前置節(jié)點存在時,等待前置節(jié)點刪除喚醒 if (zkClient.exists(beforePath)) { countDownLatch = new CountDownLatch(1); try { countDownLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } } // 刪除對前置節(jié)點的監(jiān)聽 zkClient.unsubscribeDataChanges(beforePath, iZkDataListener); } /** * 釋放鎖 */ @Override public void unLock() { if (zkClient != null) { System.out.println("釋放鎖資源"); zkClient.delete(currentPath); zkClient.close(); } } }
以上就是分析ZooKeeper分布式鎖的實現(xiàn)的詳細內(nèi)容,更多關于ZooKeeper分布式鎖的資料請關注腳本之家其它相關文章!
相關文章
java后臺利用Apache poi 生成excel文檔提供前臺下載示例
本篇文章主要介紹了java后臺利用Apache poi 生成excel文檔提供前臺下載示例,非常具有實用價值,需要的朋友可以參考下2017-05-05SpringBoot結果封裝和異常攔截的實現(xiàn)示例
SpringBoot 項目中,我們通常需要將結果數(shù)據(jù)封裝成特定的格式,以方便客戶端進行處理,本文主要介紹了SpringBoot?優(yōu)雅的結果封裝和異常攔截,感興趣的可以了解一下2023-08-08Spring @Valid @Validated實現(xiàn)驗證
這篇文章主要介紹了Spring @Valid @Validated實現(xiàn)驗證,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-01-01