From 41160df3d5870e7a6d6a510c715f39f45266d6c6 Mon Sep 17 00:00:00 2001 From: "Yangkai.Shen" <237497819@qq.com> Date: Sat, 3 Sep 2022 13:47:53 +0800 Subject: [PATCH] =?UTF-8?q?:sparkles:=20=E5=88=86=E5=B8=83=E5=BC=8F?= =?UTF-8?q?=E9=94=81=E6=A8=A1=E5=9D=97=E4=B9=8B=20=E5=9F=BA=E4=BA=8E=20cur?= =?UTF-8?q?ator=20=E5=AE=9E=E7=8E=B0=E5=88=86=E5=B8=83=E5=BC=8F=E9=94=81?= =?UTF-8?q?=20=E6=A1=88=E4=BE=8B=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../demo-distributed-lock-curator/README.md | 180 +++++++++++++++++++++ .../docker-compose.env.yml | 14 ++ .../demo-distributed-lock-curator/pom.xml | 45 ++++++ .../lock/CuratorDistributedLockApplication.java | 21 +++ .../lock/autoconfigure/CuratorDistributedLock.java | 64 ++++++++ .../CuratorDistributedLockAutoConfiguration.java | 38 +++++ .../CuratorDistributedLockClient.java | 34 ++++ .../src/main/resources/application.yml | 16 ++ demo-distributed-lock/pom.xml | 3 +- 9 files changed, 414 insertions(+), 1 deletion(-) create mode 100644 demo-distributed-lock/demo-distributed-lock-curator/README.md create mode 100644 demo-distributed-lock/demo-distributed-lock-curator/docker-compose.env.yml create mode 100644 demo-distributed-lock/demo-distributed-lock-curator/pom.xml create mode 100644 demo-distributed-lock/demo-distributed-lock-curator/src/main/java/com/xkcoding/distributed/lock/CuratorDistributedLockApplication.java create mode 100644 demo-distributed-lock/demo-distributed-lock-curator/src/main/java/com/xkcoding/distributed/lock/autoconfigure/CuratorDistributedLock.java create mode 100644 demo-distributed-lock/demo-distributed-lock-curator/src/main/java/com/xkcoding/distributed/lock/autoconfigure/CuratorDistributedLockAutoConfiguration.java create mode 100644 demo-distributed-lock/demo-distributed-lock-curator/src/main/java/com/xkcoding/distributed/lock/autoconfigure/CuratorDistributedLockClient.java create mode 100644 demo-distributed-lock/demo-distributed-lock-curator/src/main/resources/application.yml diff --git a/demo-distributed-lock/demo-distributed-lock-curator/README.md b/demo-distributed-lock/demo-distributed-lock-curator/README.md new file mode 100644 index 0000000..ad36ec0 --- /dev/null +++ b/demo-distributed-lock/demo-distributed-lock-curator/README.md @@ -0,0 +1,180 @@ +## spring-boot-demo-distributed-lock-curator + +> 此 demo 主要演示了 Spring Boot 如何基于 Curator 实现一个分布式锁 + +## 1.开发步骤 + +在 `demo-distributed-lock-api` 模块中,已经实现了基于 AOP 的分布式锁注解拦截、简单的扣减库存案例,因此本模块只需要实现以下两个接口即可。 +- `com.xkcoding.distributed.lock.api.DistributedLock` +- `com.xkcoding.distributed.lock.api.DistributedLockClient` + +### 1.1.添加依赖 + +```xml + + + com.xkcoding + demo-distributed-lock-api + 1.0.0-SNAPSHOT + + + + org.apache.curator + curator-framework + ${curator.version} + + + org.apache.curator + curator-recipes + ${curator.version} + + + + org.projectlombok + lombok + true + + +``` + +### 1.2.代码实现 + +#### 1.2.1.CuratorDistributedLock + +> 基于 Curator 实现分布式锁 + +```java +public class CuratorDistributedLock extends DistributedLock { + private final CuratorFramework curatorFramework; + private final InterProcessMutex mutex; + private static final String ROOT_PATH = "/locks"; + + protected CuratorDistributedLock(CuratorFramework curatorFramework, String lockKey, long lockTime, TimeUnit timeUnit) { + super(lockKey, lockTime, timeUnit); + this.curatorFramework = curatorFramework; + mutex = new InterProcessMutex(curatorFramework, ROOT_PATH + "/" + lockKey); + } + + @Override + public void lock() { + try { + mutex.acquire(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public boolean tryLock() { + try { + return tryLock(lockTime, timeUnit); + } catch (InterruptedException e) { + return false; + } + } + + @Override + public boolean tryLock(long time, @NotNull TimeUnit unit) throws InterruptedException { + try { + return mutex.acquire(time, unit); + } catch (Exception e) { + return false; + } + } + + @Override + public void unlock() { + try { + mutex.release(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} +``` + +#### 1.2.2.CuratorDistributedLockClient + +> 获取一把 Curator 分布式锁 + +```java +@RequiredArgsConstructor +public class CuratorDistributedLockClient implements DistributedLockClient { + private final CuratorFramework curatorFramework; + + /** + * 获取一把锁 + * + * @param lockKey 锁的标识 + * @param lockTime 锁的时间 + * @param timeUnit 锁的时间单位 + * @return 锁 + */ + @Override + public DistributedLock getLock(String lockKey, long lockTime, TimeUnit timeUnit) { + return new CuratorDistributedLock(curatorFramework, lockKey, lockTime, timeUnit); + } +} +``` + +#### 1.2.3.自动装配 + +> 替换 `demo-distributed-lock-api` 中的默认实现 + +```java +@Slf4j +@Configuration(proxyBeanMethods = false) +public class CuratorDistributedLockAutoConfiguration { + @Bean + public CuratorFramework curatorFramework() { + // 指数重试 + RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3); + // 创建 Curator + CuratorFramework client = CuratorFrameworkFactory.newClient("127.0.0.1:2181", retryPolicy); + // 启动 Curator + client.start(); + log.info("===========> curator connected <==========="); + return client; + } + + @Bean + public CuratorDistributedLockClient distributedLockClient(CuratorFramework curatorFramework) { + return new CuratorDistributedLockClient(curatorFramework); + } +} +``` + +## 2.测试 + +### 2.1.环境搭建 + +主要是 mysql 及 zookeeper 环境的搭建,这里我提供了 docker-compose 文件,方便同学们一键启动测试环境 + +```bash +$ cd demo-distributed-lock/demo-distributed-lock-curator +$ docker compose -f docker-compose.env.yml up +``` + +### 2.2.测试流程 + +这里我通过 Apache Bench 进行模拟并发场景,我也构建了一个压测镜像 `xkcoding/ab:alpine-3.16.2` ,方便同学们进行快速测试 + +> 注意:每次启动项目,都会在重置库存,你也可以手动调用 `/demo/stock/reset` 接口重置 + +#### 2.2.1.测试无分布式锁下高并发请求是否会发生超卖 + +1. 把 `CuratorDistributedLockAutoConfiguration` 类全部注释掉,这将不会装配 Curator 分布式锁 +2. 启动 `CuratorDistributedLockApplication` +3. 模拟 5000 请求数 100 并发的压测环境 `docker run -it --rm xkcoding/ab:alpine-3.16.2 ab -n 5000 -c 100 http://${替换成你电脑的内网IP}:8080/demo/stock/reduce` +4. 等待压测结束,前往数据库查看库存是否从 5000 减为 0 + +#### 2.2.2.测试 Curator 分布式锁 + +1. 把 `CuratorDistributedLockAutoConfiguration` 类的注释解开,此时 Spring Boot 会自动装配我们的 Redisson 分布式锁 +2. 再次启动 `CuratorDistributedLockApplication` +3. 再次模拟 5000 请求数 100 并发的压测环境 `docker run -it --rm xkcoding/ab:alpine-3.16.2 ab -n 5000 -c 100 http://${替换成你电脑的内网IP}:8080/demo/stock/reduce` +4. 等待压测结束,前往数据库查看库存是否从 5000 减为 0 + +## 3.参考 + +- [Curator](https://curator.apache.org/) diff --git a/demo-distributed-lock/demo-distributed-lock-curator/docker-compose.env.yml b/demo-distributed-lock/demo-distributed-lock-curator/docker-compose.env.yml new file mode 100644 index 0000000..fcd4b62 --- /dev/null +++ b/demo-distributed-lock/demo-distributed-lock-curator/docker-compose.env.yml @@ -0,0 +1,14 @@ +version: "3.8" + +services: + mysql: + image: mysql:8.0.30 + ports: + - "3306:3306" + environment: + - MYSQL_ROOT_PASSWORD=root + - MYSQL_DATABASE=spring-boot-demo + zookeeper: + image: zookeeper:3.8.0 + ports: + - "2181:2181" diff --git a/demo-distributed-lock/demo-distributed-lock-curator/pom.xml b/demo-distributed-lock/demo-distributed-lock-curator/pom.xml new file mode 100644 index 0000000..8069d2d --- /dev/null +++ b/demo-distributed-lock/demo-distributed-lock-curator/pom.xml @@ -0,0 +1,45 @@ + + + + com.xkcoding + demo-distributed-lock + 1.0.0-SNAPSHOT + + + 4.0.0 + + demo-distributed-lock-curator + + + 17 + 5.3.0 + + + + + com.xkcoding + demo-distributed-lock-api + 1.0.0-SNAPSHOT + + + + org.apache.curator + curator-framework + ${curator.version} + + + org.apache.curator + curator-recipes + ${curator.version} + + + + org.projectlombok + lombok + true + + + + diff --git a/demo-distributed-lock/demo-distributed-lock-curator/src/main/java/com/xkcoding/distributed/lock/CuratorDistributedLockApplication.java b/demo-distributed-lock/demo-distributed-lock-curator/src/main/java/com/xkcoding/distributed/lock/CuratorDistributedLockApplication.java new file mode 100644 index 0000000..712bba0 --- /dev/null +++ b/demo-distributed-lock/demo-distributed-lock-curator/src/main/java/com/xkcoding/distributed/lock/CuratorDistributedLockApplication.java @@ -0,0 +1,21 @@ +package com.xkcoding.distributed.lock; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + *

