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