說說你瞭解的分佈式鎖實現
分佈式鎖是分佈式系統中用來解決多個進程或節點之間對共享資源的安全訪問問題的一種機制。
以下是幾種常見的分佈式鎖實現方式:
1. 基於數據庫的分佈式鎖
實現原理:
基於數據庫的分佈式鎖主要利用數據庫的唯一性索引或主鍵等特性來實現。
當需要獲取鎖時,向數據庫中插入一條記錄,如果插入成功,則表示獲取鎖成功;
如果插入失敗(如因唯一索引衝突),則表示鎖已被其他節點持有。
釋放鎖時,從數據庫中刪除相應的記錄。
優缺點:
-
• 優點:利用數據庫的事務特性保證了一致性和可靠性。
-
• 缺點:性能相對較差,因爲每次獲取和釋放鎖都需要對數據庫進行讀寫操作,且涉及到網絡通信延遲;同時,數據庫鎖沒有失效時間,未獲得鎖的進程只能一直等待已獲得鎖的進程主動釋放鎖,可能導致死鎖問題。
2. 基於緩存的分佈式鎖(以 Redis 爲例)
實現原理:
Redis 等緩存系統通常支持原子操作,如SETNX
(SET if Not eXists)。
當需要獲取鎖時,使用SETNX
命令嘗試設置一個鍵值對,如果鍵不存在,則設置成功並返回 1,表示獲取鎖成功;
如果鍵已存在,則返回 0,表示鎖已被其他節點持有。
同時,可以設置鍵的過期時間,以避免死鎖問題。
釋放鎖時,使用DEL
命令刪除鍵。
優缺點:
-
• 優點:性能較好,因爲緩存系統通常位於內存中,讀寫速度更快;支持原子操作,實現簡單;可以設置過期時間,避免死鎖。
-
• 缺點:可靠性相對較弱,如果緩存系統發生故障,可能導致鎖無法釋放或出現競爭條件。
3. 基於 ZooKeeper 的分佈式鎖
實現原理:
ZooKeeper 是一個分佈式協調服務,它提供了臨時節點和順序節點等特性來實現分佈式鎖。
當需要獲取鎖時,客戶端在 ZooKeeper 中創建一個臨時順序節點,然後獲取所有子節點並進行排序。
如果當前節點是所有子節點中最小的(即序號最小),則表示獲取鎖成功;否則,監聽前一個節點,等待其被刪除後重新嘗試獲取鎖。
釋放鎖時,客戶端刪除自己創建的臨時節點。
優缺點:
-
• 優點:利用 ZooKeeper 的順序節點和監聽機制保證了分佈式環境下的有序性和併發控制;ZooKeeper 本身提供了高可用和一致性保證。
-
• 缺點:實現相對複雜;對 ZooKeeper 的依賴較強;在集羣規模較大的情況下,可能存在羊羣效應等性能問題。
應用場景
對於上述提到的三種分佈式鎖實現方式(基於數據庫、基於 Redis、基於 ZooKeeper),以下是相應的 Java 示例代碼。
請注意,這些示例是爲了展示基本的實現原理,並不包含完整的錯誤處理和優化。
1. 基於數據庫的分佈式鎖(示例代碼簡化版)
由於基於數據庫的鎖實現通常依賴於數據庫的唯一約束,因此這裏給出一個簡化的 SQL 語句示例,而不是完整的 Java 代碼。
-- 創建鎖表(假設表名爲distributed_lock,包含鎖名稱和鎖持有者的信息)
CREATE TABLE distributed_lock (
lock_name VARCHAR(255) PRIMARY KEY,
lock_owner VARCHAR(255),
lock_time TIMESTAMP
);
-- 獲取鎖(假設鎖名稱爲'my_lock',鎖持有者爲'node1',鎖時間爲當前時間)
-- 如果插入成功,表示獲取鎖成功;如果插入失敗(如因唯一約束衝突),表示鎖已被其他節點持有
INSERT INTO distributed_lock (lock_name, lock_owner, lock_time)
VALUES ('my_lock', 'node1', NOW())
ON DUPLICATE KEY UPDATE lock_owner = VALUES(lock_owner), lock_time = VALUES(lock_time);
-- 釋放鎖(假設鎖名稱爲'my_lock',且鎖持有者爲'node1')
-- 在實際應用中,可能需要先檢查鎖是否仍然由當前節點持有,以避免誤刪除其他節點的鎖
DELETE FROM distributed_lock WHERE lock_name = 'my_lock' AND lock_owner = 'node1';
2. 基於 Redis 的分佈式鎖(使用 Jedis 客戶端)
import redis.clients.jedis.Jedis;
public class RedisDistributedLock {
private static final String LOCK_KEY = "my_lock";
private static final int EXPIRE_TIME = 10; // 鎖過期時間,單位秒
private static final String LOCK_VALUE = "locked";
public static boolean tryLock(Jedis jedis, String lockOwnerId) {
String result = jedis.set(LOCK_KEY, lockOwnerId, "NX", "EX", EXPIRE_TIME);
return "OK".equals(result);
}
public static void releaseLock(Jedis jedis, String lockOwnerId) {
// 使用Lua腳本確保原子性操作:只有當鎖持有者與傳入的值相同時,才刪除鎖
String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
jedis.eval(luaScript, 1, LOCK_KEY, lockOwnerId);
}
public static void main(String[] args) {
Jedis jedis = new Jedis("localhost", 6379); // 連接Redis服務器
String lockOwnerId = "node1"; // 鎖持有者ID(可以是節點名稱、線程ID等)
// 嘗試獲取鎖
if (tryLock(jedis, lockOwnerId)) {
try {
// 執行臨界區代碼
System.out.println("Lock acquired, executing critical section...");
} finally {
// 釋放鎖
releaseLock(jedis, lockOwnerId);
System.out.println("Lock released.");
}
} else {
System.out.println("Failed to acquire lock.");
}
jedis.close(); // 關閉Redis連接
}
}
3. 基於 ZooKeeper 的分佈式鎖(使用 Curator 框架)
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import java.util.concurrent.TimeUnit;
public class ZooKeeperDistributedLock {
private static final String ZOOKEEPER_ADDRESS = "localhost:2181";
private static final String LOCK_PATH = "/locks/my_lock";
public static void main(String[] args) {
CuratorFramework client = CuratorFrameworkFactory.newClient(
ZOOKEEPER_ADDRESS,
new ExponentialBackoffRetry(1000, 3)
);
client.start();
InterProcessMutex lock = new InterProcessMutex(client, LOCK_PATH);
try {
// 嘗試獲取鎖,等待最多5秒
if (lock.acquire(5, TimeUnit.SECONDS)) {
try {
// 執行臨界區代碼
System.out.println("Lock acquired, executing critical section...");
} finally {
// 釋放鎖
lock.release();
System.out.println("Lock released.");
}
} else {
System.out.println("Failed to acquire lock within the specified timeout.");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
client.close();
}
}
}
在以上示例中:
-
• 基於數據庫的鎖實現僅給出了 SQL 語句示例,實際使用時需要集成到 Java 代碼中,並通過 JDBC 或類似框架執行。
-
• 基於 Redis 的鎖實現使用了 Jedis 客戶端庫,並展示瞭如何嘗試獲取鎖和釋放鎖。
-
• 基於 ZooKeeper 的鎖實現使用了 Curator 框架,該框架提供了更高層次的抽象和簡化的 API 來與 ZooKeeper 交互。示例中展示瞭如何使用
InterProcessMutex
來獲取和釋放分佈式鎖。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/ByniE88w2FWCeu_ngDnp2g