83 zookeeper分佈式鎖三

常見問題:

使用zk實現分佈式鎖,,如果主節點宕機:會發生選舉的過程,爲了數據一致性的問題,在zk選舉的過程中整個zk環境無法使用,客戶端獲取鎖的時候,會不斷的重試,重試多次會直接拋出異常。
客戶端已經在監聽的情況下:
zk客戶端事件監聽到當前的leader節點已經宕機,會自動實現重試。
該過程可能在剩餘的節點中選出一個新的leader節點,從新監聽新的leade節點,,如果一直沒有選出leader節點,爲了避免客戶端死鎖的問題,從新獲取鎖的狀態。

使用zk實現分佈式鎖,死鎖的問題:
curator概念:
Curator是Netflix公司開源的一套zookeeper客戶端框架,解決了很多Zookeeper客戶端非常底層的細節開發工作,包括連接重連、反覆註冊Watcher和NodeExistsException異常等等。Patrixck Hunt(Zookeeper)以一句“Guava is to Java that Curator to Zookeeper”給Curator予高度評價。

Curator官網

http://curator.apache.org/

其中Curator提供分佈式鎖實現方案,解決羊羣效應問題。

核心API:

InterProcessMutex:分佈式可重入排它鎖

InterProcessSemaphoreMutex:分佈式排它鎖

InterProcessReadWriteLock:分佈式讀寫鎖

InterProcessMultiLock:將多個鎖作爲單個實體管理的容器

Curator環境搭建
Maven依賴

<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-recipes</artifactId>
    <version>4.2.0</version>
</dependency>

核心api配置

import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.curator.utils.CloseableUtils;
import org.springframework.stereotype.Component;

/**
 * @ClassName CuratorZkLock   zk框架實現分佈式鎖
 * @Author 螞蟻課堂餘勝軍 QQ644064779 www.mayikt.com
 * @Version V1.0
 **/
@Component
public class CuratorZkLock {


    // ZooKeeper 服務地址, 單機格式爲:(127.0.0.1:2181),
    // 集羣格式爲:(127.0.0.1:2181)
    private String connectString;
    // Curator 客戶端重試策略
    private RetryPolicy retry;
    // Curator 客戶端對象
    private CuratorFramework client;

    public CuratorZkLock() throws Exception {
        init();
    }

    public void init() throws Exception {
        // 設置 ZooKeeper 服務地址爲本機的 2181 端口
        connectString = "127.0.0.1:2181";
        // 重試策略
        // 初始休眠時間爲 1000ms, 最大重試次數爲 3
        retry = new ExponentialBackoffRetry(1000, 3);
        // 創建一個客戶端, 60000(ms)爲 session 超時時間, 15000(ms)爲連接超時時間
        client = CuratorFrameworkFactory.newClient(connectString, 60000, 15000, retry);
        client.start();
    }

    public void close() {
        CloseableUtils.closeQuietly(client);
    }

    public CuratorFramework getClient() {
        return client;
    }
}

簡單用法
/**

  • 秒殺lock

  • @return
    */
    @RequestMapping("/seckilLock")
    public String seckilLock() throws Exception {
    InterProcessMutex interProcessMutex = null;
    try {
    CuratorFramework client = curatorZkLock.getClient();
    interProcessMutex = new InterProcessMutex(client, lockPath);
    interProcessMutex.acquire();
    log.info("<獲取鎖成功....>");
    log.info("<執行秒殺扣庫存業務邏輯....>");
    CommodityDetails commodityDetails = commodityDetailsMapper.getCommodityDetails(1L);
    Long stock = commodityDetails.getStock();
    if (stock > 0) {
    log.info("<扣庫存成功>");
    commodityDetailsMapper.reduceInventory(1l);
    return "ok";
    }
    // 扣庫存失敗
    log.info("<扣庫存失敗>");
    return "fail";

    } catch (Exception e) {
    e.printStackTrace();
    log.info("<秒殺出現錯誤:e:{}>", e);
    return "fail";
    } finally {
    if (interProcessMutex != null)
    interProcessMutex.release();
    }

}

Curator源碼解讀

  1. 從緩存中查找是否已經創建分佈式鎖,如果已經創建了分佈式鎖
    則直接複用(具有可重入性)
  2. 如果緩存中沒有,則創建一個分佈式鎖
  3. 實現原理:創建一個臨時節點,獲取當前父節點下子節點,如果是爲最小的節點,則表示獲取鎖成功,否則獲取鎖失敗,阻塞等待,則監聽上一個節點。
  4. 當上一個節點如果釋放鎖之後,直接進入到獲取鎖的狀態。
    喚醒使用wait notify技術

獲取到當前的線程

Thread currentThread = Thread.currentThread();

從集合中查詢當前線程是否有獲取過分佈式鎖;

LockData lockData = threadData.get(currentThread);
if ( lockData != null )
{
// re-entering

如果有獲取過分佈式鎖的情況下,則重入次數就會+1

lockData.lockCount.incrementAndGet();
return true;

}

在集合中沒有查詢到當前線程有獲取過鎖,如果沒有獲取鎖的情況下,則創建一個臨時順序編號節點

String lockPath = internals.attemptLock(time, unit, getLockNodeBytes());
if ( lockPath != null )
{

在將該臨時順序編號節點存放到集合緩存。

Key爲當前線程 value 分佈式鎖
LockData newLockData = new LockData(currentThread, lockPath);
threadData.put(currentThread, newLockData);
return true;
}

創建一個臨時順序編號節點

ourPath = driver.createsTheLock(client, path, localLockNodeBytes);

判斷該臨時順序編號節點是否屬於最小的;如果屬於最小的節點

就表示獲取鎖成功;
hasTheLock = internalLockLoop(startMillis, millisToWait, ourPath);

獲取當前節點對應 index下標位置;

int ourIndex = children.indexOf(sequenceNodeName);
validateOurIndex(sequenceNodeName, ourIndex);

0 maxLeases=1;

getsTheLock =true
getsTheLock =true 表示獲取鎖成功

boolean getsTheLock = ourIndex < maxLeases;

如果getsTheLock =false的情況下,則訂閱我們上一個節點

String pathToWatch = getsTheLock ? null : children.get(ourIndex - maxLeases);

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