分布式锁解决方案
2020-09-23 11:22:37 1 举报
AI智能生成
分布式锁
作者其他创作
大纲/内容
分布式锁需要考虑的几个点?
可重入or不可重入
公平or非公平
阻塞or非阻塞
独占or非独占
基于Redis单节点
SET lock_key random_value NX PX 5000
缺点:不具备可重入性
Redission
可重入锁
用hash数据结构。key=锁名称,hkey=随机字符串:线程ID,hvalue=自增Int
重入值通过hvalue大小来说明
LUA脚本:1. 用exist判断,如果不存在,则加锁成功;
2. 用hexist判断,如果存在并且是当前线程,说明是重入锁,加锁成功;
3. 如果不是当前线程,说明是其他线程持有说,加锁失败;
2. 用hexist判断,如果存在并且是当前线程,说明是重入锁,加锁成功;
3. 如果不是当前线程,说明是其他线程持有说,加锁失败;
Redission同时支持RedLock
用set数据结构实现
源码解析
基于Redis多节点的RedLock
高可用?分布式环境下会遇见的问题?
1. 崩溃恢复
解决方案:崩溃延迟重启
2. 时钟跳跃
解决方案:
1. 采用小步快跑方式,多次修改,每次更新时间量小
2. 通过阈值来做判断
1. 采用小步快跑方式,多次修改,每次更新时间量小
2. 通过阈值来做判断
3. GC STW、缺页故障、网络延迟
antirze认为redlock做了一些微小的工作,但是没办法完全避免,其他分布式锁方案也没有办法
ZK分布式锁也解决不了这个问题
RedLock具体算法
RedLock是建立在一个Time可信模型上
1. 获取开始时间;
2.去各节点获取锁;
3.再次获取时间;
4. 计算获取锁的实践,检查获取锁的时间是否小于锁的过期时间;
5. 如果小于,持有锁
2.去各节点获取锁;
3.再次获取时间;
4. 计算获取锁的实践,检查获取锁的时间是否小于锁的过期时间;
5. 如果小于,持有锁
1-3步之间发生了阻塞,RedLock可以感知锁已经过期,但是#4之后发生阻塞怎么办?
答案是:其他分布式锁方案也没有解决这个问题
基于Redis的分布式锁到底安全吗(下)?
基于DB
基于DB排他锁(悲观锁)
方式:
public boolean lock(){
connection.setAutoCommit(false)
while(true){
try{
result = select * from methodLock where method_name=xxx for update;
if(result==null){
return true;
}
}catch(Exception e){
}
sleep(1000);
}
return false;
}
public boolean lock(){
connection.setAutoCommit(false)
while(true){
try{
result = select * from methodLock where method_name=xxx for update;
if(result==null){
return true;
}
}catch(Exception e){
}
sleep(1000);
}
return false;
}
要给method_name添加唯一性索引
1. 会不会阻塞?
其他事务如果select... for update失败,则会一直阻塞
阻塞也会带来线程池撑爆风险
2. 是否可重入?
不可重入
缺点
1. 使用不当会死锁
2. 性能上有额外开销
3. 加锁列如果不加索引会导致表锁
基于DB表记录的唯一性索引
瑜伽案例
CREATE TABLE `database_lock` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`resource` int NOT NULL COMMENT '锁定的资源',
`description` varchar(1024) NOT NULL DEFAULT "" COMMENT '描述',
PRIMARY KEY (`id`),
UNIQUE KEY `uiq_idx_resource` (`resource`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='数据库分布式锁表';
//加锁
INSERT INTO database_lock(resource, description) VALUES (1, 'lock');
//解锁
DELETE FROM database_lock WHERE resource=1;
`id` BIGINT NOT NULL AUTO_INCREMENT,
`resource` int NOT NULL COMMENT '锁定的资源',
`description` varchar(1024) NOT NULL DEFAULT "" COMMENT '描述',
PRIMARY KEY (`id`),
UNIQUE KEY `uiq_idx_resource` (`resource`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='数据库分布式锁表';
//加锁
INSERT INTO database_lock(resource, description) VALUES (1, 'lock');
//解锁
DELETE FROM database_lock WHERE resource=1;
缺点
锁没有失效时间,如果解锁失败会一直留在数据库中;可以用定时任务去清理
不可重入,同一个线程在释放之前无法再次获取锁
主从同步延迟?
优点
非阻塞式
基于DB乐观锁
CREATE TABLE `optimistic_lock` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`resource` int NOT NULL COMMENT '锁定的资源',
`version` int NOT NULL COMMENT '版本信息',
`created_at` datetime COMMENT '创建时间',
`updated_at` datetime COMMENT '更新时间',
`deleted_at` datetime COMMENT '删除时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uiq_idx_resource` (`resource`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='数据库分布式锁表';
//
//1. 加锁
SELECT resource, version FROM optimistic_lock WHERE id = 1
//2. 执行业务逻辑
//3. 解锁
UPDATE optimistic_lock SET resource = resource -1, version = version + 1 WHERE id = 1 AND version = oldVersion
`id` BIGINT NOT NULL AUTO_INCREMENT,
`resource` int NOT NULL COMMENT '锁定的资源',
`version` int NOT NULL COMMENT '版本信息',
`created_at` datetime COMMENT '创建时间',
`updated_at` datetime COMMENT '更新时间',
`deleted_at` datetime COMMENT '删除时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uiq_idx_resource` (`resource`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='数据库分布式锁表';
//
//1. 加锁
SELECT resource, version FROM optimistic_lock WHERE id = 1
//2. 执行业务逻辑
//3. 解锁
UPDATE optimistic_lock SET resource = resource -1, version = version + 1 WHERE id = 1 AND version = oldVersion
优点
不依赖DB锁
缺点
并发小的时候,有少量请求会失败;大促、秒杀场景下,会有大量请求作用于同一条行锁,对数据库有很大的写压力
适用场景
并发量不高,写不频繁的场景
缺点
1. 在数据量比较少的时候,会进行表锁,而不是行锁
2. 需要占用DB连接
3. 依赖数据库,避免单点
基于ZK
独占锁
容易引发羊群效应
共享锁
1. Client创建create类似/lockpath/{hostname}-读写类型-序号的临时有序节点
2. Client创建完之后,通过getChildren获取节点下所有子节点,并且对所有节点注册Watch监听。
3. 确定本次创建的节点序号在所有节点中的顺序
a. 对于读请求,
ⅰ. 如果没有比自己更小的子节点,后者比自己更小的子节点都是读请求,则获取锁
ⅱ. 如果比自己更小的节点中有写请求,那么等待;
b. 对于写请求,
ⅰ. 如果自己不是最小的节点,则等待;
4. 等待监听通知后,重复步骤1
2. Client创建完之后,通过getChildren获取节点下所有子节点,并且对所有节点注册Watch监听。
3. 确定本次创建的节点序号在所有节点中的顺序
a. 对于读请求,
ⅰ. 如果没有比自己更小的子节点,后者比自己更小的子节点都是读请求,则获取锁
ⅱ. 如果比自己更小的节点中有写请求,那么等待;
b. 对于写请求,
ⅰ. 如果自己不是最小的节点,则等待;
4. 等待监听通知后,重复步骤1
羊群效应?
改进后的分布式锁:
1. Client调用create创建一个/lockpath/{hostname}-读写类型-序号的临时有序节点。
2. Client调用getChildren获取所有子节点列表,这里不注册Watch监听
3. 确定本次创建的节点序号在所有节点中的顺序
a. 如果是读请求,向比自己小的写请求注册Watch监听
b. 如果是写请求,向比自己小的最后一个节点注册Watch监听
4. 等待监听后,继续执行步骤2。
1. Client调用create创建一个/lockpath/{hostname}-读写类型-序号的临时有序节点。
2. Client调用getChildren获取所有子节点列表,这里不注册Watch监听
3. 确定本次创建的节点序号在所有节点中的顺序
a. 如果是读请求,向比自己小的写请求注册Watch监听
b. 如果是写请求,向比自己小的最后一个节点注册Watch监听
4. 等待监听后,继续执行步骤2。
基于curator实现
ZK是如何检测Client已经崩溃?
依靠心跳session来维护锁的持有状态
分布式锁选择?
从性能角度
缓存》ZK》=数据库
从高可用角度
ZK》缓存》数据库
从复杂角度(从低到高)
ZK》缓存》数据库
0 条评论
下一页