😄 19年之后由于某些原因断更了三年,23年重新扬帆起航,推出更多优质博文,希望大家多多支持~
🌷 古之立大事者,不惟有超世之才,亦必有坚忍不拔之志
🎐 个人CSND主页——Micro麦可乐的博客
🐥《Docker实操教程》专栏以最新的Centos版本为基础进行Docker实操教程,入门到实战
🌺《RabbitMQ》本专栏主要介绍使用JAVA开发RabbitMQ的系列教程,从基础知识到项目实战
🌸《设计模式》专栏以实际的生活场景为案例进行讲解,让大家对设计模式有一个更清晰的理解
如果文章能够给大家带来一定的帮助!欢迎关注、评论互动~
深度解析分布式锁及实现方案
- 引言
- 什么是分布式锁?
- 分布式锁的应用场景
- 分布式锁的设计原则
- 分布式锁的实现方案
- 基于数据库的实现
- 基于缓存的实现
- 基于ZooKeeper的实现
- 分布式锁的注意事项
- 结语
引言
在分布式系统中,分布式锁是协调多个节点对共享资源进行互斥访问的关键技术。本文将深入研究分布式锁的概念、应用场景,并详细介绍三种常见的实现方案,包括基于数据库、基于缓存(以Redis
为例)和基于ZooKeeper
的实现。
什么是分布式锁?
分布式锁是一种在分布式系统中实现协同访问共享资源的机制,目的是确保在分布式环境中对共享资源的互斥访问,避免数据不一致性、并发冲突等问题。
分布式锁的应用场景
-
避免重复任务: 确保在分布式系统中某个任务只被执行一次,防止重复执行。
-
资源竞争控制: 协调多个节点对共享资源的访问,保证资源访问的互斥性。
-
分布式事务: 在分布式事务中,用于协调多个参与者对资源的访问,确保事务的一致性。
分布式锁的设计原则
Redis的官网在新窗口打开上对使用分布式锁提出至少需要满足如下三个要求:
- 互斥(属于安全性):在任何给定时刻,只有一个客户端可以持有锁。
- 无死锁(属于有效性):即使锁定资源的客户端崩溃或被分区,也总是可以获得锁;通常通过超时机制实现。
- 容错性(属于有效性):只要大多数 Redis 节点都启动,客户端就可以获取和释放锁。
除此之外,分布式锁的设计中还可以/需要考虑:
加锁解锁的同源性:A加的锁,不能被B解锁
获取锁是非阻塞的:如果获取不到锁,不能无限期等待;
高性能:加锁解锁是高性能的
分布式锁的实现方案
下面介绍几种我们日常工作中常见的分布式锁的实现方案
1、基于数据库实现分布式锁
- 基于数据库表(锁表,很少使用)
- 乐观锁(基于版本号)
- 悲观锁(基于排它锁)
2、基于 redis 实现分布式锁
- 单个Redis实例:setnx(key,当前时间+过期时间) + Lua
- Redis集群模式:Redlock
3、基于 zookeeper实现分布式锁
- 临时有序节点来实现的分布式锁,Curator
基于数据库的实现
通过数据库的事务特性来实现分布式锁,使用数据库行级锁或唯一索引。
详细代码示例(使用PostgreSQL):
// 加锁
public boolean tryLock(String lockKey, String clientId, int expireTime) {
try (Connection connection = dataSource.getConnection()) {
connection.setAutoCommit(false);
try (PreparedStatement preparedStatement = connection.prepareStatement(
"INSERT INTO distributed_lock (lock_key, client_id, expire_time) VALUES (?, ?, ?) ON CONFLICT (lock_key) DO NOTHING")) {
preparedStatement.setString(1, lockKey);
preparedStatement.setString(2, clientId);
preparedStatement.setTimestamp(3, new Timestamp(System.currentTimeMillis() + expireTime));
int affectedRows = preparedStatement.executeUpdate();
if (affectedRows > 0) {
connection.commit();
return true;
} else {
connection.rollback();
return false;
}
}
} catch (SQLException e) {
// 处理异常
return false;
}
}
// 解锁
public void unlock(String lockKey, String clientId) {
try (Connection connection = dataSource.getConnection()) {
try (PreparedStatement preparedStatement = connection.prepareStatement(
"DELETE FROM distributed_lock WHERE lock_key = ? AND client_id = ?")) {
preparedStatement.setString(1, lockKey);
preparedStatement.setString(2, clientId);
preparedStatement.executeUpdate();
}
} catch (SQLException e) {
// 处理异常
}
}
基于缓存的实现
利用分布式缓存系统(以Redis为例),通过其原子性操作来实现分布式锁。
详细代码示例(使用Redis):
// 加锁
public boolean tryLock(String lockKey, String clientId, int expireTime) {
try (Jedis jedis = jedisPool.getResource()) {
String result = jedis.set(lockKey, clientId, "NX", "PX", expireTime);
return "OK".equals(result);
}
}
// 解锁
public void unlock(String lockKey, String clientId) {
try (Jedis jedis = jedisPool.getResource()) {
jedis.del(lockKey);
}
}
基于ZooKeeper的实现
利用ZooKeeper的临时有序节点特性,实现分布式锁。
public boolean tryLock(String lockKey, String clientId, int expireTime) {
try {
String lockPath = zooKeeper.create(lockKey + "/", clientId.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
List<String> children = zooKeeper.getChildren(lockKey, false);
Collections.sort(children);
if (clientId.equals(children.get(0))) {
return true;
} else {
zooKeeper.delete(lockPath, -1);
return false;
}
} catch (Exception e) {
// 处理异常
return false;
}
}
public void unlock(String lockKey, String clientId) {
try {
List<String> children = zooKeeper.getChildren(lockKey, false);
for (String child : children) {
if (child.startsWith(clientId)) {
zooKeeper.delete(lockKey + "/" + child, -1);
}
}
} catch (Exception e) {
// 处理异常
}
}
分布式锁的注意事项
死锁和宕机
考虑在获取锁的过程中可能发生的节点宕机和死锁情况,确保系统的可用性。
锁的释放
确保锁在适当的时候被释放,防止出现死锁或者长时间占用锁的情况。
锁粒度
合理选择锁的粒度,过大的粒度可能导致性能问题,而过小的粒度可能导致锁争用。
结语
分布式锁是分布式系统中常用的同步机制,通过对共享资源的互斥访问,确保系统的一致性。在选择实现方案时,需要根据实际场景和系统要求综合考虑,保证分布式锁的性能、可靠性和可维护性。在实际应用中,可以根据业务需求选择适当的实现方案。