文章目錄
- 分布式鎖
- 分布式鎖的實現
- zookeeper 分布式鎖原理
- Curator 實現分布式鎖API
- 1. InterProcessMutex(分布式可重入互斥鎖)
- 2. InterProcessSemaphoreMutex(分布式非可重入互斥鎖)
- 3. InterProcessReadWriteLock(分布式讀寫鎖)
- 4. InterProcessSemaphoreV2(分布式信號量)
- 5. MultiSharedLock(多共享鎖)
- 方案對比與推薦
- 模擬12306售票案例
- 案例背景
- 實現步驟
- 1. 依賴引入
- 2. ZooKeeper 連接配置
- 3. 票務服務(核心邏輯)
- 4. 模擬并發搶票
- 關鍵點解析
- 運行結果示例
- 擴展優化
分布式鎖
- 在進行單機應用開發時,涉及女并發同步的時候,我們往往采用synchronized或者Lock的方式來解決多線程間的代碼同步問題,這時多線程的運行都在同一個JVM之下,沒有任何問題。
- 但當我們的應用是分布式集群工作的情況下,屬于多個JVM下的工作環境,跨JVM之間已經無法通過多線程的鎖解決同步問題。
- 那么就需要一種更高級的鎖機制來處理這種跨機器的進程之間的數據同步問題,這就是分布式鎖。
分布式鎖的實現
- 基于緩存實現分布式鎖:redis,memcache
- zookeeper實現分布式鎖:Curator
- 數據庫層面實現分布式鎖: 樂觀鎖,悲觀鎖
zookeeper 分布式鎖原理
- 核心思想: 當客戶端要獲取鎖,則創建節點,使用完鎖,則刪除該節點。
- 客戶端時,在lock節點下創建 臨時順序 節點。
- 然后獲取lock下面的所有子節點,客戶端獲取到所有的子節點后,如果發現自己創建的子節點序號最小,那么就認為該客戶端獲取到了鎖。使用完鎖后,將該節點刪除。
3 如果發現自己創建的節點并非lock所有節點中最小的,說明自己還沒有獲取到鎖,此時客戶端需要找到比自己小的那個節點,同時對其注冊事件監聽,監聽刪除事件。
4 如果發現比自己小的那個節點被刪除,則客戶端的Watcher會收到相應通知,此時再次判斷自己創建的節點是否是lock子節點中最小的,如果是則獲取到了鎖,如果不是則重復以上步驟繼續獲取到比自己小的一個節點并注冊監聽。
Curator 實現分布式鎖API
Curator 提供了五種分布式鎖方案,每種方案適用于不同的業務場景,以下是具體介紹:
1. InterProcessMutex(分布式可重入互斥鎖)
- 特點:
- 可重入:同一線程可多次獲取鎖,避免死鎖。
- 互斥性:基于 ZooKeeper 臨時順序節點實現全局互斥,確保任何時刻只有一個客戶端持有鎖。
- 自動釋放:通過臨時節點的特性,客戶端宕機時鎖自動釋放。
- 適用場景:
- 需要線程安全的分布式資源訪問(如訂單處理、庫存扣減)。
- 示例代碼:
CuratorFramework client = CuratorFrameworkFactory.newClient("localhost:2181", new ExponentialBackoffRetry(1000, 3)); client.start(); InterProcessMutex lock = new InterProcessMutex(client, "/locks/myLock"); lock.acquire(); // 獲取鎖 try {// 執行業務邏輯 } finally {lock.release(); // 釋放鎖 }
2. InterProcessSemaphoreMutex(分布式非可重入互斥鎖)
- 特點:
- 非可重入:同一線程重復獲取鎖會阻塞,避免誤用導致的死鎖。
- 輕量級:相比可重入鎖,實現更簡單,性能略高。
- 適用場景:
- 需要嚴格互斥且無需重入的場景(如分布式任務調度)。
- 示例代碼:
InterProcessSemaphoreMutex lock = new InterProcessSemaphoreMutex(client, "/locks/nonReentrantLock"); lock.acquire(); // 獲取鎖 try {// 執行業務邏輯 } finally {lock.release(); // 釋放鎖 }
3. InterProcessReadWriteLock(分布式讀寫鎖)
- 特點:
- 讀寫分離:允許多個讀鎖并發,寫鎖獨占。
- 公平性:基于 ZooKeeper 節點順序實現公平鎖,避免寫鎖饑餓。
- 適用場景:
- 讀多寫少的場景(如分布式緩存更新、配置中心)。
- 示例代碼:
InterProcessReadWriteLock rwLock = new InterProcessReadWriteLock(client, "/locks/rwLock"); InterProcessMutex readLock = rwLock.readLock(); // 讀鎖 InterProcessMutex writeLock = rwLock.writeLock(); // 寫鎖 readLock.acquire(); // 獲取讀鎖 try {// 執行讀操作 } finally {readLock.release(); // 釋放讀鎖 }
4. InterProcessSemaphoreV2(分布式信號量)
- 特點:
- 資源限制:控制同時訪問資源的客戶端數量(如許可證數量)。
- 動態調整:可通過 ZooKeeper 節點動態修改信號量值。
- 適用場景:
- 限流控制(如 API 調用限流、連接池管理)。
- 示例代碼:
InterProcessSemaphoreV2 semaphore = new InterProcessSemaphoreV2(client, "/locks/semaphore", 3); // 允許3個客戶端同時訪問 Lease lease = semaphore.acquire(); // 獲取信號量 try {// 執行業務邏輯 } finally {semaphore.returnLease(lease); // 釋放信號量 }
5. MultiSharedLock(多共享鎖)
- 特點:
- 組合鎖:允許客戶端同時獲取多個鎖,實現跨資源的原子操作。
- 避免死鎖:通過全局順序獲取鎖,防止死鎖。
- 適用場景:
- 需要跨多個資源協調的場景(如分布式事務)。
- 示例代碼:
List<String> lockPaths = Arrays.asList("/locks/resource1", "/locks/resource2"); MultiSharedLock multiLock = new MultiSharedLock(client, lockPaths); multiLock.acquire(); // 獲取所有鎖 try {// 執行跨資源操作 } finally {multiLock.release(); // 釋放所有鎖 }
方案對比與推薦
鎖類型 | 可重入 | 并發性 | 適用場景 |
---|---|---|---|
InterProcessMutex | 是 | 低 | 需要線程安全的資源訪問 |
InterProcessSemaphoreMutex | 否 | 低 | 嚴格互斥場景 |
InterProcessReadWriteLock | 讀是 | 讀高/寫低 | 讀多寫少場景 |
InterProcessSemaphoreV2 | 否 | 高(有限制) | 限流控制 |
MultiSharedLock | 是 | 低 | 跨資源原子操作 |
- 推薦選擇:
- 默認場景:優先使用
InterProcessMutex
,兼顧安全性和靈活性。 - 高性能讀場景:選擇
InterProcessReadWriteLock
的讀鎖。 - 限流場景:使用
InterProcessSemaphoreV2
控制并發量。 - 復雜協調場景:
MultiSharedLock
實現跨資源同步。
- 默認場景:優先使用
模擬12306售票案例
以下是使用 Curator 的 InterProcessMutex
模擬 12306 售票系統 的分布式鎖案例,解決高并發下超賣問題:
案例背景
12306 售票系統需要保證:
- 同一車次余票的原子性操作:多個用戶同時購票時,不能出現超賣(如余票為 1 時,兩個用戶同時搶到票)。
- 分布式環境下的線程安全:多個售票服務節點(如不同服務器)同時處理請求時,需通過分布式鎖協調。
實現步驟
1. 依賴引入
<dependency><groupId>org.apache.curator</groupId><artifactId>curator-framework</artifactId><version>4.0.0</version></dependency><dependency><groupId>org.apache.curator</groupId><artifactId>curator-recipes</artifactId><version>4.0.0</version></dependency>
2. ZooKeeper 連接配置
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;public class ZkClient {private static final String ZK_ADDRESS = "localhost:2181";private static CuratorFramework client;static {client = CuratorFrameworkFactory.newClient(ZK_ADDRESS,new ExponentialBackoffRetry(1000, 3));client.start();}public static CuratorFramework getClient() {return client;}
}
3. 票務服務(核心邏輯)
import org.apache.curator.framework.recipes.locks.InterProcessMutex;public class TicketService {private final InterProcessMutex lock;private int remainingTickets; // 剩余票數(模擬數據庫)public TicketService(String lockPath, int initialTickets) {this.lock = new InterProcessMutex(ZkClient.getClient(), lockPath);this.remainingTickets = initialTickets;}// 購票方法public boolean buyTicket(String userId) {try {// 1. 獲取分布式鎖(阻塞式)if (lock.acquire(10, TimeUnit.SECONDS)) { // 超時時間10秒try {// 2. 檢查余票(雙重檢查,避免鎖內耗時)if (remainingTickets <= 0) {System.out.println("用戶 " + userId + " 購票失敗:票已售罄");return false;}// 3. 模擬業務邏輯(如扣減庫存、生成訂單)Thread.sleep(50); // 模擬網絡延遲或耗時操作// 4. 扣減票數remainingTickets--;System.out.println("用戶 " + userId + " 購票成功!剩余票數:" + remainingTickets);return true;} finally {// 5. 釋放鎖lock.release();}}} catch (Exception e) {e.printStackTrace();}return false;}
}
4. 模擬并發搶票
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;public class TicketSimulation {public static void main(String[] args) throws InterruptedException {// 初始化票務服務(鎖路徑為 /tickets/train123,初始票數10)TicketService ticketService = new TicketService("/tickets/train123", 10);// 模擬5個用戶并發搶票ExecutorService executor = Executors.newFixedThreadPool(2);for (int i = 1; i <= 5; i++) {final String userId = "User-" + i;executor.execute(() -> ticketService.buyTicket(userId));}executor.shutdown();executor.awaitTermination(1, TimeUnit.MINUTES);ZkClient.getClient().close();}
}
關鍵點解析
-
分布式鎖的作用:
- 確保同一時間只有一個線程能操作余票,避免超賣。
- 使用 ZooKeeper 臨時順序節點實現,客戶端宕機時鎖自動釋放。
-
鎖的粒度:
- 鎖路徑
/tickets/train123
對應具體車次,不同車次互不影響。
- 鎖路徑
-
超時控制:
lock.acquire(10, TimeUnit.SECONDS)
防止死鎖(如客戶端崩潰未釋放鎖)。
-
雙重檢查:
- 獲取鎖后再次檢查余票,避免鎖內耗時導致其他線程重復扣減。
-
性能優化:
- 鎖的持有時間盡可能短(僅包裹關鍵代碼段)。
運行結果示例
用戶 User-1 購票成功!剩余票數:9
用戶 User-2 購票成功!剩余票數:8
...
用戶 User-3 購票成功!剩余票數:0
用戶 User-2 購票失敗:票已售罄
...
擴展優化
-
數據庫集成:
- 實際場景中,余票應存儲在數據庫,通過鎖保證分布式事務(如扣減庫存和生成訂單的原子性)。
-
鎖的公平性:
- Curator 的
InterProcessMutex
默認公平鎖,按請求順序獲取鎖。
- Curator 的
-
Redisson 替代方案:
- 如果使用 Redis,可用 Redisson 的
RLock
實現類似功能。
- 如果使用 Redis,可用 Redisson 的
-
鎖重試策略:
- 可通過
RetryNTimes
或RetryUntilElapsed
自定義重試邏輯。
- 可通過
通過 InterProcessMutex
,12306 售票系統能安全處理高并發請求,確保數據一致性。