在现代分布式系统中,多进程或多线程环境下的资源竞争问题尤为突出。为了确保数据的一致性和安全性,分布式锁应运而生。下面介绍三种常用的实现分布式锁的方法,分别是基于数据库的锁、基于Redis的锁以及基于Zookeeper的锁。
一、基于数据库的锁
在传统的关系型数据库中,我们可以使用表来实现分布式锁。具体做法是,创建一张锁表,然后通过事务机制来实现加锁和解锁操作。
-- 创建锁表
CREATE TABLE distributed_lock (
lock_name VARCHAR(255) PRIMARY KEY,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
Java代码示例:
public class DatabaseLock {
private DataSource dataSource;
public DatabaseLock(DataSource dataSource) {
this.dataSource = dataSource;
}
public boolean acquireLock(String lockName) {
try (Connection conn = dataSource.getConnection()) {
conn.setAutoCommit(false);
PreparedStatement ps = conn.prepareStatement("INSERT INTO distributed_lock (lock_name) VALUES (?)");
ps.setString(1, lockName);
int rowsAffected = ps.executeUpdate();
if (rowsAffected > 0) {
conn.commit();
return true; // 成功获取锁
}
conn.rollback();
return false; // 锁已被占用
} catch (SQLException e) {
e.printStackTrace();
return false;
}
}
public void releaseLock(String lockName) {
try (Connection conn = dataSource.getConnection()) {
PreparedStatement ps = conn.prepareStatement("DELETE FROM distributed_lock WHERE lock_name = ?");
ps.setString(1, lockName);
ps.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
二、基于Redis的分布式锁
Redis提供了SETNX命令,非常适合用来实现分布式锁。使用SETNX可以在键不存在时设置键值并返回成功,保证了原子性。
import redis.clients.jedis.Jedis;
public class RedisLock {
private Jedis jedis;
private static final String LOCK_SUCCESS = "OK";
private static final Long RELEASE_SUCCESS = 1L;
public RedisLock(Jedis jedis) {
this.jedis = jedis;
}
public boolean acquireLock(String lockKey, String lockValue, int expireTime) {
String result = jedis.set(lockKey, lockValue, "NX", "EX", expireTime);
return LOCK_SUCCESS.equals(result);
}
public boolean releaseLock(String lockKey, String lockValue) {
// Lua脚本原子删除
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, Collections.singletonList(lockKey), Collections.singletonList(lockValue)).equals(1L);
}
}
三、基于Zookeeper的锁
Zookeeper也非常适合用来实现分布式锁,利用它的顺序节点和临时节点特性,能够构建一个可靠的分布式锁机制。
Java代码示例:
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.ACL;
import java.util.Collections;
import java.util.List;
public class ZkLock implements Watcher {
private ZooKeeper zooKeeper;
private String lockRoot = "/locks";
private String lockName;
private String myZnode;
public ZkLock(ZooKeeper zooKeeper, String lockName) {
this.zooKeeper = zooKeeper;
this.lockName = lockName;
}
public boolean acquireLock() throws Exception {
if (zooKeeper.exists(lockRoot, false) == null) {
zooKeeper.create(lockRoot, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
myZnode = zooKeeper.create(lockRoot + "/" + lockName, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
return checkLock();
}
private boolean checkLock() throws Exception {
List<String> children = zooKeeper.getChildren(lockRoot, false);
Collections.sort(children);
if (myZnode.equals(lockRoot + "/" + children.get(0))) {
return true; // 成功获得锁
}
return false; // 锁被其他节点持有
}
public void releaseLock() throws Exception {
zooKeeper.delete(myZnode, -1);
}
@Override
public void process(WatchedEvent event) {
// 处理 Zookeeper 事件
}
}
总结
以上三种方式各有优缺点。基于数据库的锁简单易用,但可能存在性能瓶颈;Redis锁高效且能很好地支持高并发,但需要外部的Redis服务;Zookeeper能够提供高可靠的分布式锁支持,但其复杂性和维护成本相对较高。选择使用哪种方式,需要根据具体的应用场景和需求进行权衡。