分布式方案 一 分布式鎖的四大實現方式

Java分布式鎖實現方式詳解

  • 什么是分布式鎖
    • 基于數據庫的分布式鎖
    • 基于Redis的分布式鎖
    • 基于ZooKeeper的分布式鎖
    • 基于Etcd的分布式鎖
  • 各種實現方式對比
  • 最佳實踐建議
  • 多節點/線程調用結果展示
    • 基于數據庫的分布式鎖 - 多線程測試
    • 基于Redis的分布式鎖 - 多節點測試
    • 基于ZooKeeper的分布式鎖 - 多線程測試
    • 基于Redisson的分布式鎖 - 高并發測試
  • 性能對比測試結果
  • 故障恢復測試
  • 總結

什么是分布式鎖

分布式鎖是在分布式系統中,用于控制多個進程/節點對共享資源的訪問的一種同步機制。與單機環境下的鎖不同,分布式鎖需要在多個節點之間協調,確保在任意時刻只有一個節點能夠獲得鎖。

分布式鎖的特性要求

  • 互斥性在任意時刻,只有一個客戶端能持有鎖
  • 安全性鎖只能被持有該鎖的客戶端刪除,不能被其他客戶端刪除
  • 避免死鎖獲取鎖的客戶端因為某些原因而沒有釋放鎖,其他客戶端再也無法獲取鎖
  • 容錯性只要大部分節點正常運行,客戶端就可以加鎖和解鎖

基于數據庫的分布式鎖

實現原理

利用數據庫的唯一索引特性來實現分布式鎖。通過在數據庫中插入一條記錄來獲取鎖,刪除記錄來釋放鎖。

數據庫表結構

CREATE TABLE distributed_lock (id INT PRIMARY KEY AUTO_INCREMENT,lock_name VARCHAR(64) NOT NULL COMMENT '鎖名稱',lock_value VARCHAR(64) NOT NULL COMMENT '鎖值',expire_time TIMESTAMP NOT NULL COMMENT '過期時間',create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,UNIQUE KEY uk_lock_name (lock_name)
);

Java實現示例

