资讯中心

架构师必备:分布式锁方案选型(含代码实战)

📅 2026/7/28 19:13:53
架构师必备:分布式锁方案选型(含代码实战)
1. 引言在单体应用时代Java 的synchronized和ReentrantLock足以应对大部分并发场景。但当系统拆分为微服务架构后多个进程、多台机器需要协调访问共享资源时分布式锁便成为架构师必须掌握的核心技术。本文将从原理出发对比主流分布式锁方案Redis、ZooKeeper、etcd、数据库并给出可直接运行的代码实战帮助你在真实项目中做出合理选型。2. 分布式锁的核心要求一个合格的分布式锁应满足以下条件互斥性同一时刻只有一个客户端持有锁。防死锁持有锁的客户端崩溃后锁能自动释放。可重入性可选同一客户端可重复获取同一把锁。高性能加锁/解锁延迟低吞吐量高。高可用锁服务本身不成为单点瓶颈。3. 方案一基于 Redis 的分布式锁3.1 基本原理利用 Redis 的SET NX EX原子命令实现加锁Lua 脚本保证解锁的原子性。3.2 代码实战Redisson 实现Redisson 是官方推荐的 Java 客户端内置了可重入锁、公平锁、红锁等实现。!-- pom.xml 依赖 -- dependency groupIdorg.redisson/groupId artifactIdredisson-spring-boot-starter/artifactId version3.27.0/version /dependencyimport 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 RedisDistributedLock { private final RedissonClient redissonClient; public RedisDistributedLock() { Config config new Config(); config.useSingleServer() .setAddress(redis://127.0.0.1:6379); this.redissonClient Redisson.create(config); } /** 模拟扣减库存 */ public void deductStock(String productId, int quantity) { String lockKey lock:stock: productId; RLock lock redissonClient.getLock(lockKey); try { // 尝试加锁最多等待 5 秒锁有效期 30 秒 boolean locked lock.tryLock(5, 30, TimeUnit.SECONDS); if (!locked) { System.out.println(获取锁失败请稍后重试); return; } // 业务逻辑扣减库存 System.out.println(成功扣减库存 quantity 剩余库存...); Thread.sleep(100); // 模拟业务耗时 } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { // 释放锁Redisson 自动处理 Lua 脚本 lock.unlock(); } } }3.3 优缺点分析优点性能极高纯内存操作Redisson 封装完善支持看门狗自动续期。缺点主从切换时可能出现锁丢失Redis 异步复制导致需要引入 RedLock 或使用 Redis Cluster 提升可靠性。4. 方案二基于 ZooKeeper 的分布式锁4.1 基本原理利用 ZooKeeper 的临时顺序节点Watcher 机制实现。客户端创建临时顺序节点序号最小的节点获得锁其他节点监听前一个节点的删除事件。4.2 代码实战Curator 实现Apache Curator 是 ZooKeeper 的顶级 Java 客户端内置了InterProcessMutex可重入锁。dependency groupIdorg.apache.curator/groupId artifactIdcurator-recipes/artifactId version5.6.0/version /dependencyimport 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 ZkDistributedLock { private final CuratorFramework client; private static final String LOCK_PATH /locks/stock; public ZkDistributedLock() { this.client CuratorFrameworkFactory.builder() .connectString(127.0.0.1:2181) .sessionTimeoutMs(60000) .connectionTimeoutMs(15000) .retryPolicy(new ExponentialBackoffRetry(1000, 3)) .build(); client.start(); } public void deductStock(String productId, int quantity) throws Exception { InterProcessMutex lock new InterProcessMutex(client, LOCK_PATH / productId); if (lock.acquire(5, TimeUnit.SECONDS)) { try { System.out.println(获取 ZK 锁成功扣减库存 quantity); Thread.sleep(100); } finally { lock.release(); } } else { System.out.println(获取 ZK 锁超时); } } }4.3 优缺点分析优点强一致性ZAB 协议临时节点自动释放无锁丢失风险。缺点性能低于 Redis磁盘写入 选举开销频繁加解锁可能产生羊群效应。5. 方案三基于 etcd 的分布式锁5.1 基本原理etcd 使用 Raft 协议保证强一致性通过Lease租约Revision版本号实现分布式锁。客户端创建 key 时绑定租约租约到期自动删除 key防止死锁。5.2 代码实战Jetcd 实现dependency groupIdio.etcd/groupId artifactIdjetcd-core/artifactId version0.7.7/version /dependencyimport io.etcd.jetcd.*; 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 java.nio.charset.StandardCharsets; import java.util.concurrent.CompletableFuture; public class EtcdDistributedLock { private final Client client; private final KV kvClient; private final Lease leaseClient; public EtcdDistributedLock() { this.client Client.builder().endpoints(http://127.0.0.1:2379).build(); this.kvClient client.getKVClient(); this.leaseClient client.getLeaseClient(); } public boolean tryLock(String lockKey, long ttlSeconds) throws Exception { ByteSequence key ByteSequence.from(lockKey, StandardCharsets.UTF_8); ByteSequence value ByteSequence.from(locked, StandardCharsets.UTF_8); // 创建租约 long leaseId leaseClient.grant(ttlSeconds).get().getID(); // 事务如果 key 不存在则创建否则失败 CompletableFutureamp;lt;TxnResponseamp;gt; txnFuture kvClient.txn() .If(new Cmp(key, Cmp.Op.EQUAL, CmpTarget.createRevision(0))) .Then(Op.put(key, value, leaseId)) .Else() .commit(); TxnResponse response txnFuture.get(); return response.isSucceeded(); } public void unlock(String lockKey) throws Exception { ByteSequence key ByteSequence.from(lockKey, StandardCharsets.UTF_8); kvClient.delete(key).get(); } }5.3 优缺点分析优点强一致性天然支持租约续期gRPC 协议性能优秀云原生生态首选。缺点社区生态不如 Redis 成熟Java 客户端Jetcd文档较少。6. 方案四基于数据库的分布式锁6.1 基本原理利用数据库唯一索引或SELECT ... FOR UPDATE实现互斥。常见做法是创建一张锁表通过插入/删除记录模拟加锁/解锁。6.2 代码实战MySQL 实现CREATE TABLE distributed_lock ( lock_key VARCHAR(128) NOT NULL COMMENT 锁标识, owner_id VARCHAR(64) NOT NULL COMMENT 持有者标识, expire_at DATETIME NOT NULL COMMENT 过期时间, create_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (lock_key) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;import javax.sql.DataSource; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.time.LocalDateTime; public class DbDistributedLock { private final DataSource dataSource; public DbDistributedLock(DataSource dataSource) { this.dataSource dataSource; } public boolean tryLock(String lockKey, String ownerId, int ttlSeconds) { String sql INSERT INTO distributed_lock (lock_key, owner_id, expire_at) VALUES (?, ?, ?); try (Connection conn dataSource.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, lockKey); ps.setString(2, ownerId); ps.setObject(3, LocalDateTime.now().plusSeconds(ttlSeconds)); return ps.executeUpdate() gt; 0; } catch (Exception e) { // 唯一键冲突表示锁已被占用 return false; } } public boolean unlock(String lockKey, String ownerId) { String sql DELETE FROM distributed_lock WHERE lock_key ? AND owner_id ?; try (Connection conn dataSource.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, lockKey); ps.setString(2, ownerId); return ps.executeUpdate() gt; 0; } catch (Exception e) { return false; } } }6.3 优缺点分析优点无需引入额外中间件利用现有数据库即可实现。缺点性能最差磁盘 IO 行锁竞争数据库单点风险需自行处理锁过期清理。7. 方案对比与选型建议维度RedisZooKeeperetcd数据库一致性最终一致性强一致性强一致性强一致性性能极高中等高低自动续期支持看门狗支持临时节点支持Lease需自行实现锁丢失风险主从切换时存在无无无运维复杂度低中中低社区生态非常成熟成熟快速发展无专用生态选型建议高并发、允许短暂不一致首选 Redis Redisson性能最优。强一致性、金融级场景选择 ZooKeeper 或 etcd避免锁丢失。云原生架构、Kubernetes 环境etcd 是最佳选择天然集成。中小团队、已有 MySQL 集群数据库锁可作为快速实现方案但不要用于核心高并发链路。8. 总结分布式锁没有银弹选型需要结合业务场景、一致性要求、团队技术栈和运维能力综合判断。本文给出了四种主流方案的完整代码示例你可以直接复制到项目中验证。建议先通过压测对比各方案在真实业务下的表现再做最终决策。如果你正在设计新的微服务系统推荐优先考虑Redis Redisson或etcd Jetcd两者在性能和可靠性之间取得了较好的平衡。