溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

如何理解分布式鎖的場(chǎng)景

發(fā)布時(shí)間:2021-10-14 11:39:46 來(lái)源:億速云 閱讀:134 作者:iii 欄目:web開(kāi)發(fā)

本篇內(nèi)容主要講解“如何理解分布式鎖的場(chǎng)景”,感興趣的朋友不妨來(lái)看看。本文介紹的方法操作簡(jiǎn)單快捷,實(shí)用性強(qiáng)。下面就讓小編來(lái)帶大家學(xué)習(xí)“如何理解分布式鎖的場(chǎng)景”吧!

秒殺場(chǎng)景案例

對(duì)于商品秒殺的場(chǎng)景,我們需要防止庫(kù)存超賣或者重復(fù)扣款等并發(fā)問(wèn)題,我們通常需要使用分布式鎖,來(lái)解決共享資源競(jìng)爭(zhēng)導(dǎo)致數(shù)據(jù)不一致的問(wèn)題。

以手機(jī)秒殺的場(chǎng)景為例子,在搶購(gòu)的過(guò)程中通常我們有三個(gè)步驟:

扣掉對(duì)應(yīng)商品的庫(kù)存;2. 創(chuàng)建商品的訂單;3. 用戶支付。

對(duì)于這樣的場(chǎng)景我們就可以采用分布式鎖的來(lái)解決,比如我們?cè)谟脩暨M(jìn)入秒殺 “下單“  鏈接的過(guò)程中,我們可以對(duì)商品庫(kù)存進(jìn)行加鎖,然后完成扣庫(kù)存和其他操作,操作完成后。釋放鎖,讓下一個(gè)用戶繼續(xù)進(jìn)入保證庫(kù)存的安全性;也可以減少因?yàn)槊霘⑹?,?dǎo)致 DB  回滾的次數(shù)。整個(gè)流程如下圖所示:

 如何理解分布式鎖的場(chǎng)景

注:對(duì)于鎖的粒度要根據(jù)具體的場(chǎng)景和需求來(lái)權(quán)衡。

三種分布式鎖

對(duì)于 Zookeeper 的分布式鎖實(shí)現(xiàn),主要是利用 Zookeeper 的兩個(gè)特征來(lái)實(shí)現(xiàn):

  1. 鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)

  2. Zookeeper 的一個(gè)節(jié)點(diǎn)不能被重復(fù)創(chuàng)建

  3. Zookeeper 的 Watcher 監(jiān)聽(tīng)機(jī)制

非公平鎖

對(duì)于非公平鎖,我們?cè)诩渔i的過(guò)程如下圖所示。

如何理解分布式鎖的場(chǎng)景

優(yōu)點(diǎn)和缺點(diǎn)

其實(shí)上面的實(shí)現(xiàn)有優(yōu)點(diǎn)也有缺點(diǎn):

優(yōu)點(diǎn):

實(shí)現(xiàn)比較簡(jiǎn)單,有通知機(jī)制,能提供較快的響應(yīng),有點(diǎn)類似 ReentrantLock 的思想,對(duì)于節(jié)點(diǎn)刪除失敗的場(chǎng)景由 Session  超時(shí)保證節(jié)點(diǎn)能夠刪除掉。

缺點(diǎn):

重量級(jí),同時(shí)在大量鎖的情況下會(huì)有 “驚群” 的問(wèn)題。

“驚群” 就是在一個(gè)節(jié)點(diǎn)刪除的時(shí)候,大量對(duì)這個(gè)節(jié)點(diǎn)的刪除動(dòng)作有訂閱 Watcher  的線程會(huì)進(jìn)行回調(diào),這對(duì)Zk集群是十分不利的。所以需要避免這種現(xiàn)象的發(fā)生。

解決“驚群”:

