在 Java 分布式系统中,分布式锁是解决跨进程、跨服务器共享资源并发竞争的核心方案(例如秒杀库存扣减、分布式任务调度、避免重复提交等场景)。与单机锁(synchronized、ReentrantLock)不同,分布式锁需要协调多个节点的状态,确保同一时刻只有一个节点能获取锁。
一、分布式锁的核心要求
一个可靠的分布式锁必须满足以下特性:
- 互斥性:同一时刻只有一个客户端能持有锁;
- 安全性:锁只能被持有它的客户端释放,防止误释放他人的锁;
- 可用性:分布式环境下,锁服务不能单点故障(需高可用);
- 原子性:锁的获取 / 释放操作必须是原子的,避免网络延迟导致的状态不一致;
- 可重入性(可选):同一客户端可重复获取同一把锁(避免死锁);
- 超时释放(可选):防止客户端崩溃后锁一直占用(避免死锁)。
二、Java 中分布式锁的实现方案
常用的实现方案有 4 种,各有优劣,需根据场景选择:
方案 1:基于 Redis 的分布式锁(最常用)
Redis 因高性能、高可用(主从 + 哨兵 / 集群),是分布式锁的首选方案。核心利用 Redis 的 原子命令(SETNX、EX、Lua 脚本)保证锁的原子性。
1. 核心原理
- 获取锁:用
SET key value NX EX timeout命令(原子操作):NX:仅当 key 不存在时才设置(保证互斥);EX timeout:自动过期(避免死锁);value:用 UUID + 线程 ID 标识客户端(保证安全性,仅能释放自己的锁)。
- 释放锁:用 Lua 脚本原子性校验 value 并删除 key(避免误删他人锁)。
- 可重入性:需额外存储锁的持有次数(例如 Redis Hash 结构)。
- 高可用:Redis 集群(主从 + 哨兵),避免单点故障。
2. 手动实现(简化版)
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
public class RedisDistributedLock {
private final StringRedisTemplate redisTemplate;
private final String lockKey; // 锁的唯一标识(例如 "stock_lock_1001")
private final long expireTime; // 锁超时时间(秒)
private String lockValue; // 当前客户端的锁值(UUID+线程ID)
// 释放锁的Lua脚本(原子校验+删除)
private static final String UNLOCK_SCRIPT = """
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
else
return 0
end
""";
public RedisDistributedLock(StringRedisTemplate redisTemplate, String lockKey, long expireTime) {
this.redisTemplate = redisTemplate;
this.lockKey = lockKey;
this.expireTime = expireTime;
this.lockValue = UUID.randomUUID().toString() + "-" + Thread.currentThread().getId();
}
// 获取锁
public boolean tryLock() {
// SET key value NX EX expireTime:原子操作
Boolean success = redisTemplate.opsForValue().setIfAbsent(
lockKey, lockValue, expireTime, TimeUnit.SECONDS
);
return Boolean.TRUE.equals(success);
}
// 释放锁
public boolean unlock() {
DefaultRedisScript<Long> script = new DefaultRedisScript<>(UNLOCK_SCRIPT, Long.class);
Long result = redisTemplate.execute(
script,
Collections.singletonList(lockKey), // KEYS[1]
lockValue // ARGV[1]
);
return result != null && result > 0;
}
}
3. 成熟框架:Redisson(推荐)
手动实现需处理可重入、锁续期、集群高可用等细节,推荐使用 Redisson(Redis 官方推荐的 Java 客户端),它封装了完整的分布式锁实现:
- 支持可重入锁(
RLock)、公平锁、联锁、红锁等; - 自动实现锁续期(避免锁超时释放);
- 适配 Redis 单机、主从、哨兵、集群等部署模式。
Redisson 示例代码:
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
public class RedissonLockDemo {
public static void main(String[] args) {
// 1. 配置 Redisson 客户端
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
RedissonClient redissonClient = Redisson.create(config);
// 2. 获取分布式锁(锁的唯一标识)
RLock lock = redissonClient.getLock("stock_lock_1001");
try {
// 3. 尝试获取锁:等待10秒,10秒内未获取则失败;锁自动过期30秒
boolean locked = lock.tryLock(10, 30, TimeUnit.SECONDS);
if (locked) {
// 4. 执行业务逻辑(例如扣减库存)
System.out.println("获取锁成功,执行核心业务...");
} else {
System.out.println("获取锁失败,当前锁被占用");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
// 5. 释放锁(仅持有锁的客户端能释放)
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
// 6. 关闭客户端
redissonClient.shutdown();
}
}
4. 优缺点
- 优点:高性能(Redis 单机 QPS 万级)、部署简单、支持多种锁类型;
- 缺点:Redis 集群下可能出现 “主从切换丢失锁”(主节点锁未同步到从节点就宕机),需用红锁(RedLock)缓解(但红锁性能略低)。
方案 2:基于 ZooKeeper 的分布式锁
ZooKeeper 是分布式协调工具,基于 临时有序节点 实现分布式锁,天然支持高可用和公平锁。
1. 核心原理
- 客户端在 ZooKeeper 的
/lock节点下创建 临时有序子节点(例如/lock/lock-xxx-00000001); - 客户端获取
/lock下所有子节点,判断自己的节点是否是最小的:- 是:获取锁成功;
- 否:监听前一个节点(Watcher 机制),前一个节点删除时触发通知,再次判断是否为最小节点;
- 释放锁:客户端断开连接时,临时节点自动删除(天然支持超时释放),或主动删除节点。
2. 成熟框架:Curator
ZooKeeper 原生 API 复杂,推荐使用 Curator 框架(Apache 官方维护),它封装了分布式锁实现:
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 ZkDistributedLockDemo {
public static void main(String[] args) {
// 1. 配置 Curator 客户端(连接 ZooKeeper 集群)
CuratorFramework client = CuratorFrameworkFactory.builder()
.connectString("127.0.0.1:2181") // ZooKeeper 地址(集群用逗号分隔)
.retryPolicy(new ExponentialBackoffRetry(1000, 3)) // 重试策略
.build();
client.start();
// 2. 创建分布式锁(锁的路径)
InterProcessMutex lock = new InterProcessMutex(client, "/distributed/lock/stock_1001");
try {
// 3. 尝试获取锁:等待5秒,超时未获取则失败
boolean locked = lock.acquire(5, TimeUnit.SECONDS);
if (locked) {
// 4. 执行业务逻辑
System.out.println("获取锁成功,执行核心业务...");
} else {
System.out.println("获取锁失败");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 5. 释放锁
if (lock.isAcquiredInThisProcess()) {
try {
lock.release();
} catch (Exception e) {
e.printStackTrace();
}
}
// 6. 关闭客户端
client.close();
}
}
}
3. 优缺点
- 优点:天然高可用(ZooKeeper 集群)、支持公平锁、无锁丢失问题(临时节点依赖会话);
- 缺点:性能低于 Redis(ZooKeeper 写操作需集群同步,QPS 千级)、部署复杂。
方案 3:基于数据库的分布式锁
利用数据库的 唯一索引 或 悲观锁 / 乐观锁 实现,适合对性能要求不高、已有数据库的场景。
1. 实现方式 1:唯一索引(互斥锁)
- 建表:创建锁表,
lock_key设为唯一索引; - 获取锁:插入一条
lock_key = 目标锁标识的记录(唯一索引保证仅一条成功); - 释放锁:删除该记录;
- 超时释放:给表加
expire_time字段,定时任务删除过期记录。
表结构示例:
CREATE TABLE distributed_lock (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
lock_key VARCHAR(64) NOT NULL COMMENT '锁的唯一标识',
lock_value VARCHAR(128) NOT NULL COMMENT '客户端标识(UUID+线程ID)',
expire_time DATETIME NOT NULL COMMENT '过期时间',
UNIQUE KEY uk_lock_key (lock_key)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
Java 代码示例(简化):
import org.springframework.jdbc.core.JdbcTemplate;
import java.util.UUID;
import java.util.Date;
public class DbDistributedLock {
private final JdbcTemplate jdbcTemplate;
private final String lockKey;
private final String lockValue;
private final long expireSeconds; // 过期时间(秒)
public DbDistributedLock(JdbcTemplate jdbcTemplate, String lockKey, long expireSeconds) {
this.jdbcTemplate = jdbcTemplate;
this.lockKey = lockKey;
this.lockValue = UUID.randomUUID().toString() + "-" + Thread.currentThread().getId();
this.expireSeconds = expireSeconds;
}
// 获取锁
public boolean tryLock() {
Date expireTime = new Date(System.currentTimeMillis() + expireSeconds * 1000);
try {
String sql = "INSERT INTO distributed_lock (lock_key, lock_value, expire_time) VALUES (?, ?, ?)";
jdbcTemplate.update(sql, lockKey, lockValue, expireTime);
return true; // 插入成功=获取锁
} catch (Exception e) {
return false; // 唯一索引冲突=锁被占用
}
}
// 释放锁
public boolean unlock() {
String sql = "DELETE FROM distributed_lock WHERE lock_key = ? AND lock_value = ?";
int rows = jdbcTemplate.update(sql, lockKey, lockValue);
return rows > 0;
}
}
2. 实现方式 2:悲观锁(SELECT FOR UPDATE)
- 利用数据库的行级锁,通过
SELECT ... FOR UPDATE锁定目标记录,其他事务需等待锁释放。 - 缺点:性能差,容易导致死锁,不推荐高并发场景。
3. 优缺点
- 优点:实现简单、无需额外中间件;
- 缺点:性能低(数据库 IO 瓶颈)、容易产生死锁、不支持高并发。
方案 4:基于 Etcd 的分布式锁
Etcd 是云原生场景下的分布式键值存储(类似 ZooKeeper),基于 Raft 协议 保证一致性,支持原子操作和租约机制。
核心原理
- 获取锁:用
PUT /lock/key value --lease=leaseId原子操作(租约保证超时释放),同时监听前序节点; - 释放锁:删除键或租约过期;
- 高可用:Raft 协议保证集群数据一致性。
Java 客户端:jetcd
import io.etcd.jetcd.*;
import io.etcd.jetcd.lock.LockResponse;
import io.etcd.jetcd.lock.UnlockResponse;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
public class EtcdDistributedLockDemo {
public static void main(String[] args) throws Exception {
// 1. 创建 Etcd 客户端
Client client = Client.builder().endpoints("http://127.0.0.1:2379").build();
Lock lockClient = client.getLockClient();
// 2. 锁的标识和租约(租约10秒,超时自动释放)
ByteSequence lockKey = ByteSequence.from("stock_lock_1001", StandardCharsets.UTF_8);
long leaseId = client.getLeaseClient().grant(10).get().getID();
try {
// 3. 获取锁(异步转同步)
CompletableFuture<LockResponse> lockFuture = lockClient.lock(lockKey, leaseId);
LockResponse lockResponse = lockFuture.get(5, TimeUnit.SECONDS); // 等待5秒
// 4. 执行业务逻辑
System.out.println("获取锁成功,锁标识:" + lockResponse.getKey());
} catch (Exception e) {
System.out.println("获取锁失败:" + e.getMessage());
} finally {
// 5. 释放锁
CompletableFuture<UnlockResponse> unlockFuture = lockClient.unlock(lockKey);
unlockFuture.get();
// 6. 关闭客户端
client.close();
}
}
}
优缺点
- 优点:强一致性(Raft 协议)、云原生场景适配好、支持租约机制;
- 缺点:部署复杂、Java 生态不如 Redis/ZooKeeper 成熟。
三、方案对比与选型建议
| 方案 | 性能 | 高可用 | 实现复杂度 | 适用场景 |
|---|---|---|---|---|
| Redis(Redisson) | 高(万级 QPS) | 支持(集群) | 低(框架封装) | 大多数分布式场景(秒杀、库存) |
| ZooKeeper(Curator) | 中(千级 QPS) | 支持(集群) | 中(框架封装) | 对公平锁、一致性要求高的场景 |
| 数据库 | 低(百级 QPS) | 支持(主从) | 低(简单 SQL) | 低并发、已有数据库的场景 |
| Etcd | 中(千级 QPS) | 支持(集群) | 高(部署复杂) | 云原生、K8s 生态场景 |
选型优先级
- 优先选 Redis(Redisson):平衡性能、易用性和高可用,覆盖绝大多数场景;
- 需公平锁 / 强一致性选 ZooKeeper(Curator):例如分布式任务调度;
- 低并发 / 无中间件选数据库:例如内部系统的简单并发控制;
- 云原生场景选 Etcd:例如 K8s 集群内的服务协调。
四、注意事项
- 锁的粒度:锁的 key 要精准(例如 “stock_lock_1001” 而非 “stock_lock”),避免大面积并发阻塞;
- 超时设置:锁超时时间需大于业务执行时间,避免业务未完成锁已释放;
- 锁续期:长耗时业务需手动续期(Redisson 自动支持),防止锁超时;
- 异常处理:必须在
finally中释放锁,避免异常导致锁泄漏; - 集群部署:Redis/ZooKeeper/Etcd 必须集群部署,避免单点故障。
Java分布式锁技术深度解析

2977

被折叠的 条评论
为什么被折叠?



