搜尋
首頁常見問題分散式鎖的三種實作方式是什麼?

分散式鎖定三種實作方式:1、基於資料庫實作分散式鎖定;2、基於快取(Redis等)實作分散式鎖定;3、基於Zookeeper實作分散式鎖定。從效能角度(從高到低)來看:「快取方式>Zookeeper方式>=資料庫方式」。

分散式鎖的三種實作方式是什麼?

本文操作環境:windows系統、redis 6.0、thinkpad t480電腦。

分散式鎖定三種實作方式:

1. 基於資料庫實作分散式鎖定; 
2. 基於快取(Redis等)實作分散式鎖定;
3. 基於Zookeeper實作分散式鎖定;

一,基於資料庫實作分散式鎖定

##1. 悲觀鎖定

利用select … where … for update 排他鎖

注意: 其他附加功能與實作一基本一致,這裡需要注意的是“where name=lock ”,name欄位必須要走索引,否則會鎖表。有些情況下,例如表不大,mysql優化器會不走這個索引,導致鎖定表問題。

2. 樂觀鎖

所謂樂觀鎖與前邊最大區別在於基於CAS思想,是不具有互斥性,不會產生鎖等待而消耗資源,操作過程中認為不存在併發衝突,只有update version失敗後才能覺察。我們的搶購、秒殺就是用了這種實作來防止超賣。

透過增加遞增的版本號欄位實現樂觀鎖定

#二, 基於快取(Redis等)實作分散式鎖定

1. 使用指令介紹:

(1)SETNX
SETNX key val:當且僅當key不存在時,set一個key為val的字串,傳回1;若key存在,則什麼都不做,回傳0。
(2)expire
expire key timeout:為key設定一個超時時間,單位為second,超過這個時間鎖定會自動釋放,避免死鎖。
(3)delete
delete key:刪除key

在使用Redis實作分散式鎖定的時候,主要就會使用到這三個指令。

2. 實作思想:

(1)取得鎖的時候,使用setnx加鎖,並使用expire指令為鎖添加一個超時時間,超過該時間則自動釋放鎖,鎖的value值為一個隨機產生的UUID,透過此在釋放鎖的時候進行判斷。
(2)取得鎖的時候也設定一個取得的逾時時間,若超過這個時間就放棄取得鎖定。
(3)釋放鎖的時候,透過UUID判斷是不是該鎖,若是該鎖,則執行delete進行鎖釋放。

3. 分散式鎖定的簡單實作程式碼:

 /**
    * 分布式锁的简单实现代码    */
   public class DistributedLock {
   
       private final JedisPool jedisPool;
   
       public DistributedLock(JedisPool jedisPool) {
          this.jedisPool = jedisPool;
      }
  
      /**
       * 加锁
       * @param lockName       锁的key
       * @param acquireTimeout 获取超时时间
       * @param timeout        锁的超时时间
       * @return 锁标识
       */
      public String lockWithTimeout(String lockName, long acquireTimeout, long timeout) {
          Jedis conn = null;
          String retIdentifier = null;
          try {
              // 获取连接
              conn = jedisPool.getResource();
              // 随机生成一个value
              String identifier = UUID.randomUUID().toString();
              // 锁名,即key值
              String lockKey = "lock:" + lockName;
              // 超时时间,上锁后超过此时间则自动释放锁
              int lockExpire = (int) (timeout / );
  
              // 获取锁的超时时间,超过这个时间则放弃获取锁
              long end = System.currentTimeMillis() + acquireTimeout;
              while (System.currentTimeMillis()  results = transaction.exec();
                     if (results == null) {
                         continue;
                     }
                     retFlag = true;
                 }
                 conn.unwatch();
                 break;
             }
         } catch (JedisException e) {
             e.printStackTrace();
         } finally {
             if (conn != null) {
                 conn.close();
             }
         }
         return retFlag;
     }
 }
4. 測試剛才實作的分散式鎖定

範例中使用50個執行緒模擬秒殺一個商品,使用–運算子來實現商品減少,從結果有序性就可以看出是否為加鎖狀態。

模擬秒殺服務,在其中配置了jedis執行緒池,在初始化的時候傳給分散式鎖,供其使用。

public class Service {

    private static JedisPool pool = null;

    private DistributedLock lock = new DistributedLock(pool);

    int n = 500;

    static {
        JedisPoolConfig config = new JedisPoolConfig();
        // 设置最大连接数
        config.setMaxTotal(200);
        // 设置最大空闲数
        config.setMaxIdle(8);
        // 设置最大等待时间
        config.setMaxWaitMillis(1000 * 100);
        // 在borrow一个jedis实例时,是否需要验证,若为true,则所有jedis实例均是可用的
        config.setTestOnBorrow(true);
        pool = new JedisPool(config, "127.0.0.1", 6379, 3000);
    }

    public void seckill() {
        // 返回锁的value值,供释放锁时候进行判断
        String identifier = lock.lockWithTimeout("resource", 5000, 1000);
        System.out.println(Thread.currentThread().getName() + "获得了锁");
        System.out.println(--n);
        lock.releaseLock("resource", identifier);
    }
}
模擬執行緒進行秒殺服務;

public class ThreadA extends Thread {
    private Service service;

    public ThreadA(Service service) {
        this.service = service;
    }

    @Override
    public void run() {
        service.seckill();
    }
}