+ * 启动器 + *

+ * + * @author yangkai.shen + * @date 2022-09-03 13:00 + */ +@SpringBootApplication +public class CuratorDistributedLockApplication { + + public static void main(String[] args) { + SpringApplication.run(CuratorDistributedLockApplication.class, args); + } + +} diff --git a/demo-distributed-lock/demo-distributed-lock-curator/src/main/java/com/xkcoding/distributed/lock/autoconfigure/CuratorDistributedLock.java b/demo-distributed-lock/demo-distributed-lock-curator/src/main/java/com/xkcoding/distributed/lock/autoconfigure/CuratorDistributedLock.java new file mode 100644 index 0000000..e33ef4a --- /dev/null +++ b/demo-distributed-lock/demo-distributed-lock-curator/src/main/java/com/xkcoding/distributed/lock/autoconfigure/CuratorDistributedLock.java @@ -0,0 +1,64 @@ +package com.xkcoding.distributed.lock.autoconfigure; + +import com.xkcoding.distributed.lock.api.DistributedLock; +import org.apache.curator.framework.CuratorFramework; +import org.apache.curator.framework.recipes.locks.InterProcessMutex; +import org.jetbrains.annotations.NotNull; + +import java.util.concurrent.TimeUnit; + +/** + *

+ * 基于 Curator 实现的分布式锁 + *

+ * + * @author yangkai.shen + * @date 2022-09-03 13:03 + */ +public class CuratorDistributedLock extends DistributedLock { + private final CuratorFramework curatorFramework; + private final InterProcessMutex mutex; + private static final String ROOT_PATH = "/locks"; + + protected CuratorDistributedLock(CuratorFramework curatorFramework, String lockKey, long lockTime, TimeUnit timeUnit) { + super(lockKey, lockTime, timeUnit); + this.curatorFramework = curatorFramework; + mutex = new InterProcessMutex(curatorFramework, ROOT_PATH + "/" + lockKey); + } + + @Override + public void lock() { + try { + mutex.acquire(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public boolean tryLock() { + try { + return tryLock(lockTime, timeUnit); + } catch (InterruptedException e) { + return false; + } + } + + @Override + public boolean tryLock(long time, @NotNull TimeUnit unit) throws InterruptedException { + try { + return mutex.acquire(time, unit); + } catch (Exception e) { + return false; + } + } + + @Override + public void unlock() { + try { + mutex.release(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/demo-distributed-lock/demo-distributed-lock-curator/src/main/java/com/xkcoding/distributed/lock/autoconfigure/CuratorDistributedLockAutoConfiguration.java b/demo-distributed-lock/demo-distributed-lock-curator/src/main/java/com/xkcoding/distributed/lock/autoconfigure/CuratorDistributedLockAutoConfiguration.java new file mode 100644 index 0000000..2d6eaaa --- /dev/null +++ b/demo-distributed-lock/demo-distributed-lock-curator/src/main/java/com/xkcoding/distributed/lock/autoconfigure/CuratorDistributedLockAutoConfiguration.java @@ -0,0 +1,38 @@ +package com.xkcoding.distributed.lock.autoconfigure; + +import lombok.extern.slf4j.Slf4j; +import org.apache.curator.RetryPolicy; +import org.apache.curator.framework.CuratorFramework; +import org.apache.curator.framework.CuratorFrameworkFactory; +import org.apache.curator.retry.ExponentialBackoffRetry; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + *

+ * 基于Curator分布式锁自动装配类 + *

+ * + * @author yangkai.shen + * @date 2022-09-03 13:05 + */ +@Slf4j +@Configuration(proxyBeanMethods = false) +public class CuratorDistributedLockAutoConfiguration { + @Bean + public CuratorFramework curatorFramework() { + // 指数重试 + RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3); + // 创建 Curator + CuratorFramework client = CuratorFrameworkFactory.newClient("127.0.0.1:2181", retryPolicy); + // 启动 Curator + client.start(); + log.info("===========> curator connected <==========="); + return client; + } + + @Bean + public CuratorDistributedLockClient distributedLockClient(CuratorFramework curatorFramework) { + return new CuratorDistributedLockClient(curatorFramework); + } +} diff --git a/demo-distributed-lock/demo-distributed-lock-curator/src/main/java/com/xkcoding/distributed/lock/autoconfigure/CuratorDistributedLockClient.java b/demo-distributed-lock/demo-distributed-lock-curator/src/main/java/com/xkcoding/distributed/lock/autoconfigure/CuratorDistributedLockClient.java new file mode 100644 index 0000000..383f30e --- /dev/null +++ b/demo-distributed-lock/demo-distributed-lock-curator/src/main/java/com/xkcoding/distributed/lock/autoconfigure/CuratorDistributedLockClient.java @@ -0,0 +1,34 @@ +package com.xkcoding.distributed.lock.autoconfigure; + +import com.xkcoding.distributed.lock.api.DistributedLock; +import com.xkcoding.distributed.lock.api.DistributedLockClient; +import lombok.RequiredArgsConstructor; +import org.apache.curator.framework.CuratorFramework; + +import java.util.concurrent.TimeUnit; + +/** + *

+ * 获得一把 Curator 分布式锁 + *

+ * + * @author yangkai.shen + * @date 2022-09-03 13:04 + */ +@RequiredArgsConstructor +public class CuratorDistributedLockClient implements DistributedLockClient { + private final CuratorFramework curatorFramework; + + /** + * 获取一把锁 + * + * @param lockKey 锁的标识 + * @param lockTime 锁的时间 + * @param timeUnit 锁的时间单位 + * @return 锁 + */ + @Override + public DistributedLock getLock(String lockKey, long lockTime, TimeUnit timeUnit) { + return new CuratorDistributedLock(curatorFramework, lockKey, lockTime, timeUnit); + } +} diff --git a/demo-distributed-lock/demo-distributed-lock-curator/src/main/resources/application.yml b/demo-distributed-lock/demo-distributed-lock-curator/src/main/resources/application.yml new file mode 100644 index 0000000..113bd15 --- /dev/null +++ b/demo-distributed-lock/demo-distributed-lock-curator/src/main/resources/application.yml @@ -0,0 +1,16 @@ +server: + port: 8080 + servlet: + context-path: /demo +spring: + sql: + init: + continue-on-error: true + mode: always + schema-locations: + - "classpath:db/schema.sql" + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://localhost:3306/spring-boot-demo + username: root + password: root diff --git a/demo-distributed-lock/pom.xml b/demo-distributed-lock/pom.xml index ca50e17..f3112d5 100644 --- a/demo-distributed-lock/pom.xml +++ b/demo-distributed-lock/pom.xml @@ -20,10 +20,11 @@ demo-distributed-lock-api + demo-distributed-lock-mysql demo-distributed-lock-redis demo-distributed-lock-redisson demo-distributed-lock-zookeeper - demo-distributed-lock-mysql + demo-distributed-lock-curator