av激情亚洲男人的天堂国语,日韩欧美精品一中文字幕,无码av一区二区三区无码,国产又色又爽又刺激的a片,国产又色又爽又刺激的a片

分布式鎖看了又看,優(yōu)秀方案我來告訴你

分布式鎖看了又看,優(yōu)秀方案我來告訴你

作者:老鄭 2021-04-12 08:02:12

開發(fā)

前端

分布式 對于商品秒殺的場景,我們需要防止庫存超賣或者重復(fù)扣款等并發(fā)問題,我們通常需要使用分布式鎖,來解決共享資源競爭導(dǎo)致數(shù)據(jù)不一致的問題。本篇就講解如何用分布式鎖的來解決此類問題。

成都創(chuàng)新互聯(lián)是專業(yè)的豐滿網(wǎng)站建設(shè)公司,豐滿接單;提供網(wǎng)站設(shè)計、成都網(wǎng)站制作,網(wǎng)頁設(shè)計,網(wǎng)站設(shè)計,建網(wǎng)站,PHP網(wǎng)站建設(shè)等專業(yè)做網(wǎng)站服務(wù);采用PHP框架,可快速的進行豐滿網(wǎng)站開發(fā)網(wǎng)頁制作和功能擴展;專業(yè)做搜索引擎喜愛的網(wǎng)站,專業(yè)的做網(wǎng)站團隊,希望更多企業(yè)前來合作!

[[392389]]

分布式鎖的場景

秒殺場景案例

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

以手機秒殺的場景為例子,在搶購的過程中通常我們有三個步驟:

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

對于這樣的場景我們就可以采用分布式鎖的來解決,比如我們在用戶進入秒殺 “下單“ 鏈接的過程中,我們可以對商品庫存進行加鎖,然后完成扣庫存和其他操作,操作完成后。釋放鎖,讓下一個用戶繼續(xù)進入保證庫存的安全性;也可以減少因為秒殺失敗,導(dǎo)致 DB 回滾的次數(shù)。整個流程如下圖所示:

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

三種分布式鎖

對于 Zookeeper 的分布式鎖實現(xiàn),主要是利用 Zookeeper 的兩個特征來實現(xiàn):

  1. Zookeeper 的一個節(jié)點不能被重復(fù)創(chuàng)建
  2. Zookeeper 的 Watcher 監(jiān)聽機制

非公平鎖

對于非公平鎖,我們在加鎖的過程如下圖所示。

優(yōu)點和缺點

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

優(yōu)點:

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

缺點:

重量級,同時在大量鎖的情況下會有 “驚群” 的問題。

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

解決“驚群”:

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

  1. 我們將鎖抽象成目錄,多個線程在此目錄下創(chuàng)建瞬時的順序節(jié)點,因為 Zookeeper 會為我們保證節(jié)點的順序性,所以可以利用節(jié)點的順序進行鎖的判斷。
  2. 首先創(chuàng)建順序節(jié)點,然后獲取當(dāng)前目錄下最小的節(jié)點,判斷最小節(jié)點是不是當(dāng)前節(jié)點,如果是那么獲取鎖成功,如果不是那么獲取鎖失敗。
  3. 獲取鎖失敗的節(jié)點獲取當(dāng)前節(jié)點上一個順序節(jié)點,對此節(jié)點注冊監(jiān)聽,當(dāng)節(jié)點刪除的時候通知當(dāng)前節(jié)點。
  4. 當(dāng)unlock的時候刪除節(jié)點之后會通知下一個節(jié)點。

公平鎖

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

優(yōu)點和缺點

優(yōu)點: 如上借助于臨時順序節(jié)點,可以避免同時多個節(jié)點的并發(fā)競爭鎖,緩解了服務(wù)端壓力。

缺點: 對于讀寫場景來說,無法解決一致性的問題,如果讀的時候也去獲取鎖的話,這樣會導(dǎo)致性能下降,對于這樣的問題,我們可以通過讀寫鎖來實現(xiàn)如類似 jdk 中的 ReadWriteLock

讀寫鎖實現(xiàn)

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

  1. read 請求, 如果前面是讀鎖,可以直接讀取,不需要監(jiān)聽。如果前面是一個或者多個寫鎖那么只需要監(jiān)聽最后一個寫鎖。
  2. write 請求,只需要對前面的節(jié)點監(jiān)聽。Watcher 機制和互斥鎖一樣。

分布式鎖實戰(zhàn)

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

Curator 組件實現(xiàn)

