专业的JAVA编程教程与资源

网站首页 > java教程 正文

Java面试必考问题:如何实现分布式锁

temp10 2024-11-22 20:55:18 java教程 13 ℃ 0 评论

为什么需要分布式锁

为了保证一个方法或数据在高并发情况下只能被一个线程执行或访问,在单机部署的情况下,可以使用Java并发相关的API或语法实现互斥,比如如ReentrantLock或Synchronized等。

随着业务发展的需要,单机部署的系统会演化成分布式集群部署,应用程序的线程分布在不同机器上,单机并发控制的锁策略也就失效了。为了解决这个问题,就需要跨虚拟机的分布式锁策略,来实现分布式系统的互斥和访问控制。

Java面试必考问题:如何实现分布式锁

分布式锁应该具备以下特性

  • 互斥性:在分布式环境下,一个方法在同一时间只能被一个机器的一个线程执行;
  • 高可用的获取锁与释放锁;
  • 高性能的获取锁与释放锁;
  • 具备可重入特性;
  • 具备锁失效机制,防止死锁;
  • 具备非阻塞锁特性,即没有获取到锁将直接返回获取锁失败。

分布式锁的实现方式

实现分布式锁的基本思路是:在集群的一个公共节点中,记录当前集群中哪个线程获得了资源锁,这个地方各个服务器的线程都可以访问得到。

如果有线程要访问资源,必须先查看是否有其他线程已经获取了资源的锁,如果没有那就可以占用资源锁,并记录在公共节点上。

分布式锁可以通过数据库、缓存或者ZooKeeper中间件来实现。

基于数据库实现分布式锁

基于数据库实现分布式锁,就是在数据库中针对需要进行互斥的方法建立一张锁表。

CREATE TABLE `method_lock` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
  `method_name` varchar(64) NOT NULL COMMENT '锁定的方法名',
  `state` tinyint NOT NULL COMMENT '1:未分配;2:已分配',
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `version` int NOT NULL COMMENT '版本号',
  `PRIMARY KEY (`id`),
  UNIQUE KEY `uidx_method_name` (`method_name`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COMMENT='锁定中的方法';

当有线程要调用某个方法时,在数据库的锁表中设置该方法的记录的状态为“已分配”。当有线程准备调用该方法时,看到锁表中已经设置了该方法的状态时,表示该方法已经被其他线程使用。当线程使用完方法,需要将该记录的状态字段设置为“未分配”,实现解锁。

基于缓存(Redis)实现分布式锁

基于数据库的方案由于每次执行方法都需要先对数据库进行IO操作,性能效率低下,基于redis的分布式锁更有实用价值。

当线程要调用需要互斥的方法时,在redis中先检查一下方法锁是否已经设置对应的key了,如果没有设置就表示可以获取锁。执行完方法需要将redis中的key进行重置。

对于单点部署,这个方案没有什么问题,在主从架构中,这个方案存在明显缺陷:

比如线程A从master节点获取到锁,在master节点将锁同步到slave节点前,master节点宕机了。随后slave节点升级为master节点,另外一个线程B此时仍然可以获取到锁,互斥失效!

基于ZooKeeper实现分布式锁

基于ZooKeeper的分布式锁方案可以解决上述问题,ZooKeeper集群的同步机制是可以支持最终一致性的。ZooKeeper实现分布式锁的方案如下:

(1)在Zookeeper当中先创建一个持久节点ParentLock。客户端Client_1尝试获取锁,需要在ParentLock下面创建一个临时顺序节点 Lock1。

(2)Client_1发现自己创建的临时顺序节点Lock1是顺序最靠前的,因此可以获得锁。

(3)Client_2也尝试获取锁,在ParentLock下再创建一个临时顺序节点Lock2。

(4)Client_2发现自己创建的节点Lock2前面还有Lock1,所以还不能获得锁。于是就建立对Lock1节点的监听。

(5)Client_3到来尝试获取锁,建立新的临时顺序节点Lock3。

(6)Client_3发现前面还有Lock2,不能获得锁,于是Client_3开始监听Lock2。

以上(1)-(6)是加锁的过程,锁的竞争者们通过临时顺序节点的创建和监听,形成了一个等待队列。

(7)Client1任务完成,删除节点Lock1。如果Client1因为某种原因断开连接,那么ZooKeeper也会删除对应的节点Lock1。

(8)Client2发现此时Lock2已经排在最前面了,Client2可以获得锁。

(9)Client2执行完成,删除节点Lock2,释放锁。这下轮到Client3获得锁了。

以上(7)-(9)是释放锁的过程。

总结

从性能角度来说,基于redis的方案性能最好;从可靠性角度来说,基于ZooKeeper的方案可靠性最高。合适的才是最好的,我们还是要根据自身需求来选择方案。

我会持续更新关于物联网、云原生以及数字科技方面的文章,用简单的语言描述复杂的技术,也会偶尔发表一下对IT产业的看法,欢迎大家关注,谢谢。

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表