為了解決“驚群“問(wèn)題,我們需要放棄訂閱一個(gè)節(jié)點(diǎn)的策略,那么怎么做呢?

  1. 鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)

  2. 我們將鎖抽象成目錄,多個(gè)線程在此目錄下創(chuàng)建瞬時(shí)的順序節(jié)點(diǎn),因?yàn)?Zookeeper 會(huì)為我們保證節(jié)點(diǎn)的順序性,所以可以利用節(jié)點(diǎn)的順序進(jìn)行鎖的判斷。

  3. 首先創(chuàng)建順序節(jié)點(diǎn),然后獲取當(dāng)前目錄下最小的節(jié)點(diǎn),判斷最小節(jié)點(diǎn)是不是當(dāng)前節(jié)點(diǎn),如果是那么獲取鎖成功,如果不是那么獲取鎖失敗。

  4. 獲取鎖失敗的節(jié)點(diǎn)獲取當(dāng)前節(jié)點(diǎn)上一個(gè)順序節(jié)點(diǎn),對(duì)此節(jié)點(diǎn)注冊(cè)監(jiān)聽(tīng),當(dāng)節(jié)點(diǎn)刪除的時(shí)候通知當(dāng)前節(jié)點(diǎn)。

  5. 當(dāng)unlock的時(shí)候刪除節(jié)點(diǎn)之后會(huì)通知下一個(gè)節(jié)點(diǎn)。

公平鎖

基于非公平鎖的缺點(diǎn),我們可以通過(guò)一下的方案來(lái)規(guī)避。

如何理解分布式鎖的場(chǎng)景

優(yōu)點(diǎn)和缺點(diǎn)

優(yōu)點(diǎn): 如上借助于臨時(shí)順序節(jié)點(diǎn),可以避免同時(shí)多個(gè)節(jié)點(diǎn)的并發(fā)競(jìng)爭(zhēng)鎖,緩解了服務(wù)端壓力。

缺點(diǎn): 對(duì)于讀寫(xiě)場(chǎng)景來(lái)說(shuō),無(wú)法解決一致性的問(wèn)題,如果讀的時(shí)候也去獲取鎖的話,這樣會(huì)導(dǎo)致性能下降,對(duì)于這樣的問(wèn)題,我們可以通過(guò)讀寫(xiě)鎖來(lái)實(shí)現(xiàn)如類似 jdk  中的 ReadWriteLock

讀寫(xiě)鎖實(shí)現(xiàn)

對(duì)于讀寫(xiě)鎖的特點(diǎn):讀寫(xiě)鎖在如果多個(gè)線程都是在讀的時(shí)候,是可以并發(fā)讀的,就是一個(gè)無(wú)鎖的狀態(tài),如果有寫(xiě)鎖正在操作的時(shí)候,那么讀鎖需要等待寫(xiě)鎖。在加寫(xiě)鎖的時(shí)候,由于前面的讀鎖都是并發(fā),所以需要監(jiān)聽(tīng)最后一個(gè)讀鎖完成后執(zhí)行寫(xiě)鎖。步驟如下:

  1. 鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)

  2. read 請(qǐng)求, 如果前面是讀鎖,可以直接讀取,不需要監(jiān)聽(tīng)。如果前面是一個(gè)或者多個(gè)寫(xiě)鎖那么只需要監(jiān)聽(tīng)最后一個(gè)寫(xiě)鎖。

  3. write 請(qǐng)求,只需要對(duì)前面的節(jié)點(diǎn)監(jiān)聽(tīng)。Watcher 機(jī)制和互斥鎖一樣。

如何理解分布式鎖的場(chǎng)景

分布式鎖實(shí)戰(zhàn)

本文源碼中使用環(huán)境:JDK 1.8 、Zookeeper 3.6.x

Curator 組件實(shí)現(xiàn)

POM 依賴

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

互斥鎖運(yùn)用

由于 Zookeeper 非公平鎖的 “驚群” 效應(yīng),非公平鎖在 Zookeeper 中其實(shí)并不是最好的選擇。下面是一個(gè)模擬秒殺的例子來(lái)使用  Zookeeper 分布式鎖。

