常见的分布式锁(简略)
1、背景和简介#
- 传统单体应用使用本地锁(synchronized、ReentrantLock)
- 随着分布式的快速发展,本地锁已经无法解决并发问题
- 需要一种能跨微服务/虚拟机的锁机制--->分布式锁
分布式锁的作用
- 并发正确性(资源独占)
- 效率: 避免多个pod(虚拟机)都执行同一个操作,重复处理业务。
分布式锁的特性
- 互斥性:基本功能,一个获取锁,另一个就不能获取
- 可重入性:一个线程获取锁之后可以再次获取这个锁
- 锁超时:持有锁的线程挂掉后,一定时间后锁自动释放
- 高效:加锁/释放锁的速度快
- 高可用
- 支持阻塞和非阻塞
- 支持公平锁(排着队来等)和非公平锁(随机来抢)
常用的分布式锁中间件
- MySQL
- Zookeeper
- Chubby(谷歌)
- Etcd
- Redis
2、MySQL分布式锁#
- 专用的数据表
CREATE TABLE `resource_lock`(
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`resource_name` varchar(128) NOT NULL DEFAULT '' COMMENT '资源名字、模块名称、业务名称',
`node_info` varhcar(128) NOT NULL DEFAULT '' COMMENT '节点信息、线程信息、线程标识',
`count` varchar(11) NOT NULL DEFAULT '0' COMMENT '锁的次数,实现可重入',
`create_time` timestamp NULL DEFAULT NULL COMMENT '创建时间',
`update_time` timestamp NULL DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `unq_resource`(`resource_name`)
)
- 若需要枷锁的资源恰好有对应的数据表,可以在数据表中增加相应的字段,达到复用数据的目的
代码实现(方式)#
阻塞式获取锁
循环调用lock()函数,直到返回true
@Transaction // 必须要有事物
public boolean lock() {
// for update 增加写锁
if(select * from resource_lock where resource_name = '***' for update -> 有数据){
// 有数据,表示资源已经被枷锁,需要判断是否是重入
if(currentNodeInfo == resultNodeInfo) {
// 是自己加的锁,增加count,表示重入了
update resource_lock set count = count + 1 where resource_name = '***';
return true;
} else {
return false
}
} else {
//
insert into resource_lock
return true;
}
}
非阻塞式获取锁
public boolean tryLock(long timeout `超时时间`) {
long endTimeout = System.currentTimeMills() + timeout;
where(true){
// 上面的lock锁
if(mysqlLock.lock()) {
return true;
}
// 如果超时,加锁失败
if(endTimeout < System.currentTimeMills()) {
return false;
}
}
}
释放锁
@Transaction
public boolean unlock() {
if (select * from resource_lock where resource_name = '***' for update -> 有数据) {
// 有数据,表示资源已经被加锁,需要判断是否被自己加锁
if (currentNodeInfo == resultNodeInfo) {
// 是自己加的锁
if (count > 1) {
// 锁被自己重入,直接减少count字段
update count = count - 1;
} else {
// 锁没有重入,直接删除
delete;
}
} else {
// 不是自己加的锁,无法释放
return false;
}
} else {
return true;
}
}
锁超时
启动一个定时任务循环遍历锁,长时间未释放的锁即为超时,直接删除。
MySQL分布式锁特点#
- 适用场景:没有其他中间件可用,需要枷锁的资源刚好有对应的数据表
- 优点:理解起来简单,不需要维护其他中间件
- 缺点:需要自己实现加锁、解锁过程,性能比较差
3、ZooKeeper分布式锁#
ZoopKeeper是以Paxos算法为基础分布式应用程序协调服务
简介和工作原理#
知识点:
临时节点(有序、无序)、zookeeper中的watch
加锁流程#
- 进行重入判定(可以利用ThreadLocal)
- 在被锁资源下建立ephemeral_sequential节点
- 判断自己的节点是否位于第一个
- 若是第一个,则获取到锁,返回
- 若不是第一个,在前一个节点上注册watcher
- 进行阻塞等待
解锁流程#
- 进行重入判定(可以利用ThreadLocal)
- 若为重入,在重入次数上减1,返回
- 删除Zookeeper上的节点
代码实现#
Curator包含了几个包
curator-framework:对zookeeper的底层api的一些封装
curator-client:提供一些客户端的操作,例如重试策略等
curator-recipes:封装了一些高级特性,如:Cache事件监听、选举、分布式锁、分布式计数器、分布式Barrier等
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.12.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-client</artifactId>
<version>2.12.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.12.0</version>
</dependency>
优缺点#
4、Chubby分布式锁#
简介#
- Chubby是谷歌开放的分布式应用程序协调服务
- 功能上与Zookeeper类似
5、Etcd分布式锁#
简介#
- Etcd是一个高可用的分布式键值(key-value)数据库
- Etcd内部采用raft协议作为一致性算法
实现方式#
6、Redis分布式锁#
简介#
- Redis(REmote Dlctionary Server)是一个key-value存储中间件
- Redis中的value可以是字符串(string)、哈希(hash)、列表(list)、集合(set)和有序集合(sorted sets)等类型
简易实现方式#
ex : 超时时间,用于锁超时处理
nx:是否存在,存在则不成功,用于锁的独占处理
Redission实现分布式锁#
RedLock实现Redis分布式锁#
-
Redis主从与集群并不是强一致性的,所以在极端情况下,会有一致性问题
-
若Redis未及时落盘,重启后会丢失数据
-
为了解决以上问题,Redis作者提出了RedLock红锁算法
-
首先生成多个Redis集群的Rlock,并将其构造成RedLock
-
依次循环对三个(多个)集群进行加锁,加锁方式和Redission一致
-
如果循环加锁的过程中加锁失败,那么需要判断加锁失败的次数是否超出了最大之(要多数成功)
-
加锁的过程中需要判断是否加锁超时
-
若失败,向所有节点请求解锁
优缺点#
7、分布式锁的安全问题#
GC导致锁超时
网络I/O导致锁超时
时钟跳跃导致锁超时
作者:mountainstudy
出处:https://www.cnblogs.com/mountainstudy/p/17094045.html
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)