分佈式鎖簡單瞭解

分佈式鎖簡單瞭解

1. 是什麼

鎖,在多線程環境中控制對資源的併發訪問,比如synchronized、Lock等

分佈式鎖,synchronized和Lock不只能本地加鎖,或者說單機部署環境下的鎖,在(單個服務)集羣部署或者多服務完成一個接口時,需要用到分佈式鎖。

2. 應用場景

  • 互聯網秒殺

  • 搶優惠券

3. 常見實現技術

MySQL

使用這麼一個表:

作用
id
resource_name 鎖定的資源名
node_info 機器信息
count 重入次數
desc
create_time
update_time

上鎖:resource_name,排它鎖for update

解鎖:resource_name查詢到記錄,然後機器信息驗證通過,count–,等於0則刪除

Zookeeper

Curator已經封裝好了分佈式鎖的功能。

關鍵點:臨時(有序)節點、watch(無需處理超時,機器宕機則自動刪除節點)

Redis

setNx

setNx+Ex

Redisson已經封裝好了分佈式鎖的功能。

關鍵點:原子命令

對比

- MySQL ZK Redis

4. 關鍵

  • 互斥性:只有一個線程獲取鎖
  • 有效性:避免機器宕機(獲取鎖的時間,大於過期時間,需更新過期時間,防止業務未完成被釋放,常見於Redis)
  • 重入性
  • 釋放鎖:上鎖線程纔有權釋放鎖
  • 可選:阻塞、公平

5. 不安全

  • GC的STW
  • 時鐘跳躍
  • 長時間網絡IO

6. Redis demo

場景:秒殺。提供一個接口,成功則庫存-1,庫存數保存在redis

1. 無鎖

用戶獲取當前庫存數,夠則扣除1,否則結束

問題:多個用戶訪問?見2. 本地鎖

@GetMapping("stock1")
@ResponseBody
public String stock() {
    int curStock = Integer.parseInt(redisTemplate.opsForValue().get(KEY));
    if (curStock > 0) {
        int newStock = curStock - 1;
        redisTemplate.opsForValue().set(KEY, String.valueOf(newStock));
        System.out.println("扣除庫存成功,剩餘庫存:" + newStock);
    } else {
        System.out.println("庫存不足");
    }

    return "end";
}

2. 本地鎖

多個用戶訪問,一個一個秒殺成功,直至沒有庫存數

問題:集羣部署,用戶A訪問實例1,用戶B訪問實例1,如果剩餘1個庫存,可能AB都能秒殺成功

@GetMapping("stock2")
@ResponseBody
public String stock() {
    synchronized (this) {
        int curStock = Integer.parseInt(redisTemplate.opsForValue().get(KEY));
        if (curStock > 0) {
            int newStock = curStock - 1;
            redisTemplate.opsForValue().set(KEY, String.valueOf(newStock));
            System.out.println("扣除庫存成功,剩餘庫存:" + newStock);
        } else {
            System.out.println("庫存不足");
        }
    }

    return "end";
}

3. Redis原生鎖

前提:原子API,加鎖並設置過期時間

第一點:用戶獲取鎖,宕機。超時時間

第二點:業務時間 > 超時時間。定時器,定期更新

第三點:上鎖和解鎖爲同一個線程

// 原子,同時完成加鎖和過期時間設置
Boolean setIfAbsent(K key, V value, long timeout, TimeUnit unit);
// 定時器,開個線程完成即可
// 上解鎖,添加一個唯一標示,一般UUID即可

實現略。

4. Redisson分佈式鎖

@GetMapping("stock4")
@ResponseBody
public String stock4() {
    RLock rLock = null;
    try {
        rLock = redisson.getLock(LOCK);
        rLock.lock();
        // 如果使用lock(int, int),不會自動更新超時時間
        //            try {
        //                Thread.sleep(35*1000);
        //            } catch (Exception e) {
        //                e.printStackTrace();
        //            }

        int curStock = Integer.parseInt(redisTemplate.opsForValue().get(KEY));
        if (curStock > 0) {
            int newStock = curStock - 1;
            redisTemplate.opsForValue().set(KEY, String.valueOf(newStock));
            System.out.println("扣除庫存成功,剩餘庫存:" + newStock);
        } else {
            System.out.println("庫存不足");
        }
    } finally {
        if (rLock != null) {
            rLock.unlock();
        }
    }

    return "end";
}

參考

https://juejin.im/post/5bbb0d8df265da0abd3533a5

https://www.bilibili.com/video/BV1d4411y79Y?from=search&seid=4094813974568007126

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