public class MutexTest {     static ExecutorService executor = Executors.newFixedThreadPool(8);     static AtomicInteger stock = new AtomicInteger(3);     public static void main(String[] args) throws InterruptedException {         CuratorFramework client = getZkClient();         String key = "/lock/lockId_111/111";         final InterProcessMutex mutex = new InterProcessMutex(client, key);         for (int i = 0; i < 99; i++) {             executor.submit(() -> {                 if (stock.get() < 0) {                     System.err.println("庫(kù)存不足, 直接返回");                     return;                 }                 try {                     boolean acquire = mutex.acquire(200, TimeUnit.MILLISECONDS);                     if (acquire) {                         int s = stock.decrementAndGet();                         if (s < 0) {                             System.err.println("進(jìn)入秒殺,庫(kù)存不足");                         } else {                             System.out.println("購(gòu)買成功, 剩余庫(kù)存: " + s);                         }                     }                 } catch (Exception e) {                     e.printStackTrace();                 } finally {                     try {                         if (mutex.isAcquiredInThisProcess())                             mutex.release();                     } catch (Exception e) {                         e.printStackTrace();                     }                 }             });         }         while (true) {             if (executor.isTerminated()) {                 executor.shutdown();                 System.out.println("秒殺完畢剩余庫(kù)存為:" + stock.get());             }             TimeUnit.MILLISECONDS.sleep(100);         }     }     private static CuratorFramework getZkClient() {         String zkServerAddress = "127.0.0.1:2181";         ExponentialBackoffRetry retryPolicy = new ExponentialBackoffRetry(1000, 3, 5000);         CuratorFramework zkClient = CuratorFrameworkFactory.builder()                 .connectString(zkServerAddress)                 .sessionTimeoutMs(5000)                 .connectionTimeoutMs(5000)                 .retryPolicy(retryPolicy)                 .build();         zkClient.start();         return zkClient;     } }

讀寫(xiě)鎖運(yùn)用

讀寫(xiě)鎖可以用來(lái)保證緩存雙寫(xiě)的強(qiáng)一致性的,因?yàn)樽x寫(xiě)鎖在多線程讀的時(shí)候是無(wú)鎖的, 只有在前面有寫(xiě)鎖的時(shí)候才會(huì)等待寫(xiě)鎖完成后訪問(wèn)數(shù)據(jù)。

public class ReadWriteLockTest {     static ExecutorService executor = Executors.newFixedThreadPool(8);     static AtomicInteger stock = new AtomicInteger(3);     static InterProcessMutex readLock;     static InterProcessMutex writeLock;     public static void main(String[] args) throws InterruptedException {         CuratorFramework client = getZkClient();         String key = "/lock/lockId_111/1111";         InterProcessReadWriteLock readWriteLock = new InterProcessReadWriteLock(client, key);         readLock = readWriteLock.readLock();         writeLock = readWriteLock.writeLock();         for (int i = 0; i < 16; i++) {             executor.submit(() -> {                 try {                     boolean read = readLock.acquire(2000, TimeUnit.MILLISECONDS);                     if (read) {                         int num = stock.get();                         System.out.println("讀取庫(kù)存,當(dāng)前庫(kù)存為: " + num);                         if (num < 0) {                             System.err.println("庫(kù)存不足, 直接返回");                             return;                         }                     }                 } catch (Exception e) {                     e.printStackTrace();                 }finally {                     if (readLock.isAcquiredInThisProcess()) {                         try {                             readLock.release();                         } catch (Exception e) {                             e.printStackTrace();                         }                     }                 }                 try {                     boolean acquire = writeLock.acquire(2000, TimeUnit.MILLISECONDS);                     if (acquire) {                         int s = stock.get();                         if (s <= 0) {                             System.err.println("進(jìn)入秒殺,庫(kù)存不足");                         } else {                             s = stock.decrementAndGet();                             System.out.println("購(gòu)買成功, 剩余庫(kù)存: " + s);                         }                     }                 } catch (Exception e) {                     e.printStackTrace();                 } finally {                     try {                         if (writeLock.isAcquiredInThisProcess())                             writeLock.release();                     } catch (Exception e) {                         e.printStackTrace();                     }                 }             });         }         while (true) {             if (executor.isTerminated()) {                 executor.shutdown();                 System.out.println("秒殺完畢剩余庫(kù)存為:" + stock.get());             }             TimeUnit.MILLISECONDS.sleep(100);         }     }     private static CuratorFramework getZkClient() {         String zkServerAddress = "127.0.0.1:2181";         ExponentialBackoffRetry retryPolicy = new ExponentialBackoffRetry(1000, 3, 5000);         CuratorFramework zkClient = CuratorFrameworkFactory.builder()                 .connectString(zkServerAddress)                 .sessionTimeoutMs(5000)                 .connectionTimeoutMs(5000)                 .retryPolicy(retryPolicy)                 .build();         zkClient.start();         return zkClient;     } }

