分布式锁

分布式锁

1.概念

分布式锁是控制分布式系统或不同系统之间共同访问共享资源的一种锁实现,如果不同的系统或同一个系统的不同主机之间共享了某个资源时,往往需要互斥来防止彼此干扰来保证一致性。

2.分布式锁需要具备的条件

2.1互斥性

在任意一个时刻,只有一个客户端持有锁。

2.2无死锁

即便持有锁的客户端崩溃或者其他意外事件,锁仍然可以被获取。

2.3容错

只要大部分节点都活着,客户端就可以获取和释放锁

3.三种实现方式

3.1基于数据库

3.1.1创建表
CREATE TABLE `resource_lock` (
  `id` int(11) NOT NULL AUTO_INCREMENT
  `resource_name` varchar(64) NOT NULL ,
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '保存数据时间,自动生成',
  `holder_info` varchar(64) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uidx_resource` (`resource_name`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT '资源锁表';

唯一索引 UNIQUE KEY resource_name保证了在任何时候每个资源只能有一个主机能够获取该资源
holder_info记录了获取资源的是谁,这个字段使分布式锁支持可重入性,从而避免了死锁。
update_time记录锁的持有时间。
注意:务必对resource_name添加索引,否则mysql将会锁全表,这会造成其它线程都无法申请锁

3.1.2创建锁
insert into methodLock(method_name,desc) values (‘method_name’,desc)

method_name具有唯一性,多个线程调用同一个方法,数据库会保证只有一个调用成功

3.2缺憾及解决方式
3.2.1依赖数据库的可用性

一旦数据库挂掉会导致服务不可用
解决方式:使用两个数据库,双向同步。一旦挂掉快速切换到备库上。

3.2.2锁没有失效时间

一旦解锁失败会导致锁一直存在数据库,其他线程无法调用
解决方式:可以写一个定时任务,每隔多长时间根据updatetime字段清理超时的数据

3.2.3锁是非阻塞的

一旦插入失败就会直接报错,要想再次获得锁需要重新触发获得锁的操作
解决方式:设置一个超时时间,多长时间获取不到报错

-- 获取锁代码如下
begin;
select * from resource_lock where resource_name='resource' for update
-- 业务逻辑
dosomething()
-- 释放锁代码
commit;
3.2.4锁是非重入的

同一个线程在没有释放锁的时候无法再次获得锁
解决方式:可以再加一个字段记录线程的唯一性信息,然后插入数据时如果发现表中存在数据,直接分配锁给他,不需要再新增,修改下update_time即可。

3.3优缺点

优点:
1.直接使用数据库容易理解
2.数据库锁具有持久化特性
缺点:
1.操作数据库会有性能开销
2.数据库阻塞锁的实现可能会造成占用大量数据库连接池连接导致其它线程无连接可用

3.2基于zookeeper

3.2.1原理

通过创建临时顺序的节点来实现分布式锁

3.2.2场景

客户端A访问zk,创建临时顺序节点,判断节点是否为第一个节点(查询锁节点下的所有节点判断自己是否为第一个),是的话获取锁。
客户端B访问zk,创建临时顺序节点,判断节点是否为第一个节点,发现不是第一个节点,给之前的节点创建一个watcher,监听上一个节点的销毁(销毁代表锁的释放)。
注意:
临时节点是为了防止客户端宕机,zk会删除该客户端的临时节点
顺序节点是为了保证每个客户端获取锁是顺序的

3.4基于redis

3.4.1单个redis

使用setnx来设置key,value,超时时间

超时时间的存在为了防止这种场景发生:
故障场景:客户端A设置了一个锁,但是挂掉了,客户端B一直在等待客户端A释放锁。
解决方式:设置超时时间后多一段时间如果A没主动释放,锁会自动释放,B可以再去获取锁

value的存在为了防止这种场景发生:
故障场景:客户端A设置了一个锁,但是挂掉了,超时时间也到了,客户端B获取到了锁,这是A又活了,主动去删除了B设置的锁,客户端C获取到了锁,C写入的数据覆盖了B写入得数据。
解决方式:设置value值后,每个客户端删除锁之前需要查看是否为自己的value从而判读是否为自己的锁。由于需要两个操作,获取和删除的操作,保证原子性需要使用lua脚本

注:可以使用redisson来使用加锁和解锁,加锁也使用的lua脚本,支持可重入。

3.4.2集群模式
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值