java代碼怎樣實現分布式鎖 java代碼分布式編程的基礎教程
實現分布式鎖的核心是利用共享存儲服務協調多個節點訪問資源,1. 使用setnx和expire命令結合uuid和lua腳本,以確保基于redis的方案的唯一性和原子性,適用于高性能場景;2. 基于zookeeper的解決方案是通過創建臨時順序節點和監控前驅節點來實現的。watcher機制用于減少輪詢,支持自動釋放鎖,適用于高可靠性場景;3. 在選擇方案時,需要權衡性能、可靠性、復雜性、鎖類型以及是否需要自動釋放等因素,redis適用于高性能、低延遲的需求,zookeeper適合強一致性要求;4. 避免死鎖等常見問題,設置過期時間或臨時節點,使用lua腳本確保刪除原子,避免提前釋放可啟用鎖續約,合理配置集群參數,支持重新訪問可記錄線程ID和重新訪問次數;5. 其它方案包括數據庫行鎖,etcd、consul和基于paxos/raft的一致性系統分別適用于不同性能、可靠性和復雜性要求的場景。最終選擇應根據實際業務需要進行綜合評估。
實現分布式鎖的核心在于利用分布式系統中的共享存儲服務,如Redis、Zookeeper或數據庫協調多個節點訪問共享資源。選擇哪種方案取決于您的具體需求,如性能、可靠性和復雜性。
解決方案:
以下是基于Redis和Zookeper實現分布式鎖的Java代碼示例:
立即學習“Java免費學習筆記(深入);
1. 基于Redis的分布式鎖
Redis
SETNX
(Set if Not Exists)命令和
EXPIRE
命令組合可以實現簡單的分布式鎖。為了避免死鎖,需要設置鎖的過期時間。
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import java.util.UUID;
public class RedisDistributedLock {
private static final String LOCK_PREFIX = "lock:";
private static final String SUCCESS_RESULT = "OK";
private static final Long RELEASE_SUCCESS = 1L;
private JedisPool jedisPool;
public RedisDistributedLock(String host, int port) {
JedisPoolConfig config = new JedisPoolConfig();
// 連接池配置可根據需要調整
jedisPool = new JedisPool(config, host, port);
}
/**
* 嘗試獲取鎖
* @param lockName 鎖的名稱
* @param expireTime 鎖的過期時間,單位:秒
* @return 獲得鎖成功返回鎖的值,失敗返回nulll
*/
public String tryLock(String lockName, int expireTime) {
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
String lockKey = LOCK_PREFIX + lockName;
String lockValue = UUID.randomUUID().toString(); // 鎖的值,用于釋放鎖時的驗證
String result = jedis.set(lockKey, lockValue, "NX", "EX", expireTime);
if (SUCCESS_RESULT.equals(result)) {
return lockValue;
}
return null;
} finally {
if (jedis != null) {
jedis.close();
}
}
}
/**
* 釋放鎖
* @param lockName 鎖的名稱
* @param lockValue 鎖的值
* @return 釋放鎖是否成功
*/
public boolean releaseLock(String lockName, String lockValue) {
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
String lockKey = LOCK_PREFIX + lockName;
// 使用Lua腳本保證原子性
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Object result = jedis.eval(script, 1, lockKey, lockValue);
if (RELEASE_SUCCESS.equals(result)) {
return true;
}
return false;
} finally {
if (jedis != null) {
jedis.close();
}
}
}
public void close() {
if (jedisPool != null) {
jedisPool.close();
}
}
public static void main(String[] args) throws InterruptedException {
RedisDistributedLock lock = new RedisDistributedLock("localhost", 6379);
String lockName = "myLock";
String lockValue = lock.tryLock(lockName, 10); // 嘗試獲得鎖,過期時間為10秒
if (lockValue != null) {
try {
System.out.println("成功獲取鎖,lockValue: " + lockValue);
// 模擬業務邏輯
Thread.sleep(5000);
} finally {
boolean released = lock.releaseLock(lockName, lockValue);
System.out.println("釋放鎖結果: " + released);
lock.close();
}
} else {
System.out.println("獲取鎖失敗");
lock.close();
}
}
}關鍵點:
- 鎖的唯一標志: 使用UUID作為鎖的值,防止其他客戶端的鎖被誤刪。
- Lua腳本:Lua腳本: 使用Lua腳本保證刪除鎖的原子性,避免并發問題。
- 連接池: 利用JedisPol管理Redis連接,提高性能。
2. 基于Zoookeeper的分布式鎖
Zookeeper通過創建臨時序列節點來實現分布式鎖。當客戶創建鎖節點時,如果該節點是序列號最小的節點,則認為該客戶獲得鎖。釋放鎖時,刪除該節點。
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
public class ZookeeperDistributedLock {
private static final String ZK_ADDRESS = "localhost:2181";
private static final String LOCK_ROOT_PATH = "/locks";
private static final String LOCK_NODE_PREFIX = "lock-";
private ZooKeeper zkClient;
private String lockPath;
private String currentLockNode;
private CountDownLatch latch = new CountDownLatch(1);
public ZookeeperDistributedLock(String lockName) throws IOException, InterruptedException, KeeperException {
zkClient = new ZooKeeper(ZK_ADDRESS, 5000, new Watcher() {
@Override
public void process(WatchedEvent event) {
if (event.getState() == Event.KeeperState.SyncConnected) {
latch.countDown();
}
}
});
latch.await();
// 檢查根節點是否存在,不存在就創造
Stat stat = zkClient.exists(LOCK_ROOT_PATH, false);
if (stat == null) {
zkClient.create(LOCK_ROOT_PATH, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
this.lockPath = LOCK_ROOT_PATH + "/" + lockName;
Stat lockStat = zkClient.exists(this.lockPath, false);
if(lockStat == null){
zkClient.create(this.lockPath, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
}
/**
* 嘗試獲取鎖
* @param timeout 超時時間,單位:毫秒
* @return 獲取鎖是否成功
*/
public boolean tryLock(long timeout) throws InterruptedException, KeeperException {
try {
// 創建臨時順序節點
currentLockNode = zkClient.create(lockPath + "/" + LOCK_NODE_PREFIX, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
// 獲取所有子節點
List<String> children = zkClient.getChildren(lockPath, false);
Collections.sort(children);
// 假如當前的節點是最小的節點,成功獲得鎖
if (currentLockNode.equals(lockPath + "/" + children.get(0))) {
return true;
}
// 監控前一節點
String previousNode = null;
for (int i = 0; i < children.size(); i++) {
if (currentLockNode.equals(lockPath + "/" + children.get(i))) {
previousNode = lockPath + "/" + children.get(i - 1);
break;
}
}
final CountDownLatch waitLatch = new CountDownLatch(1);
Watcher watcher = new Watcher() {
@Override
public void process(WatchedEvent event) {
if (event.getType() == Event.EventType.NodeDeleted) {
waitLatch.countDown();
}
}
};
zkClient.exists(previousNode, watcher);
return waitLatch.await(timeout, TimeUnit.MILLISECONDS); // 等待前一個節點被刪除
} catch (KeeperException e) {
// 處理連接斷開的情況,再試一次獲得鎖
if (e.code() == KeeperException.Code.CONNECTIONLOSS) {
return tryLock(timeout);
} else {
throw e;
}
}
}
/**
* 釋放鎖
*/
public void unlock() throws InterruptedException, KeeperException {
if (currentLockNode != null) {
zkClient.delete(currentLockNode, -1);
currentLockNode = null;
}
}
public void close() throws InterruptedException {
if (zkClient != null) {
zkClient.close();
}
}
public static void main(String[] args) throws IOException, InterruptedException, KeeperException {
ZookeeperDistributedLock lock = new ZookeeperDistributedLock("myLock");
try {
if (lock.tryLock(5000)) { // 嘗試獲得鎖,超時時間5秒
System.out.println("獲取鎖成功");
Thread.sleep(5000); // 模擬業務邏輯
} else {
System.out.println("獲取鎖失敗");
}
} finally {
lock.unlock();
lock.close();
}
}
}關鍵點:
- 臨時順序節點: 使用臨時順序節點,確保鎖的自動釋放,避免死鎖。
- Watcher機制: 利用Watcher監控前一個節點的刪除事件,減少輪詢。
- 連接丟失處理: 處理連接丟失,重新嘗試獲取鎖。
選擇Redis還是Zookeper取決于你的應用場景。Redis適用于性能要求高的場景,Zookeper適用于可靠性要求高的場景。此外,還可以考慮使用Redison等包裝好的客戶端,提供更先進的分布式鎖定功能。
如何選擇合適的分布式鎖實現方案?
選擇合適的分布式鎖實現方案,需要綜合考慮以下幾個方面:
- 性能: Redis通常性能更高,因為它基于內存,讀寫速度快。Zookeper的性能相對較低,因為它需要磁盤寫入和節點同步。如果您的應用程序對性能有很高的要求,Redis可能更合適。
- 可靠性: Zookeper具有更高的可靠性,因為它是一種專門為分布式協調設計的服務,具有容錯性和數據一致性的保證。雖然Redis也可以通過主復制和Sentinel實現高可用性,但在某些情況下可能存在數據丟失的風險。如果您的應用程序對可靠性有很高的要求,Zookeper可能更合適。
- 復雜度: Redis的實現相對簡單,只需要使用
SETNX
和
EXPIRE
命令。Zookeeper的實現相對復雜,需要創建臨時順序節點、監控節點刪除事件等。如果您的應用程序需要開發效率,Redis可能更合適。
- 鎖的類型: Redis只支持排他鎖,即同時只有一個客戶端。Zookeeper可以支持多種類型的鎖,如排他鎖、共享鎖等。如果您的應用程序需要支持多種類型的鎖,Zookeeper可能更合適。
- 鎖是否需要自動釋放: 即使客戶端出現故障,Zookeeper的臨時節點也可以自動釋放鎖。Redis需要設置鎖的過期時間,以避免死鎖,但如果客戶端在過期前完成操作,鎖可能會提前釋放。如果您的應用程序需要自動釋放鎖的功能,Zookeeper可能更合適。
一般來說,Redis適用于性能要求高、可靠性要求低的場景,而Zookeper適用于可靠性要求高、性能要求低的場景。在實際應用中,可根據具體需要進行選擇。
如何避免分布式鎖的常見問題?
使用分布式鎖時,應注意以下常見問題:
- 死鎖: 如果客戶端在獲得鎖后出現故障,鎖未釋放,可能會導致死鎖。為了避免死鎖,需要設置鎖的過期時間或使用Zookeeper的臨時節點。
- 提前釋放鎖: 如果客戶端在鎖過期之前完成操作,鎖可能會提前釋放,其他客戶端可以獲得鎖,從而導致并發問題。為了避免提前釋放鎖,可以使用Lua腳本來確保刪除鎖的原子性,或者使用Redisson等包裝好的客戶端。
- 鎖的續約: 如果客戶端的操作時間超過鎖的過期時間,鎖可能會自動釋放,導致并發問題。為了避免鎖自動釋放,可以使用鎖的續約機制,即當鎖即將過期時,客戶端會自動延長鎖的過期時間。Redison等客戶端提供鎖的續約功能。
- 腦裂: 在Redis集群中,如果發生腦破裂,即集群分裂成多個獨立的子集群,多個客戶端可能同時獲得鎖,從而導致并發問題。為了避免腦破裂,Redis集群的參數需要合理配置,例如設置
min-replicas-to-write
和
min-replicas-max-lag
參數。
- 重入性: 在某些情況下,相同的線程可能需要多次獲得相同的鎖。如果鎖不支持重新進入,可能會導致死鎖。為了支持重新進入,可以在鎖中記錄線程ID和重新進入次數。Redison等客戶提供可重新進入鎖。
除Redis和ZooKeeper外,還有以下幾種實現分布式鎖定的方法:
- 數據庫: 可使用數據庫的行鎖或樂觀鎖實現分布式鎖。例如,可以使用
SELECT ... FOR UPDATE
語句獲取行鎖,或使用版本號實現樂觀鎖。數據庫實現分布式鎖的優點簡單易懂,缺點是性能低,因為需要磁盤IO。
- etcd: etcd是一種可用于實現分布式鎖的分布式鍵值存儲系統。etcd的性能高于Zookeeper,但可靠性相對較低。
- Consul: Consul是一種服務發現和配置管理系統,也可用于實現分布式鎖。Consul的優點是使用簡單,缺點是性能低。
- 基于Paxos/Raft算法的分布式一致性系統: 基于Paxos/Raft算法的分布式一致性系統,如TiDBBOS、為了實現分布式鎖,CockroachDB等。該方案具有可靠性高、復雜性高等優點。
選擇哪個方案取決于你的具體需求。如果性能要求很高,可以選擇Redis或者ETCD。如果對可靠性要求很高,可以選擇基于Paxos/Raft算法的Zookeeper或分布式一致性系統。如果對復雜性有要求,可以選擇數據庫或者Consul。
以上是java代碼如何實現分布式鎖 關于java代碼分布式編程基礎教程的詳細信息。

浙公網安備 33010602011771號