POM 依賴

  
 
 
 
  1.  
  2.   org.apache.curator 
  3.   curator-framework 
  4.   2.13.0 
  5.  
  6.  
  7.   org.apache.curator 
  8.   curator-recipes 
  9.   2.13.0 
  10.  

互斥鎖運用

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

  
 
 
 
  1. public class MutexTest { 
  2.     static ExecutorService executor = Executors.newFixedThreadPool(8); 
  3.     static AtomicInteger stock = new AtomicInteger(3); 
  4.     public static void main(String[] args) throws InterruptedException { 
  5.         CuratorFramework client = getZkClient(); 
  6.         String key = "/lock/lockId_111/111"; 
  7.         final InterProcessMutex mutex = new InterProcessMutex(client, key); 
  8.         for (int i = 0; i < 99; i++) { 
  9.             executor.submit(() -> { 
  10.                 if (stock.get() < 0) { 
  11.                     System.err.println("庫存不足, 直接返回"); 
  12.                     return; 
  13.                 } 
  14.                 try { 
  15.                     boolean acquire = mutex.acquire(200, TimeUnit.MILLISECONDS); 
  16.                     if (acquire) { 
  17.                         int s = stock.decrementAndGet(); 
  18.                         if (s < 0) { 
  19.                             System.err.println("進入秒殺,庫存不足"); 
  20.                         } else { 
  21.                             System.out.println("購買成功, 剩余庫存: " + s); 
  22.                         } 
  23.                     } 
  24.                 } catch (Exception e) { 
  25.                     e.printStackTrace(); 
  26.                 } finally { 
  27.                     try { 
  28.                         if (mutex.isAcquiredInThisProcess()) 
  29.                             mutex.release(); 
  30.                     } catch (Exception e) { 
  31.                         e.printStackTrace(); 
  32.                     } 
  33.                 } 
  34.             }); 
  35.         } 
  36.         while (true) { 
  37.             if (executor.isTerminated()) { 
  38.                 executor.shutdown(); 
  39.                 System.out.println("秒殺完畢剩余庫存為:" + stock.get()); 
  40.             } 
  41.             TimeUnit.MILLISECONDS.sleep(100); 
  42.         } 
  43.     } 
  44.     private static CuratorFramework getZkClient() { 
  45.         String zkServerAddress = "127.0.0.1:2181"; 
  46.         ExponentialBackoffRetry retryPolicy = new ExponentialBackoffRetry(1000, 3, 5000); 
  47.         CuratorFramework zkClient = CuratorFrameworkFactory.builder() 
  48.                 .connectString(zkServerAddress) 
  49.                 .sessionTimeoutMs(5000) 
  50.                 .connectionTimeoutMs(5000) 
  51.                 .retryPolicy(retryPolicy) 
  52.                 .build(); 
  53.         zkClient.start(); 
  54.         return zkClient; 
  55.     } 

讀寫鎖運用

讀寫鎖可以用來保證緩存雙寫的強一致性的,因為讀寫鎖在多線程讀的時候是無鎖的, 只有在前面有寫鎖的時候才會等待寫鎖完成后訪問數(shù)據(jù)。

  
 
 
 
  1. public class ReadWriteLockTest { 
  2.     static ExecutorService executor = Executors.newFixedThreadPool(8); 
  3.     static AtomicInteger stock = new AtomicInteger(3); 
  4.     static InterProcessMutex readLock; 
  5.     static InterProcessMutex writeLock; 
  6.     public static void main(String[] args) throws InterruptedException { 
  7.         CuratorFramework client = getZkClient(); 
  8.         String key = "/lock/lockId_111/1111"; 
  9.         InterProcessReadWriteLock readWriteLock = new InterProcessReadWriteLock(client, key); 
  10.         readLock = readWriteLock.readLock(); 
  11.         writeLock = readWriteLock.writeLock(); 
  12.         for (int i = 0; i < 16; i++) { 
  13.             executor.submit(() -> { 
  14.                 try { 
  15.                     boolean read = readLock.acquire(2000, TimeUnit.MILLISECONDS); 
  16.                     if (read) { 
  17.                         int num = stock.get(); 
  18.                         System.out.println("讀取庫存,當(dāng)前庫存為: " + num); 
  19.                         if (num < 0) { 
  20.                             System.err.println("庫存不足, 直接返回"); 
  21.                             return; 
  22.                         } 
  23.                     } 
  24.                 } catch (Exception e) { 
  25.                     e.printStackTrace(); 
  26.                 }finally { 
  27.                     if (readLock.isAcquiredInThisProcess()) { 
  28.                         try { 
  29.                             readLock.release(); 
  30.                         } catch (Exception e) { 
  31.                             e.printStackTrace(); 
  32.                         } 
  33.                     } 
  34.                 } 
  35.                 try { 
  36.                     boolean acquire = writeLock.acquire(2000, TimeUnit.MILLISECONDS); 
  37.                     if (acquire) { 
  38.                         int s = stock.get(); 
  39.                         if (s <= 0) { 
  40.                             System.err.println("進入秒殺,庫存不足"); 
  41.                         } else { 
  42.                             s = stock.decrementAndGet(); 
  43.                             System.out.println("購買成功, 剩余庫存: " + s); 
  44.                         } 
  45.                     } 
  46.                 } catch (Exception e) { 
  47.                     e.printStackTrace(); 
  48.                 } finally { 
  49.                     try { 
  50.                         if (writeLock.isAcquiredInThisProcess()) 
  51.                             writeLock.release(); 
  52.                     } catch (Exception e) { 
  53.                         e.printStackTrace(); 
  54.                     } 
  55.                 } 
  56.             }); 
  57.         } 
  58.         while (true) { 
  59.             if (executor.isTerminated()) { 
  60.                 executor.shutdown(); 
  61.                 System.out.println("秒殺完畢剩余庫存為:" + stock.get()); 
  62.             } 
  63.             TimeUnit.MILLISECONDS.sleep(100); 
  64.         } 
  65.     } 
  66.     private static CuratorFramework getZkClient() { 
  67.         String zkServerAddress = "127.0.0.1:2181"; 
  68.         ExponentialBackoffRetry retryPolicy = new ExponentialBackoffRetry(1000, 3, 5000); 
  69.         CuratorFramework zkClient = CuratorFrameworkFactory.builder() 
  70.                 .connectString(zkServerAddress) 
  71.                 .sessionTimeoutMs(5000) 
  72.                 .connectionTimeoutMs(5000) 
  73.                 .retryPolicy(retryPolicy) 
  74.                 .build(); 
  75.         zkClient.start(); 
  76.         return zkClient; 
  77.     } 

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

  
 
 
 
  1. 讀取庫存,當(dāng)前庫存為: 3 
  2. 讀取庫存,當(dāng)前庫存為: 3 
  3. 讀取庫存,當(dāng)前庫存為: 3 
  4. 讀取庫存,當(dāng)前庫存為: 3 
  5. 讀取庫存,當(dāng)前庫存為: 3 
  6. 讀取庫存,當(dāng)前庫存為: 3 
  7. 讀取庫存,當(dāng)前庫存為: 3 
  8. 讀取庫存,當(dāng)前庫存為: 3 
  9. 購買成功, 剩余庫存: 2 
  10. 購買成功, 剩余庫存: 1 
  11. 購買成功, 剩余庫存: 0 
  12. 進入秒殺,庫存不足 
  13. 進入秒殺,庫存不足 
  14. 進入秒殺,庫存不足 
  15. 進入秒殺,庫存不足 
  16. 進入秒殺,庫存不足 
  17. 讀取庫存,當(dāng)前庫存為: 0 
  18. 讀取庫存,當(dāng)前庫存為: 0 
  19. 讀取庫存,當(dāng)前庫存為: 0 
  20. 讀取庫存,當(dāng)前庫存為: 0 
  21. 讀取庫存,當(dāng)前庫存為: 0 
  22. 讀取庫存,當(dāng)前庫存為: 0 
  23. 讀取庫存,當(dāng)前庫存為: 0 
  24. 讀取庫存,當(dāng)前庫存為: 0 
  25. 進入秒殺,庫存不足 
  26. 進入秒殺,庫存不足 
  27. 進入秒殺,庫存不足 
  28. 進入秒殺,庫存不足 
  29. 進入秒殺,庫存不足 
  30. 進入秒殺,庫存不足 
  31. 進入秒殺,庫存不足 
  32. 進入秒殺,庫存不足 

分布式鎖的選擇

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

參考資料

https://www.cnblogs.com/leeego-123/p/12162220.html

http://curator.apache.org/

https://blog.csdn.net/hosaos/article/details/89521537


當(dāng)前題目:分布式鎖看了又看,優(yōu)秀方案我來告訴你
網(wǎng)站URL:http://uogjgqi.cn/article/cdjpepo.html
掃二維碼與項目經(jīng)理溝通

我們在微信上24小時期待你的聲音

解答本文疑問/技術(shù)咨詢/運營咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流