Zookeeper分布式锁原理分析
Zookeeper用作Leader选举的原理分析
我们先来看一个问题,如下图所示,两个用户同时去抢购秒杀商品,当秒杀服务同时收到秒杀请求时,都去进行库存扣减,此时在没有做任何处理的情况下,就会导致库存数量变成负数从而导致超卖现象。
这种情况下我们一般会选择加锁的方式来避免并发的问题。但是在分布式场景中,采用传统的锁并不能解决跨进程并发的问题,所以需要引入一个分布式锁,来解决多个节点之间的访问控制。

我们可以基于Zookeeper的两种特性来实现分布式锁:
1、使用唯一节点特性实现分布式锁
就是基于唯一节点特性,如下图所示。多个应用程序去抢占锁资源时,只需要在指定节点上创建一个 /Lock 节点,由于Zookeeper中节点的唯一性特性,使得只会有一个用户成功创建 /Lock 节点,剩下没有创建成功的用户表示竞争锁失败。

这种方法能达到目的,但是会有一个问题,如下图所示,假设有非常多的节点需要等待获得锁,那么等待的方式自然是使用Watcher机制来监听/lock节点的删除事件,一旦发现该节点被删除说明之前获得锁的节点已经释放了锁,此时剩下的B、C、D。节点同时会收到删除事件从而去竞争锁,这个过程会产生惊群效应。

“惊群效应”,简单来说就是如果存在许多的客户端在等待获取锁,当成功获取到锁的进程释放该节点后,所有处于等待状态的客户端都会被唤醒,这个时候zookeeper在短时间内发送大量子节点变更事件给所有待获取锁的客户端,然后实际情况是只会有一个客户端获得锁。如果在集群规模比较大的情况下,会对zookeeper服务器的性能产生比较大的影响。
2、使用有序节点实现分布式锁
因此为了解决这个问题,我们可以采用Zookeeper的有序节点特性来实现分布式锁。
如下图所示,每个客户端都往指定的节点下注册一个临时有序节点,越早创建的节点,节点的顺序编号就越小,那么我们可以判断子节点中最小的节点设置为获得锁。如果自己的节点不是所有子节点中最小的,意味着还没有获得锁。这个的实现和前面单节点实现的差异性在于,每个节点只需要监听比自己小的节点,当比自己小的节点删除以后,客户端会收到watcher事件,此时再次判断自己的节点是不是所有子节点中最小的,如果是则获得锁,否则就不断重复这个过程,这样就不会导致羊群效应,因为每个客户端只需要监控一个节点。

如下图所示,表示有序节点实现分布式锁的流程。

在本文中我们使用Curator来实现分布式锁。为了实现分布式锁,我们先演示一个存在并发异常的场景。
项目见zk-demo中的scene包下面
1、sql脚本
- DROP TABLE IF EXISTS `goods_stock`;
- CREATE TABLE `goods_stock` (
- `id` int unsigned NOT NULL AUTO_INCREMENT,
- `goods_no` int NOT NULL COMMENT '商品编号',
- `stock` int DEFAULT NULL COMMENT '库存',
- `isActive` smallint DEFAULT NULL COMMENT '是否上架(1上,0不是)',
- PRIMARY KEY (`id`)
- ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
2、采用mybatis-plus搭建项目,项目结构如图所示

3、POM文件
- "1.0" encoding="UTF-8"?>
- <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
- <modelVersion>4.0.0modelVersion>
- <groupId>com.examplegroupId>
- <artifactId>zk-demoartifactId>
- <version>0.0.1-SNAPSHOTversion>
- <name>zk-demoname>
- <description>zookeeper应用description>
-
- <properties>
- <java.version>1.8java.version>
- <project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
- <project.reporting.outputEncoding>UTF-8project.reporting.outputEncoding>
- <spring-boot.version>2.3.2.RELEASEspring-boot.version>
- properties>
-
- <dependencies>
- <dependency>
- <groupId>org.springframework.bootgroupId>
- <artifactId>spring-boot-starter-webartifactId>
- dependency>
-
- <dependency>
- <groupId>org.springframework.bootgroupId>
- <artifactId>spring-boot-starter-testartifactId>
- dependency>
- <dependency>
- <groupId>org.apache.zookeepergroupId>
- <artifactId>zookeeperartifactId>
- <version>3.4.13version>
- dependency>
- <dependency>
- <groupId>org.apache.curatorgroupId>
- <artifactId>curator-frameworkartifactId>
- <version>4.0.1version>
- <exclusions>
- <exclusion>
- <groupId>org.apache.zookeepergroupId>
- <artifactId>zookeeperartifactId>
- exclusion>
- exclusions>
- dependency>
- <dependency>
- <groupId>org.apache.curatorgroupId>
- <artifactId>curator-recipesartifactId>
- <version>4.0.1version>
- <exclusions>
- <exclusion>
- <groupId>org.apache.zookeepergroupId>
- <artifactId>zookeeperartifactId>
- exclusion>
- exclusions>
- dependency>
-
- <dependency>
- <groupId>mysqlgroupId>
- <artifactId>mysql-connector-javaartifactId>
- <scope>runtimescope>
- dependency>
- <dependency>
- <groupId>com.baomidougroupId>
- <artifactId>mybatis-plus-boot-starterartifactId>
- <version>3.4.2version>
- dependency>
- <dependency>
- <groupId>com.baomidougroupId>
- <artifactId>mybatis-plus-generatorartifactId>
- <version>3.4.0version>
- dependency>
- <dependency>
- <groupId>com.alibabagroupId>
- <artifactId>druid-spring-boot-starterartifactId>
- <version>1.2.9version>
- dependency>
- <dependency>
- <groupId>org.apache.commonsgroupId>
- <artifactId>commons-lang3artifactId>
- dependency>
- <dependency>
- <groupId>org.projectlombokgroupId>
- <artifactId>lombokartifactId>
- dependency>
- <dependency>
- <groupId>org.apache.commonsgroupId>
- <artifactId>commons-pool2artifactId>
- dependency>
- <dependency>
- <groupId>org.freemarkergroupId>
- <artifactId>freemarkerartifactId>
- dependency>
- dependencies>
-
- <dependencyManagement>
- <dependencies>
- <dependency>
- <groupId>org.springframework.bootgroupId>
- <artifactId>spring-boot-dependenciesartifactId>
- <version>${spring-boot.v