一、分布式锁
1. 为什么需要分布式锁?
在单体应用中,通过synchronized或ReentrantLock等进程内锁即可解决多线程资源竞争问题。但在分布式系统中,多个服务实例运行在不同进程中,传统进程内锁失效,此时需要一种跨进程的互斥机制 ------分布式锁,确保在分布式环境下对共享资源的互斥访问。
2. 分布式锁的核心功能
- 互斥性:同一时刻只能有一个客户端获取锁
- 可重入性:同一个客户端可多次获取同一把锁(需记录持有锁的客户端信息)
- 容错性:锁服务需具备高可用性,避免单点故障
- 锁超时:防止死锁,需设置合理的锁过期时间
- 公平性(可选):保证锁获取顺序(适用于有严格顺序要求的场景)
3. 典型应用场景
- 分布式缓存更新:避免多个节点同时更新缓存导致数据不一致
- 分布式定时任务:确保同一任务只被一个节点执行
- 分布式事务:作为分布式事务的协调机制之一
- 库存扣减:保证高并发下库存的准确扣减
二、核心实现原理
1. 分布式锁的实现模型
所有分布式锁的实现本质上都基于分布式系统中的一致性协议,核心是解决以下问题:
- 如何标识锁(唯一的资源标识符)
- 如何实现互斥(原子性操作保证)
- 如何处理锁超时(防止资源泄露)
- 如何实现锁的释放(安全释放机制)
2. 关键技术点
原子性操作
需要依赖底层中间件提供的原子操作接口,例如:
- Redis 的SETNX(Set If Not Exists)
- ZooKeeper 的节点创建操作
- 数据库的INSERT ...
锁的持有标识
为避免误释放其他客户端的锁,获取锁时需生成唯一的客户端标识(如 UUID),释放锁时需验证标识一致性,而非直接删除锁。
锁续约机制
为防止业务执行时间超过锁过期时间,需实现锁的自动续约(如 Redisson 的看门狗机制),在锁过期前自动延长有效期。
等待队列
对于非公平锁,客户端获取锁失败后直接返回;对于公平锁,需实现等待队列(如 ZooKeeper 的有序节点队列)。
三、主流实现方案
方案一:基于 Redis 的分布式锁
1. 基础实现:SETNX + 过期时间
java
// 伪代码:基于Jedis实现
public boolean tryLock(String lockKey, String clientId, long expireTime) {
// SETNX命令保证原子性,NX表示仅当key不存在时设置
// EX设置过期时间,防止死锁
String result = jedis.set(lockKey, clientId, "NX", "EX", expireTime);
return "OK".equals(result);
}
public boolean releaseLock(String lockKey, String clientId) {
// 检查锁是否属于当前客户端,避免误释放
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
return jedis.eval(script, 1, lockKey, clientId).equals(1L);
}
2. 缺陷与优化
- 竞争条件:锁过期与业务执行完成之间可能被其他客户端获取锁,需通过校验客户端标识 + Lua 脚本原子释放解决
- 集群模式问题:主从模式下存在锁丢失风险(主节点未同步到从节点时宕机),需通过 RedLock 算法(多节点获取锁)提升可靠性
- 推荐工具:Redisson 框架(实现了可重入锁、公平锁、锁续约等完整功能)
3. Redisson 分布式锁示例
java
// 引入依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.17.6</version>
</dependency>
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
RedissonClient redisson = Redisson.create(config);
RLock lock = redisson.getLock("resourceLock");
try {
// 可重入锁,默认过期时间30秒(看门狗机制自动续约)
lock.lock();
// 执行业务逻辑
} finally {
lock.unlock();
}
方案二:基于 ZooKeeper 的分布式锁
1. 实现原理
利用 ZooKeeper 的临时有序节点特性:
- 客户端在/locks节点下创建临时顺序节点(如/locks/lock-)
- 获取/locks下所有子节点,判断自己是否是最小序号的节点
- 若是则获取锁,否则监听前一个节点的删除事件(Watcher 机制)
- 释放锁时删除自己的节点,触发后续节点的监听事件
2. 核心优势
- 高可用性:ZooKeeper 的集群选举机制保证锁服务的可用性
- 公平性:有序节点天然支持公平锁
- 自动释放:临时节点在客户端宕机后自动删除,避免死锁
3. Curator 框架实现示例
java
// 引入依赖
// <dependency>
// <groupId>org.apache.curator</groupId>
// <artifactId>curator-recipes</artifactId>
// <version>5.3.0</version>
// </dependency>
CuratorFramework client = CuratorFrameworkFactory.builder()
.connectString("zk1:2181,zk2:2181,zk3:2181")
.retryPolicy(new ExponentialBackoffRetry(1000, 3))
.build();
client.start();
InterProcessMutex lock = new InterProcessMutex(client, "/distributed_lock");
try {
// 可重入锁,支持超时获取
boolean acquired = lock.acquire(10, TimeUnit.SECONDS);
if (acquired) {
// 执行业务逻辑
}
} catch (Exception e) {
// 处理异常
} finally {
try {
lock.release();
} catch (Exception e) {
// 释放锁异常处理
}
}
方案三:基于数据库的分布式锁
1. 实现方式一:表锁(悲观锁)
创建锁表distributed_lock:
java
CREATE TABLE distributed_lock (
lock_key VARCHAR(64) PRIMARY KEY,
client_id VARCHAR(64),
expire_time BIGINT
);
获取锁:
java
INSERT INTO distributed_lock (lock_key, client_id, expire_time)
VALUES ('resource_1', 'client_A', 1678901234)
释放锁:
java
DELETE FROM distributed_lock
WHERE lock_key='resource_1' AND client_id='client_A';
2. 实现方式二:乐观锁(版本号控制)
通过UPDATE ... WHERE version = ?实现:
java
UPDATE resource_table
SET status='locked', version=version+1
WHERE resource_id=1 AND version=123;
3. 适用场景
- 简单场景或遗留系统兼容
- 不适合高并发场景(数据库成为瓶颈)
4. 缺陷
- 性能问题:依赖数据库 IO,吞吐量有限
- 锁超时处理:需额外的定时任务清理过期锁
- 分布式事务问题:需结合数据库事务保证一致性
四、分布式锁最佳实践
1. 锁粒度控制
遵循最小锁粒度原则,避免长时间持有锁
例如:库存扣减时以商品ID为锁 Key,而非整个库存系统
2. 异常处理
- 获取锁时设置超时时间,避免线程永久阻塞
- 释放锁时必须校验客户端标识,防止误释放
- 业务代码需捕获异常并确保锁释放(使用finally块)
3. 监控与报警
- 监控锁的获取成功率、等待时间等指标
- 对锁超时、锁竞争激烈的场景设置报警
4. 混合方案
对于金融等高可靠性场景,可结合 Redis 和 ZooKeeper 实现双锁机制,提升容错能力。
五、总结与发展趋势
分布式锁是分布式系统中解决资源竞争的核心组件,不同方案各有优劣:
- Redis 方案凭借高性能和简单易用成为首选
- ZooKeeper 方案在需要严格顺序和高可用性时更优
- 数据库方案适用于轻量场景或兼容性需求
随着云原生技术的发展,基于 Kubernetes 的分布式锁(如利用 ETCD)和 Service Mesh 中的分布式锁实现正在成为新的研究方向。开发者需根据具体业务场景(并发量、可靠性要求、技术栈)选择合适的方案,并结合框架(如 Redisson、Curator)简化开发成本。