public class Test {
    public static void main(String[] args) {
        Service service = new Service();
        for (int i = 0; i 結果如下,結果為有序的:<p></p><p><img src="/static/imghwm/default1.png" data-src="https://img.php.cn/upload/article/000/000/024/0c1cb2ceea4c8cbfa184228a02822f99-1.png?x-oss-process=image/resize,p_40" class="lazy" alt="分散式鎖的三種實作方式是什麼?"></p>若註解掉使用鎖定的部分:<p></p><pre class="brush:php;toolbar:false">public void seckill() {
    // 返回锁的value值,供释放锁时候进行判断
    //String indentifier = lock.lockWithTimeout("resource", 5000, 1000);
    System.out.println(Thread.currentThread().getName() + "获得了锁");
    System.out.println(--n);
    //lock.releaseLock("resource", indentifier);
}
從結果可以看出,有些是異步進行的:

分散式鎖的三種實作方式是什麼?

#三,基於Zookeeper實作分散式鎖定

ZooKeeper是一個為分散式應用程式提供一致性服務的開源元件,它內部是一個分層的檔案系統目錄樹結構,規定同一個目錄下只能有一個唯一檔案名稱。基於ZooKeeper實作分散式鎖定的步驟如下:

(1)建立一個目錄mylock;

(2)執行緒A想取得鎖定就在mylock目錄下建立臨時順序節點;
(3 )取得mylock目錄下所有的子節點,然後取得比自己小的兄弟節點,如果不存在,則表示當前線程順序號最小,獲得鎖;
(4)線程B取得所有節點,判斷自己不是最小節點,設定監聽比自己次小的節點;
(5)線程A處理完,刪除自己的節點,線程B監聽到變更事件,判斷自己是不是最小的節點,如果是則獲得鎖。

這裡推薦一個Apache的開源函式庫Curator,它是一個ZooKeeper客戶端,Curator提供的InterProcessMutex是分散式鎖的實作,acquire方法用來取得鎖,release方法用來釋放鎖。

實作原始碼如下:

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.RetryNTimes;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.data.Stat;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

/**
 * 分布式锁Zookeeper实现
 *
 */
@Slf4j
@Component
public class ZkLock implements DistributionLock {
private String zkAddress = "zk_adress";
    private static final String root = "package root";
    private CuratorFramework zkClient;

    private final String LOCK_PREFIX = "/lock_";

    @Bean
    public DistributionLock initZkLock() {
        if (StringUtils.isBlank(root)) {
            throw new RuntimeException("zookeeper 'root' can't be null");
        }
        zkClient = CuratorFrameworkFactory
                .builder()
                .connectString(zkAddress)
                .retryPolicy(new RetryNTimes(2000, 20000))
                .namespace(root)
                .build();
        zkClient.start();
        return this;
    }

    public boolean tryLock(String lockName) {
        lockName = LOCK_PREFIX+lockName;
        boolean locked = true;
        try {
            Stat stat = zkClient.checkExists().forPath(lockName);
            if (stat == null) {
                log.info("tryLock:{}", lockName);
                stat = zkClient.checkExists().forPath(lockName);
                if (stat == null) {
                    zkClient
                            .create()
                            .creatingParentsIfNeeded()
                            .withMode(CreateMode.EPHEMERAL)
                            .forPath(lockName, "1".getBytes());
                } else {
                    log.warn("double-check stat.version:{}", stat.getAversion());
                    locked = false;
                }
            } else {
                log.warn("check stat.version:{}", stat.getAversion());
                locked = false;
            }
        } catch (Exception e) {
            locked = false;
        }
        return locked;
    }

    public boolean tryLock(String key, long timeout) {
        return false;
    }

    public void release(String lockName) {
        lockName = LOCK_PREFIX+lockName;
        try {
            zkClient
                    .delete()
                    .guaranteed()
                    .deletingChildrenIfNeeded()
                    .forPath(lockName);
            log.info("release:{}", lockName);
        } catch (Exception e) {
            log.error("删除", e);
        }
    }

    public void setZkAddress(String zkAddress) {
        this.zkAddress = zkAddress;
    }
}
優點:具備高可用、可重入、阻塞鎖定特性,可解決失效死鎖問題。

缺點:因為需要頻繁的建立和刪除節點,效能上不如Redis方式。

四,比較

資料庫分散式鎖定實作缺點:

1.db操作效能較差,且有鎖定表的風險
2.非阻塞操作失敗後,需要輪詢,佔用cpu資源;
3.長時間不commit或長時間輪詢,可能會佔用較多連線資源

Redis(快取)分散式鎖定實作
#缺點:

#1.鎖定刪除失敗過期時間不好控制
2.非阻塞,操作失敗後,需要輪詢,佔用cpu資源;

ZK分散式鎖定實作
缺點:效能不如redis實現,主要原因是寫操作(取得鎖定釋放鎖)都需要在Leader上執行,然後同步到follower。

總之:ZooKeeper有較好的效能和可靠性。

從理解的難易度角度(從低到高)資料庫> 快取> Zookeeper

從實現的複雜性角度(從低到高)Zookeeper >= 快取> ; 資料庫

從效能角度(從高到低)快取> Zookeeper >= 資料庫

從可靠性角度(從高到低)Zookeeper > 快取> 資料庫

相關推薦:《程式教學

以上是分散式鎖的三種實作方式是什麼?的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

SublimeText3 英文版

SublimeText3 英文版

推薦:為Win版本,支援程式碼提示!

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

mPDF

mPDF

mPDF是一個PHP庫,可以從UTF-8編碼的HTML產生PDF檔案。原作者Ian Back編寫mPDF以從他的網站上「即時」輸出PDF文件,並處理不同的語言。與原始腳本如HTML2FPDF相比,它的速度較慢,並且在使用Unicode字體時產生的檔案較大,但支援CSS樣式等,並進行了大量增強。支援幾乎所有語言,包括RTL(阿拉伯語和希伯來語)和CJK(中日韓)。支援嵌套的區塊級元素(如P、DIV),

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境