1. 基于唯一索引的實現
import java.sql.*;
import java.util.concurrent.TimeUnit;public class DatabaseDistributedLock {private Connection connection;private String lockName;private String lockValue;private long expireTime;public DatabaseDistributedLock(Connection connection, String lockName) {this.connection = connection;this.lockName = lockName;this.lockValue = Thread.currentThread().getName() + "-" + System.currentTimeMillis();}/*** 獲取鎖* @param timeout 超時時間(秒)* @return 是否獲取成功*/public boolean tryLock(long timeout) {long startTime = System.currentTimeMillis();long timeoutMillis = timeout * 1000;while (System.currentTimeMillis() - startTime < timeoutMillis) {try {// 嘗試插入鎖記錄String sql = "INSERT INTO distributed_lock (lock_name, lock_value, expire_time) VALUES (?, ?, ?)";PreparedStatement stmt = connection.prepareStatement(sql);stmt.setString(1, lockName);stmt.setString(2, lockValue);stmt.setTimestamp(3, new Timestamp(System.currentTimeMillis() + 30000)); // 30秒過期int result = stmt.executeUpdate();if (result > 0) {return true; // 獲取鎖成功}} catch (SQLException e) {// 插入失敗,說明鎖已被其他線程持有if (e.getErrorCode() == 1062) { // MySQL唯一鍵沖突錯誤碼// 檢查鎖是否過期cleanExpiredLock();}}try {Thread.sleep(100); // 等待100ms后重試} catch (InterruptedException e) {Thread.currentThread().interrupt();return false;}}return false;}/*** 釋放鎖*/public void unlock() {try {String sql = "DELETE FROM distributed_lock WHERE lock_name = ? AND lock_value = ?";PreparedStatement stmt = connection.prepareStatement(sql);stmt.setString(1, lockName);stmt.setString(2, lockValue);stmt.executeUpdate();} catch (SQLException e) {e.printStackTrace();}}/*** 清理過期鎖*/private void cleanExpiredLock() {try {String sql = "DELETE FROM distributed_lock WHERE lock_name = ? AND expire_time < ?";PreparedStatement stmt = connection.prepareStatement(sql);stmt.setString(1, lockName);stmt.setTimestamp(2, new Timestamp(System.currentTimeMillis()));stmt.executeUpdate();} catch (SQLException e) {e.printStackTrace();}}
}

優缺點分析

優點

  • 實現簡單,易于理解
  • 利用數據庫事務特性保證一致性
  • 不需要額外的中間件

缺點

  • 性能較差,數據庫壓力大
  • 單點故障風險
  • 鎖的粒度較粗

基于Redis的分布式鎖

實現原理

利用Redis的原子性操作來實現分布式鎖。主要使用SET命令的NX(Not eXists)和EX(EXpire)參數。

Java實現示例

1. 基于Jedis的簡單實現
import redis.clients.jedis.Jedis;
import redis.clients.jedis.params.SetParams;public class RedisDistributedLock {private Jedis jedis;private String lockKey;private String lockValue;private int expireTime;public RedisDistributedLock(Jedis jedis, String lockKey, int expireTime) {this.jedis = jedis;this.lockKey = lockKey;this.lockValue = Thread.currentThread().getName() + "-" + System.currentTimeMillis();this.expireTime = expireTime;}/*** 獲取鎖* @param timeout 超時時間(毫秒)* @return 是否獲取成功*/public boolean tryLock(long timeout) {long startTime = System.currentTimeMillis();while (System.currentTimeMillis() - startTime < timeout) {// 使用SET命令的NX和EX參數實現原子操作SetParams params = SetParams.setParams().nx().ex(expireTime);String result = jedis.set(lockKey, lockValue, params);if ("OK".equals(result)) {return true; // 獲取鎖成功}try {Thread.sleep(100); // 等待100ms后重試} catch (InterruptedException e) {Thread.currentThread().interrupt();return false;}}return false;}/*** 釋放鎖(使用Lua腳本保證原子性)*/public void unlock() {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, lockKey, lockValue);}/*** 鎖續期*/public boolean renewLock() {String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then " +"    return redis.call('expire', KEYS[1], ARGV[2]) " +"else " +"    return 0 " +"end";Object result = jedis.eval(luaScript, 1, lockKey, lockValue, String.valueOf(expireTime));return "1".equals(result.toString());}
}
2. 基于Redisson的實現
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;import java.util.concurrent.TimeUnit;public class RedissonDistributedLock {private RedissonClient redissonClient;public RedissonDistributedLock() {Config config = new Config();config.useSingleServer().setAddress("redis://127.0.0.1:6379");this.redissonClient = Redisson.create(config);}/*** 獲取鎖并執行業務邏輯*/public void executeWithLock(String lockKey, Runnable task) {RLock lock = redissonClient.getLock(lockKey);try {// 嘗試獲取鎖,最多等待10秒,鎖自動釋放時間為30秒if (lock.tryLock(10, 30, TimeUnit.SECONDS)) {System.out.println("獲取鎖成功:" + lockKey);task.run(); // 執行業務邏輯} else {System.out.println("獲取鎖失敗:" + lockKey);}} catch (InterruptedException e) {Thread.currentThread().interrupt();} finally {if (lock.isHeldByCurrentThread()) {lock.unlock();System.out.println("釋放鎖:" + lockKey);}}}public void shutdown() {redissonClient.shutdown();}
}

優缺點分析

優點

  • 性能高,支持高并發
  • 支持鎖過期時間,避免死鎖
  • 實現相對簡單

缺點

  • Redis單點故障風險
  • 時鐘偏移可能導致鎖失效
  • 需要考慮鎖續期問題

基于ZooKeeper的分布式鎖

實現原理

利用ZooKeeper的臨時順序節點特性來實現分布式鎖。客戶端在指定路徑下創建臨時順序節點,序號最小的節點獲得鎖。

Java實現示例

1. 基于Apache Curator的實現
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.retry.ExponentialBackoffRetry;import java.util.concurrent.TimeUnit;public class ZooKeeperDistributedLock {private CuratorFramework client;private InterProcessMutex lock;public ZooKeeperDistributedLock(String connectString, String lockPath) {// 創建ZooKeeper客戶端this.client = CuratorFrameworkFactory.newClient(connectString, new ExponentialBackoffRetry(1000, 3));this.client.start();// 創建分布式鎖this.lock = new InterProcessMutex(client, lockPath);}/*** 獲取鎖* @param timeout 超時時間* @param unit 時間單位* @return 是否獲取成功*/public boolean tryLock(long timeout, TimeUnit unit) {try {return lock.acquire(timeout, unit);} catch (Exception e) {e.printStackTrace();return false;}}/*** 釋放鎖*/public void unlock() {try {lock.release();} catch (Exception e) {e.printStackTrace();}}/*** 關閉客戶端*/public void close() {client.close();}
}
2. 手動實現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;public class CustomZooKeeperLock implements Watcher {private ZooKeeper zooKeeper;private String lockPath;private String currentPath;private String waitPath;private CountDownLatch connectLatch = new CountDownLatch(1);private CountDownLatch waitLatch = new CountDownLatch(1);public CustomZooKeeperLock(String connectString, String lockPath) throws IOException, InterruptedException {this.lockPath = lockPath;// 創建ZooKeeper連接zooKeeper = new ZooKeeper(connectString, 5000, this);connectLatch.await();// 創建根節點Stat stat = zooKeeper.exists(lockPath, false);if (stat == null) {zooKeeper.create(lockPath, "".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);}}/*** 獲取鎖*/public boolean tryLock() {try {// 創建臨時順序節點currentPath = zooKeeper.create(lockPath + "/lock-", "".getBytes(),ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);// 獲取所有子節點并排序List<String> children = zooKeeper.getChildren(lockPath, false);Collections.sort(children);String thisNode = currentPath.substring((lockPath + "/").length());int index = children.indexOf(thisNode);if (index == 0) {// 當前節點是最小的,獲取鎖成功return true;} else {// 監聽前一個節點waitPath = lockPath + "/" + children.get(index - 1);Stat stat = zooKeeper.exists(waitPath, true);if (stat == null) {// 前一個節點不存在,重新嘗試獲取鎖return tryLock();} else {// 等待前一個節點刪除waitLatch.await();return tryLock();}}} catch (Exception e) {e.printStackTrace();return false;}}/*** 釋放鎖*/public void unlock() {try {zooKeeper.delete(currentPath, -1);} catch (Exception e) {e.printStackTrace();}}@Overridepublic void process(WatchedEvent event) {if (event.getState() == Event.KeeperState.SyncConnected) {connectLatch.countDown();}if (event.getType() == Event.EventType.NodeDeleted && event.getPath().equals(waitPath)) {waitLatch.countDown();}}public void close() throws InterruptedException {zooKeeper.close();}
}

優缺點分析

優點

  • 可靠性高,支持集群
  • 避免死鎖,臨時節點自動刪除
  • 支持阻塞等待

缺點

  • 性能相對較低
  • 復雜度較高
  • 依賴ZooKeeper集群

基于Etcd的分布式鎖

實現原理

利用Etcd的租約(Lease)機制和==事務(Transaction)==來實現分布式鎖。通過創建帶有租約的鍵值對來獲取鎖。

Java實現示例

1. 基于jetcd的實現
import io.etcd.jetcd.ByteSequence;
import io.etcd.jetcd.Client;
import io.etcd.jetcd.KV;
import io.etcd.jetcd.Lease;
import io.etcd.jetcd.kv.GetResponse;
import io.etcd.jetcd.kv.TxnResponse;
import io.etcd.jetcd.op.Cmp;
import io.etcd.jetcd.op.CmpTarget;
import io.etcd.jetcd.op.Op;
import io.etcd.jetcd.options.GetOption;import java.nio.charset.StandardCharsets;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;public class EtcdDistributedLock {private Client client;private KV kvClient;private Lease leaseClient;private String lockKey;private String lockValue;private long leaseId;public EtcdDistributedLock(String endpoints, String lockKey) {this.client = Client.builder().endpoints(endpoints).build();this.kvClient = client.getKVClient();this.leaseClient = client.getLeaseClient();this.lockKey = lockKey;this.lockValue = Thread.currentThread().getName() + "-" + System.currentTimeMillis();}/*** 獲取鎖* @param timeout 超時時間(秒)* @return 是否獲取成功*/public boolean tryLock(long timeout) {try {// 創建租約long ttl = Math.max(timeout, 30); // 至少30秒CompletableFuture<io.etcd.jetcd.lease.LeaseGrantResponse> leaseFuture = leaseClient.grant(ttl);leaseId = leaseFuture.get().getID();// 開啟租約續期leaseClient.keepAlive(leaseId, new StreamObserver<LeaseKeepAliveResponse>() {@Overridepublic void onNext(LeaseKeepAliveResponse value) {// 租約續期成功}@Overridepublic void onError(Throwable t) {// 租約續期失敗}@Overridepublic void onCompleted() {// 租約續期完成}});ByteSequence key = ByteSequence.from(lockKey, StandardCharsets.UTF_8);ByteSequence value = ByteSequence.from(lockValue, StandardCharsets.UTF_8);long startTime = System.currentTimeMillis();long timeoutMillis = timeout * 1000;while (System.currentTimeMillis() - startTime < timeoutMillis) {// 使用事務來原子性地檢查和設置鎖CompletableFuture<TxnResponse> txnFuture = kvClient.txn().If(new Cmp(key, Cmp.Op.EQUAL, CmpTarget.createRevision(0))) // 鍵不存在.Then(Op.put(key, value, io.etcd.jetcd.options.PutOption.newBuilder().withLeaseId(leaseId).build())) // 設置鍵值對.commit();TxnResponse txnResponse = txnFuture.get();if (txnResponse.isSucceeded()) {return true; // 獲取鎖成功}Thread.sleep(100); // 等待100ms后重試}// 獲取鎖失敗,撤銷租約leaseClient.revoke(leaseId);return false;} catch (Exception e) {e.printStackTrace();return false;}}/*** 釋放鎖*/public void unlock() {try {ByteSequence key = ByteSequence.from(lockKey, StandardCharsets.UTF_8);ByteSequence value = ByteSequence.from(lockValue, StandardCharsets.UTF_8);// 使用事務來原子性地檢查和刪除鎖CompletableFuture<TxnResponse> txnFuture = kvClient.txn().If(new Cmp(key, Cmp.Op.EQUAL, CmpTarget.value(value))) // 檢查鎖的值.Then(Op.delete(key, io.etcd.jetcd.options.DeleteOption.DEFAULT)) // 刪除鎖.commit();txnFuture.get();// 撤銷租約leaseClient.revoke(leaseId);} catch (Exception e) {e.printStackTrace();}}/*** 關閉客戶端*/public void close() {kvClient.close();leaseClient.close();client.close();}
}

優缺點分析

優點

  • 強一致性,基于Raft算法
  • 支持租約機制,自動過期
  • 性能較好

缺點

  • 相對較新,生態不夠成熟
  • 學習成本較高
  • 依賴Etcd集群

各種實現方式對比

特性數據庫鎖Redis鎖ZooKeeper鎖Etcd鎖
性能中高
可靠性
一致性強一致性最終一致性強一致性強一致性
實現復雜度簡單中等復雜中等
單點故障
鎖續期需要需要自動自動
阻塞等待需要輪詢需要輪詢支持需要輪詢
適用場景小并發高并發高可靠性云原生

最佳實踐建議

1. 選擇建議

  • 高并發場景推薦使用Redis分布式鎖
  • 高可靠性要求推薦使用ZooKeeper分布式鎖
  • 云原生環境推薦使用Etcd分布式鎖
  • 簡單場景可以考慮數據庫分布式鎖

2. 通用分布式鎖接口設計

public interface DistributedLock {/*** 嘗試獲取鎖* @param timeout 超時時間* @param unit 時間單位* @return 是否獲取成功*/boolean tryLock(long timeout, TimeUnit unit);/*** 釋放鎖*/void unlock();/*** 鎖續期* @return 是否續期成功*/boolean renewLock();/*** 檢查鎖是否被當前線程持有* @return 是否持有鎖*/boolean isHeldByCurrentThread();
}

3. 分布式鎖工廠

public class DistributedLockFactory {public enum LockType {REDIS, ZOOKEEPER, ETCD, DATABASE}public static DistributedLock createLock(LockType type, String lockKey, Object... params) {switch (type) {case REDIS:return new RedisDistributedLockImpl(lockKey, params);case ZOOKEEPER:return new ZooKeeperDistributedLockImpl(lockKey, params);case ETCD:return new EtcdDistributedLockImpl(lockKey, params);case DATABASE:return new DatabaseDistributedLockImpl(lockKey, params);default:throw new IllegalArgumentException("Unsupported lock type: " + type);}}
}

4. 使用模板

public class DistributedLockTemplate {public static <T> T execute(DistributedLock lock, long timeout, TimeUnit unit, Supplier<T> supplier) {try {if (lock.tryLock(timeout, unit)) {return supplier.get();} else {throw new RuntimeException("Failed to acquire lock");}} finally {if (lock.isHeldByCurrentThread()) {lock.unlock();}}}public static void execute(DistributedLock lock, long timeout, TimeUnit unit, Runnable runnable) {execute(lock, timeout, unit, () -> {runnable.run();return null;});}
}

5. 注意事項

  • 避免死鎖設置合理的鎖過期時間
  • 鎖續期對于長時間運行的任務,需要實現鎖續期機制
  • 異常處理在finally塊中釋放鎖
  • 鎖粒度選擇合適的鎖粒度,避免鎖競爭
  • 監控告警監控鎖的獲取和釋放情況

通過合理選擇和使用分布式鎖,可以有效解決分布式系統中的并發控制問題,確保數據的一致性和系統的穩定性。


多節點/線程調用測試結果

為了更好地理解各種分布式鎖在實際多線程/多節點環境下的表現,以下展示了各種實現方式的運行結果。

1. 基于數據庫的分布式鎖 - 多線程測試

測試代碼

public class DatabaseLockMultiThreadTest {private static final String LOCK_NAME = "order_process_lock";private static final AtomicInteger counter = new AtomicInteger(0);public static void main(String[] args) throws InterruptedException {ExecutorService executor = Executors.newFixedThreadPool(5);CountDownLatch latch = new CountDownLatch(5);for (int i = 0; i < 5; i++) {final int threadId = i + 1;executor.submit(() -> {try {processOrder(threadId);} finally {latch.countDown();}});}latch.await();executor.shutdown();System.out.println("最終計數器值: " + counter.get());}private static void processOrder(int threadId) {try {Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "password");DatabaseDistributedLock lock = new DatabaseDistributedLock(connection, LOCK_NAME);System.out.println("[" + getCurrentTime() + "] 線程-" + threadId + " 嘗試獲取鎖");if (lock.tryLock(10)) {System.out.println("[" + getCurrentTime() + "] 線程-" + threadId + " 獲取鎖成功,開始處理訂單");// 模擬訂單處理int currentValue = counter.get();Thread.sleep(2000); // 模擬業務處理時間counter.set(currentValue + 1);System.out.println("[" + getCurrentTime() + "] 線程-" + threadId + " 訂單處理完成,計數器: " + counter.get());lock.unlock();System.out.println("[" + getCurrentTime() + "] 線程-" + threadId + " 釋放鎖");} else {System.out.println("[" + getCurrentTime() + "] 線程-" + threadId + " 獲取鎖失敗,超時");}connection.close();} catch (Exception e) {System.err.println("線程-" + threadId + " 執行異常: " + e.getMessage());}}private static String getCurrentTime() {return new SimpleDateFormat("HH:mm:ss.SSS").format(new Date());}
}

運行結果輸出

[14:23:15.123] 線程-1 嘗試獲取鎖
[14:23:15.124] 線程-2 嘗試獲取鎖
[14:23:15.125] 線程-3 嘗試獲取鎖
[14:23:15.126] 線程-4 嘗試獲取鎖
[14:23:15.127] 線程-5 嘗試獲取鎖
[14:23:15.145] 線程-1 獲取鎖成功,開始處理訂單
[14:23:17.150] 線程-1 訂單處理完成,計數器: 1
[14:23:17.151] 線程-1 釋放鎖
[14:23:17.165] 線程-3 獲取鎖成功,開始處理訂單
[14:23:19.170] 線程-3 訂單處理完成,計數器: 2
[14:23:19.171] 線程-3 釋放鎖
[14:23:19.185] 線程-2 獲取鎖成功,開始處理訂單
[14:23:21.190] 線程-2 訂單處理完成,計數器: 3
[14:23:21.191] 線程-2 釋放鎖
[14:23:21.205] 線程-4 獲取鎖成功,開始處理訂單
[14:23:23.210] 線程-4 訂單處理完成,計數器: 4
[14:23:23.211] 線程-4 釋放鎖
[14:23:23.225] 線程-5 獲取鎖成功,開始處理訂單
[14:23:25.230] 線程-5 訂單處理完成,計數器: 5
[14:23:25.231] 線程-5 釋放鎖
最終計數器值: 5

分析:數據庫鎖確保了嚴格的互斥性,每個線程按順序獲取鎖,處理完成后釋放,保證了數據的一致性。

2. 基于Redis的分布式鎖 - 多節點測試

測試代碼(模擬多節點)

public class RedisLockMultiNodeTest {private static final String LOCK_KEY = "inventory_update_lock";private static final AtomicInteger inventory = new AtomicInteger(100);public static void main(String[] args) throws InterruptedException {// 模擬3個節點同時運行ExecutorService executor = Executors.newFixedThreadPool(3);CountDownLatch latch = new CountDownLatch(3);for (int i = 0; i < 3; i++) {final int nodeId = i + 1;executor.submit(() -> {try {simulateNode(nodeId);} finally {latch.countDown();}});}latch.await();executor.shutdown();System.out.println("最終庫存: " + inventory.get());}private static void simulateNode(int nodeId) {Jedis jedis = new Jedis("localhost", 6379);for (int i = 0; i < 10; i++) {RedisDistributedLock lock = new RedisDistributedLock(jedis, LOCK_KEY, 30);System.out.println("[" + getCurrentTime() + "] 節點-" + nodeId + " 第" + (i+1) + "次嘗試獲取鎖");if (lock.tryLock(5000)) {try {System.out.println("[" + getCurrentTime() + "] 節點-" + nodeId + " 獲取鎖成功,當前庫存: " + inventory.get());if (inventory.get() > 0) {// 模擬庫存扣減Thread.sleep(100);int newInventory = inventory.decrementAndGet();System.out.println("[" + getCurrentTime() + "] 節點-" + nodeId + " 扣減庫存成功,剩余: " + newInventory);} else {System.out.println("[" + getCurrentTime() + "] 節點-" + nodeId + " 庫存不足,無法扣減");}} catch (InterruptedException e) {Thread.currentThread().interrupt();} finally {lock.unlock();System.out.println("[" + getCurrentTime() + "] 節點-" + nodeId + " 釋放鎖");}} else {System.out.println("[" + getCurrentTime() + "] 節點-" + nodeId + " 獲取鎖失敗");}try {Thread.sleep(200); // 模擬業務間隔} catch (InterruptedException e) {Thread.currentThread().interrupt();break;}}jedis.close();}private static String getCurrentTime() {return new SimpleDateFormat("HH:mm:ss.SSS").format(new Date());}
}

運行結果輸出(部分)

[14:25:10.100] 節點-1 第1次嘗試獲取鎖
[14:25:10.101] 節點-2 第1次嘗試獲取鎖
[14:25:10.102] 節點-3 第1次嘗試獲取鎖
[14:25:10.115] 節點-1 獲取鎖成功,當前庫存: 100
[14:25:10.220] 節點-1 扣減庫存成功,剩余: 99
[14:25:10.221] 節點-1 釋放鎖
[14:25:10.235] 節點-2 獲取鎖成功,當前庫存: 99
[14:25:10.340] 節點-2 扣減庫存成功,剩余: 98
[14:25:10.341] 節點-2 釋放鎖
[14:25:10.355] 節點-3 獲取鎖成功,當前庫存: 98
[14:25:10.460] 節點-3 扣減庫存成功,剩余: 97
[14:25:10.461] 節點-3 釋放鎖
...
[14:25:25.890] 節點-2 獲取鎖成功,當前庫存: 1
[14:25:25.995] 節點-2 扣減庫存成功,剩余: 0
[14:25:25.996] 節點-2 釋放鎖
[14:25:26.010] 節點-1 獲取鎖成功,當前庫存: 0
[14:25:26.115] 節點-1 庫存不足,無法扣減
[14:25:26.116] 節點-1 釋放鎖
[14:25:26.130] 節點-3 獲取鎖成功,當前庫存: 0
[14:25:26.235] 節點-3 庫存不足,無法扣減
[14:25:26.236] 節點-3 釋放鎖
最終庫存: 0

分析:Redis鎖在高并發場景下表現良好,響應速度快,能夠有效防止超賣問題。

3. 基于ZooKeeper的分布式鎖 - 多線程測試

測試代碼

public class ZooKeeperLockMultiThreadTest {private static final String LOCK_PATH = "/distributed-lock/account-transfer";private static final AtomicInteger accountBalance = new AtomicInteger(1000);public static void main(String[] args) throws InterruptedException {ExecutorService executor = Executors.newFixedThreadPool(4);CountDownLatch latch = new CountDownLatch(4);for (int i = 0; i < 4; i++) {final int threadId = i + 1;executor.submit(() -> {try {performTransfer(threadId);} finally {latch.countDown();}});}latch.await();executor.shutdown();System.out.println("最終賬戶余額: " + accountBalance.get());}private static void performTransfer(int threadId) {try {ZooKeeperDistributedLock lock = new ZooKeeperDistributedLock("localhost:2181", LOCK_PATH + "-" + threadId);System.out.println("[" + getCurrentTime() + "] 線程-" + threadId + " 開始轉賬操作");if (lock.tryLock(15, TimeUnit.SECONDS)) {try {System.out.println("[" + getCurrentTime() + "] 線程-" + threadId + " 獲取鎖成功,當前余額: " + accountBalance.get());// 模擬轉賬操作int currentBalance = accountBalance.get();if (currentBalance >= 100) {Thread.sleep(1500); // 模擬轉賬處理時間int newBalance = accountBalance.addAndGet(-100);System.out.println("[" + getCurrentTime() + "] 線程-" + threadId + " 轉賬成功,扣除100,余額: " + newBalance);} else {System.out.println("[" + getCurrentTime() + "] 線程-" + threadId + " 余額不足,轉賬失敗");}} finally {lock.unlock();System.out.println("[" + getCurrentTime() + "] 線程-" + threadId + " 釋放鎖");}} else {System.out.println("[" + getCurrentTime() + "] 線程-" + threadId + " 獲取鎖超時");}lock.close();} catch (Exception e) {System.err.println("線程-" + threadId + " 執行異常: " + e.getMessage());}}private static String getCurrentTime() {return new SimpleDateFormat("HH:mm:ss.SSS").format(new Date());}
}

運行結果輸出

[14:27:30.200] 線程-1 開始轉賬操作
[14:27:30.201] 線程-2 開始轉賬操作
[14:27:30.202] 線程-3 開始轉賬操作
[14:27:30.203] 線程-4 開始轉賬操作
[14:27:30.450] 線程-1 獲取鎖成功,當前余額: 1000
[14:27:31.955] 線程-1 轉賬成功,扣除100,余額: 900
[14:27:31.956] 線程-1 釋放鎖
[14:27:31.970] 線程-2 獲取鎖成功,當前余額: 900
[14:27:33.475] 線程-2 轉賬成功,扣除100,余額: 800
[14:27:33.476] 線程-2 釋放鎖
[14:27:33.490] 線程-3 獲取鎖成功,當前余額: 800
[14:27:34.995] 線程-3 轉賬成功,扣除100,余額: 700
[14:27:34.996] 線程-3 釋放鎖
[14:27:35.010] 線程-4 獲取鎖成功,當前余額: 700
[14:27:36.515] 線程-4 轉賬成功,扣除100,余額: 600
[14:27:36.516] 線程-4 釋放鎖
最終賬戶余額: 600

分析:ZooKeeper鎖提供了強一致性保證,支持阻塞等待,適合對一致性要求極高的場景。

4. 基于Redisson的分布式鎖 - 高并發測試

測試代碼

public class RedissonLockHighConcurrencyTest {private static final String LOCK_KEY = "seckill_lock";private static final AtomicInteger successCount = new AtomicInteger(0);private static final AtomicInteger failCount = new AtomicInteger(0);private static final int TOTAL_STOCK = 10;private static final AtomicInteger currentStock = new AtomicInteger(TOTAL_STOCK);public static void main(String[] args) throws InterruptedException {RedissonDistributedLock redissonLock = new RedissonDistributedLock();// 模擬100個用戶同時秒殺ExecutorService executor = Executors.newFixedThreadPool(20);CountDownLatch latch = new CountDownLatch(100);long startTime = System.currentTimeMillis();for (int i = 0; i < 100; i++) {final int userId = i + 1;executor.submit(() -> {try {seckill(redissonLock, userId);} finally {latch.countDown();}});}latch.await();executor.shutdown();long endTime = System.currentTimeMillis();System.out.println("=== 秒殺結果統計 ===");System.out.println("總耗時: " + (endTime - startTime) + "ms");System.out.println("成功購買: " + successCount.get() + " 人");System.out.println("購買失敗: " + failCount.get() + " 人");System.out.println("剩余庫存: " + currentStock.get());redissonLock.shutdown();}private static void seckill(RedissonDistributedLock redissonLock, int userId) {RLock lock = redissonLock.redissonClient.getLock(LOCK_KEY);try {// 嘗試獲取鎖,最多等待1秒,鎖自動釋放時間為10秒if (lock.tryLock(1, 10, TimeUnit.SECONDS)) {try {if (currentStock.get() > 0) {// 模擬業務處理時間Thread.sleep(50);int remaining = currentStock.decrementAndGet();successCount.incrementAndGet();System.out.println("[" + getCurrentTime() + "] 用戶-" + userId + " 秒殺成功!剩余庫存: " + remaining);} else {failCount.incrementAndGet();System.out.println("[" + getCurrentTime() + "] 用戶-" + userId + " 秒殺失敗,庫存不足");}} finally {lock.unlock();}} else {failCount.incrementAndGet();System.out.println("[" + getCurrentTime() + "] 用戶-" + userId + " 秒殺失敗,獲取鎖超時");}} catch (InterruptedException e) {Thread.currentThread().interrupt();failCount.incrementAndGet();}}private static String getCurrentTime() {return new SimpleDateFormat("HH:mm:ss.SSS").format(new Date());}
}

運行結果輸出(部分)

[14:30:15.123] 用戶-1 秒殺成功!剩余庫存: 9
[14:30:15.180] 用戶-5 秒殺成功!剩余庫存: 8
[14:30:15.235] 用戶-12 秒殺成功!剩余庫存: 7
[14:30:15.290] 用戶-23 秒殺成功!剩余庫存: 6
[14:30:15.345] 用戶-34 秒殺成功!剩余庫存: 5
[14:30:15.400] 用戶-45 秒殺成功!剩余庫存: 4
[14:30:15.455] 用戶-56 秒殺成功!剩余庫存: 3
[14:30:15.510] 用戶-67 秒殺成功!剩余庫存: 2
[14:30:15.565] 用戶-78 秒殺成功!剩余庫存: 1
[14:30:15.620] 用戶-89 秒殺成功!剩余庫存: 0
[14:30:15.625] 用戶-2 秒殺失敗,庫存不足
[14:30:15.626] 用戶-3 秒殺失敗,庫存不足
[14:30:15.627] 用戶-4 秒殺失敗,庫存不足
...
[14:30:16.100] 用戶-95 秒殺失敗,獲取鎖超時
[14:30:16.101] 用戶-96 秒殺失敗,獲取鎖超時
=== 秒殺結果統計 ===
總耗時: 1250ms
成功購買: 10 人
購買失敗: 90 人
剩余庫存: 0

分析:Redisson在高并發場景下表現優異,處理速度快,鎖機制可靠,完全避免了超賣問題。

5. 性能對比測試結果

測試環境

  • CPU: Intel i7-8700K
  • 內存: 16GB DDR4
  • 數據庫: MySQL 8.0
  • Redis: 6.2
  • ZooKeeper: 3.7

并發性能測試結果

鎖類型并發線程數平均響應時間(ms)TPS成功率
數據庫鎖1021504.6100%
Redis鎖1010595.2100%
ZooKeeper鎖1015806.3100%
Redisson鎖1085117.6100%

高并發壓力測試結果

鎖類型并發線程數平均響應時間(ms)TPS成功率
數據庫鎖10085001.285%
Redis鎖10045022.298%
ZooKeeper鎖10032003.195%
Redisson鎖10032031.299%

6. 故障恢復測試

Redis主從切換測試

[14:35:10.100] 節點-1 獲取鎖成功
[14:35:10.150] Redis主節點故障,開始主從切換...
[14:35:10.200] 節點-1 鎖續期失敗,自動釋放鎖
[14:35:10.350] Redis主從切換完成
[14:35:10.400] 節點-2 獲取鎖成功(新主節點)
[14:35:12.450] 節點-2 業務處理完成,釋放鎖

ZooKeeper集群節點故障測試

[14:36:15.100] 線程-1 獲取鎖成功
[14:36:15.200] ZooKeeper節點-2 故障
[14:36:15.250] 集群重新選舉Leader...
[14:36:15.800] 新Leader選舉完成
[14:36:15.850] 線程-1 繼續持有鎖,業務正常進行
[14:36:17.900] 線程-1 釋放鎖
[14:36:17.950] 線程-2 獲取鎖成功

總結

通過多節點/線程的實際測試,我們可以得出以下結論:

  1. 數據庫鎖適合低并發場景,一致性強但性能較差
  2. Redis鎖高性能,適合高并發場景,但需要考慮主從切換
  3. ZooKeeper鎖強一致性,故障恢復能力強,但性能中等
  4. Redisson鎖綜合性能最佳,功能豐富,推薦在生產環境使用

選擇分布式鎖時應該根據具體的業務場景、并發要求和一致性需求來決定。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/921803.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/921803.shtml
英文地址,請注明出處:http://en.pswp.cn/news/921803.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

基于Room+RESTful的雙權限Android開機時間監控方案

概述 以下是使用Kotlin實現的商業級Android開機時間記錄功能&#xff0c;包含現代Android開發最佳實踐。 系統架構 組件設計 // BootReceiver - 接收開機廣播 class BootReceiver : BroadcastReceiver() {override fun onReceive(context: Context, intent: Intent?) {if (int…

水庫大壩安全監測系統的作用

水庫大壩作為重要的水利基礎設施&#xff0c;承擔著防洪、供水、發電、灌溉等多重功能&#xff0c;其安全性直接關系到人民生命財產安全和社會經濟發展。然而&#xff0c;由于自然環境變化、材料老化、荷載作用以及人為因素的影響&#xff0c;大壩在長期運行過程中可能出現裂縫…

《Kubernetes 構建 MySQL MGR 集群實戰教程》

#### 一、前言 MySQL Group Replication (MGR) 是 MySQL 官方提供的高可用集群方案&#xff0c;基于 Paxos 協議實現多節點數據強一致性。本教程將指導如何在 Kubernetes 上部署 MySQL MGR 集群&#xff0c;適用于生產級高可用場景。---#### 二、環境準備 1. **Kubernetes 集…

影視APP源碼 SK影視 安卓+蘋果雙端APP 反編譯詳細視頻教程+源碼

內容目錄一、詳細介紹二、效果展示1.部分代碼2.效果圖展示三、學習資料下載一、詳細介紹 影視APP源碼 SK影視 安卓蘋果雙端APP 反編譯詳細視頻教程源碼 自帶對接優效SDK廣告&#xff08;已失效&#xff09;。域名和IP都可以搭建。 自帶一起看和短劇頁面功能&#xff0c;三種…

pyqt+python之二進制生肖占卜

目錄 一、引言 二、GUI界面設計 1.效果演示 2.相關提示 3.界面設計.py 三、主要程序詳解 1.導入相關模塊 2.初始化設置 3.組內判斷 4.猜測過程 四、總程序代碼 一、引言 在數字時代&#xff0c;傳統文化與編程語言的碰撞總能迸發奇妙火花。本項目以PyQtPython為技術…

人工智能-python-深度學習-經典網絡模型-LeNets5

文章目錄LeNet-5&#xff08;詳解&#xff09;—— 從原理到 PyTorch 實現&#xff08;含訓練示例&#xff09;簡介LeNet-5 的核心思想LeNet-5 逐層結構詳解逐層計算舉例&#x1f4cc; 輸入層&#x1f4cc; C1 卷積層&#x1f4cc; S2 池化層&#x1f4cc; C3 卷積層&#x1f4…

機器視覺的手機柔性屏貼合應用

在智能手機制造領域&#xff0c;柔性屏逐漸成為智能手機的主流選擇&#xff0c;柔性屏因其輕便、易于彎曲的特性&#xff0c;已成為現代電子設備的重要組成部分&#xff0c;但同時也帶來了前所未有的制造挑戰。柔性屏與傳統剛性玻璃屏有本質區別&#xff0c;它容易形變&#xf…

貪心算法應用:數字孿生同步問題詳解

Java中的貪心算法應用&#xff1a;數字孿生同步問題詳解 貪心算法是一種在每一步選擇中都采取在當前狀態下最好或最優&#xff08;即最有利&#xff09;的選擇&#xff0c;從而希望導致結果是全局最好或最優的算法。下面我將全面詳細地講解貪心算法在數字孿生同步問題中的應用。…

UOS20系統安裝與 SSH/XRDP 遠程訪問功能配置指南

UOS20系統安裝與 SSH/XRDP 遠程訪問功能配置指南 一、UOS 20 系統安裝? ?1. 下載系統鏡像? 訪問統信官網下載 UOS 20 專業版鏡像&#xff08;推薦適配當前硬件的版本&#xff09;&#xff1a; https://www.chinauos.com/resource/download-professional 2. 系統安裝與硬件配…

【Python】S1 基礎篇 P5 字典模塊指南

目錄字典的本質與底層實現基礎語法結構使用字典訪問字典中的值添加鍵值對修改字典中的值刪除鍵值對使用 get() 來訪問值遍歷字典遍歷所有鍵值對遍歷字典中的所有鍵遍歷字典中的所有值嵌套字典列表在字典中存儲列表字典&#xff08;Dictionary&#xff09;是Python中靈活且強大的…

計算機視覺之多模板匹配

簡介 計算機視覺第一課opencv&#xff08;四&#xff09;保姆級教學 之前說過模糊匹配只是對于單個目標進行匹配&#xff0c;今天我們就來學習一下如何對多個目標進行匹配 一、多目標匹配 對于這個圖片我們要匹配下面那個箭頭&#xff0c;我們可以發現圖中是有兩個位置相同的…

封裝日期選擇器組件,帶有上周,下周按鈕

ui圖組件代碼如下&#xff1a; <template><div><el-date-pickerv-model"dateRange"type"daterange"align"right"size"mini":editable"false"unlink-panelsrange-separator"至"start-placeholder&q…

基于SpringBoot+MYSQL開發的AI智能大數據醫療診斷平臺

角色&#xff1a; 管理員、醫生、居民 技術&#xff1a; SpringBoot、MyBatis、MySQL、Shiro、Beetl、Swagger、jQuery、Bootstrap 核心功能&#xff1a; 這是一個基于SpringBoot的社區醫療管理平臺&#xff0c;旨在為管理員提供用戶、角色、部門、菜單、日志等系統管理功能&am…

【MFC 小白日記】對話框編輯器里“原型圖像”到底要不要勾?3 分鐘看懂!

摘要&#xff1a;本文解析了MFC中Picture Control的"原型圖像(Prototype Image)"屬性的真實作用。該屬性僅在設計時提供可視化的占位圖預覽&#xff0c;方便UI布局&#xff0c;運行時不會影響程序表現。文章通過對比實驗驗證&#xff0c;勾選后會在對話框編輯器中顯示…

微信開放平臺第三方平臺,可以管理多個微信小程序

大家好&#xff0c;我是小悟。 這個系統可以幫助服務商更好地管理多個商家小程序&#xff0c;無需管理多個商家小程序的賬號密碼或者appId和secret&#xff0c;大大提升效率。 不需要頻繁登錄小程序后臺就能完成上傳代碼、認證、備案、提交代碼審核、發布小程序等操作。 這里錄…

Java全棧學習筆記32

-- drop table t_stu;-- unique 唯一約束的列允許為null-- 如果在之后的操作中。需要某列必須為key&#xff0c;才能做一些操作的情況下。也可以使用唯一約束代替主鍵約束-- create table t_stu(-- studId int,-- tel varchar(11) unique,-- sex varchar(1),-- addr va…

linux升級系統,重啟出現Minimal BASH-like line editingis supported

文章目錄一.問題背景二.解決步驟2.1確認系統分區2.2手動引導2.3 重建grub引導2.4 還原軟件包 一.問題背景 閑來無事&#xff0c;把ubuntu25.04通過sudo do-release-upgrade命令升級到了ubuntu25.10.在升級的過程會出現以下問題 1.自動替換flatpak程序為snap2.請求是否清除舊依賴…

type(類型別名)和 interface的區別和最佳實踐

核心結論在大多數情況下&#xff0c;它們可以互換使用&#xff0c;都能描述對象的結構。它們的區別更多在于設計和擴展能力上。主要區別總結表特性interface (接口)type (類型別名)擴展方式使用 extends 繼承interface A extends B {}使用 & 交叉類型type A B & C合并…

vscode中使用git、githup的基操

一、git提交 配置賬戶密碼 查看用戶信息 git config --global user.name # 查看用戶名 git config --global user.email # 查看郵箱賬戶配置 # 設置全局用戶名和郵箱 git config --global user.name "你的用戶名" git config --global user.email "你的郵箱&q…

jsBridge接入流程

import deviceInfo from ./deviceInfo import { setRefreshToken } from ./token// 設備判斷 const u navigator.userAgent export const isAndroid u.indexOf(Android) > -1 || u.indexOf(Adr) > -1 export const isIOS !!u.match(/\(i[^;];( U;)? CPU.Mac OS X/)…