分布式锁实现方式(zookeeper、redis)| 8月更文挑战

187 阅读4分钟

这是我参与8月更文挑战的第20天

分布式锁应该具备哪些条件

  • 在分布式系统环境下,一个方法在同一时间只能被一个机器的一个线程执行
  • 高可用的获取锁与释放锁
  • 高性能的获取锁与释放锁
  • 具备可重入特性(可理解为重新进入,由多于一个任务并发使用,而不必担心数据错误)
  • 具备锁失效机制,防止死锁
  • 具备非阻塞锁特性,即没有获取到锁将直接返回获取锁失败
互斥性。在任意时刻,只有一个客户端能持有锁。

不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。

具有容错性。只要大部分的Redis节点正常运行,客户端就可以加锁和解锁。

解铃还须系铃人。加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。

分布式锁的实现有哪些

  • Memcached:利用 Memcached 的 add 命令。此命令是原子性操作,只有在 key 不存在的情况下,才能 add 成功,也就意味着线程得到了锁。
  • Redis:和 Memcached 的方式类似,利用 Redis 的 setnx 命令。此命令同样是原子性操作,只有在 key 不存在的情况下,才能 set 成功。
  • Zookeeper:利用 Zookeeper 的顺序临时节点,来实现分布式锁和等待队列。Zookeeper 设计的初衷,就是为了实现分布式锁服务的。
  • Chubby:Google 公司实现的粗粒度分布式锁服务,底层利用了 Paxos 一致性算法。

通过数据库实现分布式锁

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;

通过redis实现分布式锁

通过Redis setnx方法来实现分布式锁,如果存在则返回1,如果失败则返回0,删除锁通过del方法来实现;

死锁场景:如果在上锁和解锁中间的业务代码出现异常,那么其他线程一直获取不到锁,而当前线程一直不释放锁,就会产生死锁

多线程并发访问数据产生的同步问题: 假如部署了集群redis服务,主节点突然挂掉了。同时主节点中有把锁还没有来得及同步到从节点。这样就会导致系统中同样-一把锁被两个客户端同时持有,不安全性由此产生。

redis官方为 了解决这个问题,推出了Redlock算法解决这 被两个客户端同时持有,不安全性由此产生.红纹夜蛾(redis官方为了解决这个问题,推出了redlock算法解决这)

通过zookeeper实现分布式锁

实现思路:

  • 为每一个执行线程创建一个有序节点-->在指定目录下创建zookeeper临时节点;

  • 在创建完节点后,取全部的节点重新进行排序-->getChildren()方法,利用treeSet进行排序;

  • 判断当前节点是否是第一个节点,如果是第一个节点则获取锁。

      //操作失败重试机制 1000毫秒间隔 重试3次
      RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
      //创建Curator客户端
      CuratorFramework client = CuratorFrameworkFactory.newClient("192.168.1.18:2181", retryPolicy);
      //开始
      client.start();
      
      /**
       * 这个类是线程安全的,一个JVM创建一个就好
       * mylock 为锁的根目录,我们可以针对不同的业务创建不同的根目录
       */
      final InterProcessMutex lock = new InterProcessMutex(client, "/mylock");
      try {
      	//阻塞方法,获取不到锁线程会挂起。
      	lock.acquire();
      	System.out.println("已经获取到锁");
      	 Thread.sleep(10000);
      } catch (Exception e) {
      	e.printStackTrace();
      }
      finally{
      	//释放锁,必须要放到finally里面,已确保上面方法出现异常时也能够释放锁。
      	lock.release();
      }
      
      Thread.sleep(10000);
      
      client.close();