几种分布式锁的实现方案

大型网站及应用都有分布式部署,分布式场景中数据的一致性是一个重要的话题。
“任何一个分布式系统都无法同时满足一致性、可用性和分区容错性,最多只能同时满足两项。”所以我们在设计分布式系统只能对其进行取舍,系统往往只需要
保证“最终一致性”,只有这个最终时间是在用户可以接受的范围即可。
针对分布式锁的实现,比较常用的有这么三种方案:
基于数据库实现分布式锁,基于缓存实现分布式锁,基于Zookeeper实现分布式锁。

一、基于数据库实现分布式锁 
比较简单的就是直接创建一张锁表,通过操作该表中数据来实现。当我们要锁住某个方法或资源时,在该表中增加一条记录,想要释放锁的时候就删除这条记录。
CREATE TABLE `methodLock` (
??`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
??`method_name` varchar(64) NOT NULL DEFAULT '' COMMENT '锁定的方法名',
??`desc` varchar(1024) NOT NULL DEFAULT '备注信息',
??`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '保存数据时间,自动生成',
??PRIMARY KEY (`id`),
??UNIQUE KEY `uidx_method_name` (`method_name `) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='锁定中的方法';
当我们想要锁住某个方法时,执行SQL:insert into methodLock(method_name,desc) values (‘method_name’,‘desc’)
注意我们要对method_name做唯一约束,这样有多个请求同时提交到数据库也可以保证只有一个操作可以成功,我们就可以认为操作成功的那个线程获得了该方法的锁。
当执行完毕之后,想要释放锁时,执行SQL:delete from methodLock where method_name ='method_name'
总结:
1、这把锁强依赖数据库的可用性,一单数据库挂掉就会导致系统服务不可用;
措施:数据库是单点的,那可以搞两个数据库,数据之间双向同步,一旦挂掉可以快速切换到备库。
2、这把锁没有失效时间,一旦解锁操作失败,就导致锁记录一直在数据库中,其他线程无法再获得到锁;
措施:可以做一个定时任务,每隔一段时间把数据库中的超时数据清理一遍。
3、这把锁是非阻塞,一旦插入失败直接报错,没有获得锁的线程不会进行排队队列,要想再获得锁就要再次触发获得锁的操作;
措施:可以搞一个while循环,直到insert成功再返回成功。
除了通过操作数据库表记录外,还可以借助数据库自带的排他锁来实现分布式锁。

二、基于缓存实现分布式锁
相对于数据库实现分布式锁方案来说,基于缓存来实现性能方面会表现的更好。而且很多缓存是可以集群部署,可以解决单点问题。比如Redis、memcached。
Redis实现分布式锁主要通过采用SETNX(SET if Not eXists)命令。需要注意Redis需要设置超时时间,以避免死锁。可以利用jedis客户端实现。
语法:
1.SETNX key value命令 当且仅当key不存在,将key的值设为value,并返回1;若给定的key已经存在,则SETNX不做任何动作,并返回0;
2.GETSET key value命令 将给定key的值设为value,并返回key的旧值,当key存在但不是字符串类型时,返回一个错误,当key不存在时,返回nil;
3.GET key命令 返回key所关联的字符串值,如果key不存在那么返回特殊值nil;
4.DEL key命令 删除给定的一个或多个key,不存在的key会被忽略。
总结:
1、这把锁没有失效时间,一旦解锁操作失败,就会导致锁记录一直在tair中,其他线程无法再获取得到锁;
措施:Redis的expire方法支持传入失效时间,到达时间后数据就会自动删除。
2、这把锁只能非阻塞,无论成功失败都直接返回;
措施:可以while重复执行。
使用缓存来代替数据库实现分布式锁,可以提供更高的性能,同时支持集群部署,可以有效避免单点问题。并且缓存服务提供了可以用来实现分布式锁的方法,比如Redis的setnx方法。
并且缓存服务还提供了对数据过期清理的策略,也可以直接设置超时时间来控制锁的释放。但是,缺点就是通过超时时间来控制锁的失效时间并不是十分的靠谱。

三、基于Zookeeper实现分布式锁
思想:每个客户端对某个方法加锁时,在zookeeper上与该方法对应的指定节点目录下,生成一个唯一瞬时有序节点。
zookeeper有效解决基于数据库和缓存实现分布式锁带来的问题:
1.非阻塞:
使用zookeeper可以实现阻塞的锁,客户端通过在ZK中创建顺序节点,并且在节点上绑定监听器,一旦节点变化,zookeeper通知客户端,客户端可以检查自己创建的节点是否是当前
所有节点中序号最小,如果是,那么就可以获取到锁,接下来就可以执行业务逻辑了。
2.单点问题:
使用zookeeper可以有效解决单点问题,ZK是集群部署,只有集群中有半数以上机器存活,就可以对外提供服务。

分布式锁的一些问题:
1:必要的超时机制:获取锁的客户端一旦奔溃,一定要有过期机制,否则其他客户端就无法获取锁,造成死锁;
2:多客户端情况下时间戳不能保证严格意义的一致性;
3:在持有锁期间如果需要严格依赖锁的状态,最后在关键步骤中做锁的CHECK检查机制,但是如果在大并发时,每一次CHECK锁操作需要耗时几个毫秒,而整个持有锁的处理逻辑才不到
10毫秒,那么就没必要做锁的检查机制;
4:只对关键处理节点加锁,比如连接数据库后,调用加锁机制获取锁,直接进行操作,然后释放,尽量减少持有锁的时间。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

猜你喜欢

转载自www.cnblogs.com/afeng-chen/p/12618586.html