分布式锁
一、是什么?
分布式锁是分布式系统中协调多个节点对共享资源进行互斥访问的机制,确保同一时刻只有一个节点能执行关键操作(如修改数据)。核心目标是解决分布式环境下的并发控制问题。
二、解决什么问题
- 资源竞争:防止多个节点同时修改共享资源(如数据库、文件)。
- 数据一致性:避免并发操作导致的数据错误(如超卖、重复扣款)。
- 幂等性保障:确保重复请求仅生效一次。
实现方案一:基于数据库
一、是什么?
利用数据库的唯一约束或乐观锁实现分布式锁,如MySQL的唯一索引或版本号机制。
二、解决什么问题
适用于简单场景,无需引入额外中间件,成本低。
三、核心方法
- 唯一索引锁:创建锁表,利用唯一索引插入记录作为加锁。
- 乐观锁:通过版本号字段控制更新(
UPDATE ... WHERE version=old_version
)。
四、应用场景
- 低并发系统(如小型电商库存扣减)。
- 数据库已作为核心组件的场景。
五、Java示例
java
// 基于MySQL唯一索引的锁
public boolean tryLock(String lockName) {
try (Connection conn = dataSource.getConnection()) {
String sql = "INSERT INTO distributed_lock(lock_key) VALUES (?)";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1, lockName);
return ps.executeUpdate() == 1; // 插入成功即加锁
} catch (SQLException e) {
// 唯一索引冲突说明锁已被占用
return false;
}
}
public void unlock(String lockName) {
// 删除锁记录
String sql = "DELETE FROM distributed_lock WHERE lock_key = ?";
// ...执行删除
}
六、重要注意事项
- 性能差(高频操作易致数据库压力大)。
- 需处理死锁(如超时删除)。
- 非阻塞锁需自旋重试。
实现方案二:基于Redis
一、是什么?
利用Redis的原子操作(如SETNX
)实现分布式锁,结合Lua脚本保证原子性。
二、解决什么问题
高性能需求场景,支持高并发和自动过期防死锁。
三、核心方法(Redis命令)
- 加锁:
SET lock_key unique_value NX PX 30000
(原子性设置键值+过期时间)。 - 解锁:Lua脚本校验值后删除(避免误删其他节点锁)。
- Redisson框架:Java客户端提供可重入锁、看门狗自动续期。
四、应用场景
- 高频读写的分布式缓存(如秒杀系统)。
- 需高吞吐和低延迟的场景。
五、Java示例(Redisson)
java
// 使用Redisson(JDK8+)
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
RedissonClient redisson = Redisson.create(config);
RLock lock = redisson.getLock("order_lock");
try {
// 尝试加锁,超时时间30s,自动续期
if (lock.tryLock(10, 30, TimeUnit.SECONDS)) {
// 执行业务逻辑
}
} finally {
lock.unlock(); // 确保解锁
}
六、重要注意事项
- 需确保Redis高可用(集群或哨兵)。
- 锁过期时间需大于业务执行时间(避免提前释放)。
- Redisson看门狗机制可自动续期锁。
实现方案三:基于ZooKeeper
一、是什么?
利用ZooKeeper的临时有序节点(Ephemeral Sequential Node)和Watcher机制实现锁。
二、解决什么问题
强一致性需求场景,避免Redis锁的脑裂问题。
三、核心方法
- 加锁:创建临时有序节点,检查是否为最小节点(是则获锁)。
- 解锁:删除当前节点,触发后续节点监听。
- Curator框架:封装
InterProcessMutex
简化实现。
四、应用场景
- 金融交易等强一致性系统。
- 需公平锁的场景(节点按顺序获锁)。
五、Java示例(Curator)
java
CuratorFramework client = CuratorFrameworkFactory.newClient("localhost:2181", new ExponentialBackoffRetry(1000, 3));
client.start();
InterProcessMutex lock = new InterProcessMutex(client, "/order_lock");
try {
if (lock.acquire(10, TimeUnit.SECONDS)) {
// 执行业务逻辑
}
} finally {
lock.release(); // 确保释放锁
}
六、重要注意事项
- ZooKeeper性能低于Redis(适合低频高可靠场景)。
- 会话超时需合理设置(避免临时节点意外删除)。
- 避免羊群效应(Curator优化了Watcher通知机制)。
方案区别对比
特性 | 数据库锁 | Redis锁 | ZooKeeper锁 |
---|---|---|---|
性能 | 低(依赖IO) | 高(内存操作) | 中(网络通信) |
一致性 | 强(依赖DB事务) | 弱(异步复制) | 强(ZAB协议) |
实现复杂度 | 简单 | 中等(需处理原子性) | 复杂(需维护会话) |
死锁处理 | 超时删除 | 自动过期 | 会话结束自动删除节点 |
适用场景 | 低并发、简单系统 | 高并发、容忍最终一致性 | 强一致性、公平锁 |
总结
- 数据库锁:简单但性能差,仅适用于小型系统。
- Redis锁:高性能首选,推荐使用Redisson(支持JDK8+),需注意脑裂风险。
- ZooKeeper锁:强一致性保障,适合金融级场景,但复杂度高。
选型建议:
- 优先Redis(90%场景满足)。
- 强一致性需求选ZooKeeper。
- 避免直接使用数据库锁(除非无其他中间件)。
JDK8+最佳实践:结合Redisson或Curator框架,简化开发并利用新特性(如CompletableFuture异步处理)。