目录
分布式锁是用于在分布式系统中实现资源的并发访问控制,以确保同一时刻只有一个客户端能够访问共享资源。下面是几种常见的分布式锁的实现方式及详解:
1.基于数据库实现分布式锁
基于数据库实现分布式锁是一种可行的方式,但通常不是最佳选择,因为数据库的性能和可伸缩性可能无法满足高并发的需求。然而,如果你仍然需要使用数据库实现分布式锁,以下是一个简单的基于数据库实现分布式锁的示例(以MySQL为例):
首先,我们可以创建一张数据库表来存储分布式锁信息,如下所示:
CREATE TABLE distributed_lock (
id INT AUTO_INCREMENT PRIMARY KEY,
lock_key VARCHAR(128) NOT NULL,
lock_holder VARCHAR(128) NOT NULL,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
接下来,我们可以使用数据库的事务来申请和释放锁,并结合唯一索引来确保同一时刻只有一个客户端能够成功申请锁。以下是一个简单的Java示例代码:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class DatabaseDistributedLock {
private static final String DB_URL = "jdbc:mysql://localhost:3306/your_database";
private static final String DB_USER = "your_username";
private static final String DB_PASSWORD = "your_password";
private Connection connection;
public DatabaseDistributedLock() throws SQLException {
connection = DriverManager.getConnection(DB_URL, DB_USER, DB_PASSWORD);
}
public boolean acquireLock(String lockKey, String lockHolder, long timeout) {
String sql = "INSERT INTO distributed_lock (lock_key, lock_holder) VALUES (?, ?)";
try {
connection.setAutoCommit(false);
PreparedStatement statement = connection.prepareStatement(sql);
statement.setString(1, lockKey);
statement.setString(2, lockHolder);
int rowsAffected = statement.executeUpdate();
connection.commit();
connection.setAutoCommit(true);
return rowsAffected > 0; // 如果插入成功,则表示获取锁成功
} catch (SQLException e) {
e.printStackTrace();
try {
connection.rollback();
connection.setAutoCommit(true);
} catch (SQLException ex) {
ex.printStackTrace();
}
}
return false; // 获取锁失败
}
public void releaseLock(String lockKey) {
String sql = "DELETE FROM distributed_lock WHERE lock_key = ?";
try {
PreparedStatement statement = connection.prepareStatement(sql);
statement.setString(1, lockKey);
statement.execute(); // 释放锁
} catch (SQLException e) {
e.printStackTrace();
}
}
}
在上面的示例中,我们使用数据库的唯一索引和事务来尝试插入一条记录,以实现获取分布式锁。在释放锁的时候,我们直接删除对应的记录即可。
2.基于redis的互斥命令实现分布式锁
基于Redis的互斥命令实现分布式锁是一种常见且高效的方式。Redis提供了一些原子性的操作,如SETNX(SET if Not eXists)和EXPIRE命令,这些命令可以很好地支持分布式锁的实现。
下面是一个简单的基于Redis的分布式锁的示例(使用Java的Jedis客户端):
import redis.clients.jedis.Jedis;
public class RedisDistributedLock {
private static final String LOCK_KEY = "my_lock"; // 锁的键名
private static final int LOCK_EXPIRE = 30000; // 锁的过期时间(毫秒)
private Jedis jedis;
public RedisDistributedLock() {
jedis = new Jedis("localhost"); // 初始化Redis连接
}
public boolean acquireLock() {
String lockValue = String.valueOf(System.currentTimeMillis() + LOCK_EXPIRE + 1); // 为锁生成一个唯一的值
String result = jedis.set(LOCK_KEY, lockValue, "NX", "PX", LOCK_EXPIRE);
return "OK".equals(result); // 如果返回OK,则获取锁成功
}
public void releaseLock() {
String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
jedis.eval(luaScript, 1, LOCK_KEY, jedis.get(LOCK_KEY));
}
}
在上述示例中,acquireLock方法使用了Redis的SET命令,其中"NX"表示只在键不存在时设置成功,"PX"表示设置键的过期时间。如果SET命令返回"OK",则表示获取锁成功。
在releaseLock方法中,使用Lua脚本来实现原子性释放锁的操作,首先通过GET命令获取锁的当前值,然后比较锁的当前值是否与之前获取的锁值相等,如果相等则删除键,即释放锁。
3.基于zookeeper节点的有序性实现分布式锁
基于ZooKeeper的分布式锁 ZooKeeper是一个分布式协调服务,可以用于实现分布式锁。通过创建一个有序临时节点,并判断自身是否是子节点中序号最小的节点,来判断是否获得了锁。当释放锁时,直接删除对应的节点即可。
基于ZooKeeper节点有序性实现分布式锁是非常流行和可靠的方式。ZooKeeper提供了有序节点和watch机制,可以很好地支持分布式锁的实现。
以下是一个简单的基于ZooKeeper的分布式锁的示例:
首先,我们新建一个节点存储锁,并创建临时顺序节点作为竞争锁的客户端:
import org.apache.zookeeper.*;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
public class ZooKeeperDistributedLock {
private static final String ZK_LOCK_PATH = "/distributed_lock";
private ZooKeeper zooKeeper;
private String currentLockPath;
private CountDownLatch latch = new CountDownLatch(1);
public ZooKeeperDistributedLock() throws IOException, InterruptedException, KeeperException {
this.zooKeeper = new ZooKeeper("localhost:2181", 10000, new Watcher() {
@Override
public void process(WatchedEvent event) {
if (event.getState() == Event.KeeperState.SyncConnected) {
latch.countDown();
}
}
});
latch.await();
}
public void lock() throws InterruptedException, KeeperException {
currentLockPath = zooKeeper.create(ZK_LOCK_PATH + "/lock_", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
List<String> children = zooKeeper.getChildren(ZK_LOCK_PATH, false);
Collections.sort(children);
if (currentLockPath.equals(ZK_LOCK_PATH + "/" + children.get(0))) {
return;
} else {
String subStr = currentLockPath.substring(ZK_LOCK_PATH.length() + 1);
int preNodeIndex = Collections.binarySearch(children, subStr) - 1;
zooKeeper.exists(ZK_LOCK_PATH + "/" + children.get(preNodeIndex), true);
}
}
public void unlock() throws InterruptedException, KeeperException {
zooKeeper.delete(currentLockPath, -1);
}
public void close() throws InterruptedException {
zooKeeper.close();
}
}
在上述示例中,我们首先创建一个/lock节点,并为每个客户端创建一个临时顺序节点。当一个客户端创建完节点后,将获取/lock节点下的所有子节点,对子节点进行排序,如果当前节点是最小的,表示客户端持有锁,可以执行业务逻辑。否则,阻塞等待前一个节点的删除事件。