成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

分布式鎖看了又看,優秀方案我來告訴你

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

[[392389]]

分布式鎖的場景

秒殺場景案例

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

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

扣掉對應商品的庫存;2. 創建商品的訂單;3. 用戶支付。

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

 

注:對于鎖的粒度要根據具體的場景和需求來權衡。

三種分布式鎖

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

  1. Zookeeper 的一個節點不能被重復創建
  2. Zookeeper 的 Watcher 監聽機制

非公平鎖

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

優點和缺點

其實上面的實現有優點也有缺點:

優點:

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

缺點:

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

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

解決“驚群”:

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

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

公平鎖

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

優點和缺點

優點: 如上借助于臨時順序節點,可以避免同時多個節點的并發競爭鎖,緩解了服務端壓力。

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

讀寫鎖實現

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

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

分布式鎖實戰

本文源碼中使用環境:JDK 1.8 、Zookeeper 3.6.x

Curator 組件實現

POM 依賴

  1. <dependency> 
  2.   <groupId>org.apache.curator</groupId> 
  3.   <artifactId>curator-framework</artifactId> 
  4.   <version>2.13.0</version> 
  5. </dependency> 
  6. <dependency> 
  7.   <groupId>org.apache.curator</groupId> 
  8.   <artifactId>curator-recipes</artifactId> 
  9.   <version>2.13.0</version> 
  10. </dependency> 

互斥鎖運用

由于 Zookeeper 非公平鎖的 “驚群” 效應,非公平鎖在 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.     } 

讀寫鎖運用

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

  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("讀取庫存,當前庫存為: " + 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.     } 

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

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

分布式鎖的選擇

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

參考資料

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

http://curator.apache.org/

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

 

責任編輯:姜華 來源: 運維開發故事
相關推薦

2022-02-10 08:57:45

分布式線程鎖

2024-04-26 08:06:58

分布式系統

2019-06-19 15:40:06

分布式鎖RedisJava

2023-03-01 08:07:51

2024-01-09 08:20:05

2018-07-16 09:00:06

Ceph運維開源

2023-04-03 10:00:00

Redis分布式

2022-05-18 10:38:51

Redis分布式鎖數據

2019-07-18 09:17:19

Kafka消息隊列服務器

2021-10-09 11:34:59

MySQL分布式鎖庫存

2018-06-28 08:18:56

Ceph運維存儲

2018-07-17 08:14:22

分布式分布式鎖方位

2022-08-04 08:45:50

Redisson分布式鎖工具

2019-02-26 09:51:52

分布式鎖RedisZookeeper

2021-07-16 07:57:34

ZooKeeperCurator源碼

2018-11-27 16:17:13

分布式Tomcat

2021-11-26 06:43:19

Java分布式

2020-04-23 11:18:14

Redis分布式

2023-06-14 17:56:54

2022-07-06 08:01:05

數據庫分布式
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 国产精品不卡视频 | 久久综合九色综合欧美狠狠 | 中午字幕在线观看 | 欧美激情欧美激情在线五月 | 久久33 | 亚洲精品视频导航 | 黄色一级免费看 | 综合色影院 | 久久精品国产99国产精品 | 国产精品久久久久久久久久 | 国产福利资源在线 | av中文字幕在线观看 | 国产2区 | 欧美xxxx在线 | 欧美一区二区三区在线观看 | 亚洲国产区| 国产成人91视频 | 精品国产亚洲一区二区三区大结局 | 黄色激情毛片 | 久久高清 | 天天弄 | 久久最新| 老司机免费视频 | 亚洲人成在线观看 | 午夜电影福利 | 久久亚洲一区 | 成人网在线观看 | 久热精品在线播放 | 日本天天色 | 日韩视频1 | 欧美一区二区免费视频 | 精品视频一区二区 | 国产欧美日韩在线观看 | 精品国产一区二区三区久久久蜜月 | 欧美国产日韩一区 | 亚洲一区二区三区在线免费 | 一区二区视频免费观看 | 男女视频在线免费观看 | 小早川怜子xxxxaⅴ在线 | 国产 日韩 欧美 中文 在线播放 | 亚洲国产精品久久久 |