打印結(jié)果如下,一開(kāi)始會(huì)有 8 個(gè)輸出結(jié)果為 讀取庫(kù)存,當(dāng)前庫(kù)存為: 3 然后在寫(xiě)鎖中回去順序的扣減少庫(kù)存。

讀取庫(kù)存,當(dāng)前庫(kù)存為: 3 讀取庫(kù)存,當(dāng)前庫(kù)存為: 3 讀取庫(kù)存,當(dāng)前庫(kù)存為: 3 讀取庫(kù)存,當(dāng)前庫(kù)存為: 3 讀取庫(kù)存,當(dāng)前庫(kù)存為: 3 讀取庫(kù)存,當(dāng)前庫(kù)存為: 3 讀取庫(kù)存,當(dāng)前庫(kù)存為: 3 讀取庫(kù)存,當(dāng)前庫(kù)存為: 3 購(gòu)買成功, 剩余庫(kù)存: 2 購(gòu)買成功, 剩余庫(kù)存: 1 購(gòu)買成功, 剩余庫(kù)存: 0 進(jìn)入秒殺,庫(kù)存不足 進(jìn)入秒殺,庫(kù)存不足 進(jìn)入秒殺,庫(kù)存不足 進(jìn)入秒殺,庫(kù)存不足 進(jìn)入秒殺,庫(kù)存不足 讀取庫(kù)存,當(dāng)前庫(kù)存為: 0 讀取庫(kù)存,當(dāng)前庫(kù)存為: 0 讀取庫(kù)存,當(dāng)前庫(kù)存為: 0 讀取庫(kù)存,當(dāng)前庫(kù)存為: 0 讀取庫(kù)存,當(dāng)前庫(kù)存為: 0 讀取庫(kù)存,當(dāng)前庫(kù)存為: 0 讀取庫(kù)存,當(dāng)前庫(kù)存為: 0 讀取庫(kù)存,當(dāng)前庫(kù)存為: 0 進(jìn)入秒殺,庫(kù)存不足 進(jìn)入秒殺,庫(kù)存不足 進(jìn)入秒殺,庫(kù)存不足 進(jìn)入秒殺,庫(kù)存不足 進(jìn)入秒殺,庫(kù)存不足 進(jìn)入秒殺,庫(kù)存不足 進(jìn)入秒殺,庫(kù)存不足 進(jìn)入秒殺,庫(kù)存不足

分布式鎖的選擇

咱們最常用的就是 Redis 的分布式鎖和 Zookeeper 的分布式鎖,在性能方面 Redis 的每秒鐘 TPS  可以上輕松上萬(wàn)。在大規(guī)模的高并發(fā)場(chǎng)景我推薦使用 Redis 分布式鎖來(lái)作為推薦的技術(shù)方案。如果對(duì)并發(fā)要求不是特別高的場(chǎng)景可以使用 Zookeeper  分布式來(lái)處理。

到此,相信大家對(duì)“如何理解分布式鎖的場(chǎng)景”有了更深的了解,不妨來(lái)實(shí)際操作一番吧!這里是億速云網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI