分布式锁的实现方案
在分布式系统中,分布式锁用于协调多个节点对共享资源的访问。
常见的实现方式包括基于数据库、Redis、ZooKeeper等。
以下是几种具体的实现方案。
基于Redis的分布式锁
Redis因其高性能和原子性操作,成为分布式锁的热门选择。使用SETNX(SET if Not eXists)命令可以实现锁的获取。
public class RedisDistributedLock {
private Jedis jedis;
private String lockKey;
private int expireTime; // 锁的过期时间,防止死锁
public RedisDistributedLock(Jedis jedis, String lockKey, int expireTime) {
this.jedis = jedis;
this.lockKey = lockKey;
this.expireTime = expireTime;
}
public boolean tryLock() {
long result = jedis.setnx(lockKey, "locked");
if (result == 1) {
jedis.expire(lockKey, expireTime);
return true;
}
return false;
}
public void unlock() {
jedis.del(lockKey);
}
}
问题与解决方案
问题1:锁过期但业务未完成
如果锁过期时间设置过短,业务逻辑未完成时锁已释放,可能导致其他线程获取锁并操作共享资源。
解决方案:续租机制
通过后台线程定期检查锁是否仍被持有,并延长锁的过期时间。
public boolean tryLockWithRenewal() {
String threadId = Thread.currentThread().getId() + "";
String result = jedis.set(lockKey, threadId, "NX", "PX", expireTime);
if ("OK".equals(result)) {
// 启动续租线程
new Thread(() -> {
while (true) {
try {
Thread.sleep(expireTime / 3);
if (threadId.equals(jedis.get(lockKey))) {
jedis.expire(lockKey, expireTime);
} else {
break;
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}).start();
return true;
}
return false;
}
问题2:锁误删
线程A释放锁时可能误删线程B的锁(如A因GC暂停导致锁过期后被B获取)。
解决方案:锁持有者验证
在释放锁时验证锁是否仍由当前线程持有。
public void unlockWithValidation() {
String threadId = Thread.currentThread().getId() + "";
if (threadId.equals(jedis.get(lockKey))) {
jedis.del(lockKey);
}
}
基于ZooKeeper的分布式锁
ZooKeeper通过临时顺序节点和Watcher机制实现分布式锁。
public class ZkDistributedLock {
private ZooKeeper zk;
private String lockPath;
private String currentPath;
public ZkDistributedLock(ZooKeeper zk, String lockPath) {
this.zk = zk;
this.lockPath = lockPath;
}
public boolean tryLock() throws KeeperException, InterruptedException {
currentPath = zk.create(lockPath + "/lock_", new byte[0],
ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
List<String> children = zk.getChildren(lockPath, false);
Collections.sort(children);
if (currentPath.equals(lockPath + "/" + children.get(0))) {
return true;
}
return false;
}
public void unlock() throws KeeperException, InterruptedException {
zk.delete(currentPath, -1);
}
}
问题与解决方案
问题1:惊群效应
所有等待锁的节点都会监听前一个节点的删除事件,导致大量通知。
解决方案:顺序监听
每个节点只监听其前一个节点的删除事件。
public boolean tryLockWithWatch() throws KeeperException, InterruptedException {
currentPath = zk.create(lockPath + "/lock_", new byte[0],
ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
List<String> children = zk.getChildren(lockPath, false);
Collections.sort(children);
int index = children.indexOf(currentPath.substring(lockPath.length() + 1));
if (index == 0) {
return true;
} else {
String previousPath = lockPath + "/" + children.get(index - 1);
final CountDownLatch latch = new CountDownLatch(1);
Stat stat = zk.exists(previousPath, event -> {
if (event.getType() == EventType.NodeDeleted) {
latch.countDown();
}
});
if (stat != null) {
latch.await();
}
return true;
}
}
基于数据库的分布式锁
利用数据库的唯一索引或排他锁实现分布式锁。
CREATE TABLE distributed_lock (
lock_name VARCHAR(64) PRIMARY KEY,
owner VARCHAR(64),
expire_time TIMESTAMP
);
乐观锁实现
public boolean tryLockWithOptimistic(String lockName, String owner, int expireSeconds) {
Connection conn = getConnection();
try {
PreparedStatement stmt = conn.prepareStatement(
"INSERT INTO distributed_lock (lock_name, owner, expire_time) VALUES (?, ?, ?)");
stmt.setString(1, lockName);
stmt.setString(2, owner);
stmt.setTimestamp(3, new Timestamp(System.currentTimeMillis() + expireSeconds * 1000));
return stmt.executeUpdate() > 0;
} catch (SQLException e) {
if (e.getErrorCode() == 1062) { // Duplicate entry
return false;
}
throw new RuntimeException(e);
}
}
悲观锁实现
public boolean tryLockWithPessimistic(String lockName, String owner) {
Connection conn = getConnection();
try {
conn.setAutoCommit(false);
PreparedStatement stmt = conn.prepareStatement(
"SELECT * FROM distributed_lock WHERE lock_name = ? FOR UPDATE");
stmt.setString(1, lockName);
ResultSet rs = stmt.executeQuery();
if (rs.next()) {
return false;
} else {
stmt = conn.prepareStatement(
"INSERT INTO distributed_lock (lock_name, owner) VALUES (?, ?)");
stmt.setString(1, lockName);
stmt.setString(2, owner);
stmt.executeUpdate();
conn.commit();
return true;
}
} catch (SQLException e) {
conn.rollback();
throw new RuntimeException(e);
}
}
问题与解决方案
问题1:数据库性能瓶颈
高并发场景下,数据库可能成为性能瓶颈。
解决方案:结合缓存
使用Redis或ZooKeeper作为主要锁实现,数据库作为备份或用于一致性要求极高的场景。
总结
分布式锁的实现需考虑锁的获取、释放、续租及异常处理。Redis适合高性能场景,ZooKeeper适合强一致性场景,数据库适合资源受限或已有数据库依赖的场景。根据具体需求选择合适的方案。