From a6c1902593a802dc23febc9c9bab648822b5cb79 Mon Sep 17 00:00:00 2001
From: "Yangkai.Shen" <237497819@qq.com>
Date: Sat, 3 Sep 2022 16:31:42 +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=20Red?=
=?UTF-8?q?isTemplate=20=E5=AE=9E=E7=8E=B0=E5=88=86=E5=B8=83=E5=BC=8F?=
=?UTF-8?q?=E9=94=81=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-redis/README.md | 240 +++++++++++++++++++++
.../docker-compose.env.yml | 14 ++
.../demo-distributed-lock-redis/pom.xml | 19 ++
.../lock/RedisDistributedLockApplication.java | 9 +-
.../lock/autoconfigure/RedisDistributedLock.java | 134 ++++++++++++
.../RedisDistributedLockAutoConfiguration.java | 23 ++
.../autoconfigure/RedisDistributedLockClient.java | 44 ++++
.../src/main/resources/application.yml | 19 ++
8 files changed, 497 insertions(+), 5 deletions(-)
create mode 100644 demo-distributed-lock/demo-distributed-lock-redis/README.md
create mode 100644 demo-distributed-lock/demo-distributed-lock-redis/docker-compose.env.yml
rename demo-zookeeper/src/main/java/com/xkcoding/zookeeper/SpringBootDemoZookeeperApplication.java => demo-distributed-lock/demo-distributed-lock-redis/src/main/java/com/xkcoding/distributed/lock/RedisDistributedLockApplication.java (56%)
create mode 100644 demo-distributed-lock/demo-distributed-lock-redis/src/main/java/com/xkcoding/distributed/lock/autoconfigure/RedisDistributedLock.java
create mode 100644 demo-distributed-lock/demo-distributed-lock-redis/src/main/java/com/xkcoding/distributed/lock/autoconfigure/RedisDistributedLockAutoConfiguration.java
create mode 100644 demo-distributed-lock/demo-distributed-lock-redis/src/main/java/com/xkcoding/distributed/lock/autoconfigure/RedisDistributedLockClient.java
create mode 100644 demo-distributed-lock/demo-distributed-lock-redis/src/main/resources/application.yml
diff --git a/demo-distributed-lock/demo-distributed-lock-redis/README.md b/demo-distributed-lock/demo-distributed-lock-redis/README.md
new file mode 100644
index 0000000..f522819
--- /dev/null
+++ b/demo-distributed-lock/demo-distributed-lock-redis/README.md
@@ -0,0 +1,240 @@
+## spring-boot-demo-distributed-lock-redis
+
+> 此 demo 主要演示了 Spring Boot 如何基于 RedisTemplate 实现一个分布式锁
+
+## 1.开发步骤
+
+在 `demo-distributed-lock-api` 模块中,已经实现了基于 AOP 的分布式锁注解拦截、简单的扣减库存案例,因此本模块只需要实现以下两个接口即可。
+- `com.xkcoding.distributed.lock.api.DistributedLock`
+- `com.xkcoding.distributed.lock.api.DistributedLockClient`
+
+### 1.1.添加依赖
+
+```xml
+
+ * 基于 RedisTemplate 实现的分布式锁 + *
+ * + * @author yangkai.shen + * @date 2022-09-03 12:33 + */ +public class RedisDistributedLock extends DistributedLock { + private final StringRedisTemplate redisTemplate; + /** + * 锁的唯一标识,格式:主机标识(UUID)+线程编号 + * 防误删 + */ + private final String uniqueId; + + protected RedisDistributedLock(StringRedisTemplate redisTemplate, String uniqueIdPrefix, String lockKey, long lockTime, + TimeUnit timeUnit) { + super(lockKey, lockTime, timeUnit); + this.redisTemplate = redisTemplate; + this.uniqueId = uniqueIdPrefix + ":" + Thread.currentThread().getId(); + } + + @Override + public void lock() { + // 加锁失败,自旋阻塞 + while (!tryLock()) { + try { + TimeUnit.MILLISECONDS.sleep(50); + } catch (InterruptedException 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 { + String script = "if redis.call('exists', KEYS[1]) == 0 or redis.call('hexists', KEYS[1], ARGV[1]) == 1 " + + "then " + + " redis.call('hincrby', KEYS[1], ARGV[1], 1) " + + " redis.call('expire', KEYS[1], ARGV[2]) " + + " return 1 " + + "else " + + " return 0 " + + "end"; + + long expire = unit.toSeconds(time); + Boolean getLock = Optional.ofNullable( + redisTemplate.execute( + new DefaultRedisScript<>(script, Boolean.class), + Collections.singletonList(lockKey), + uniqueId, String.valueOf(expire))) + .orElse(Boolean.FALSE); + // 如果获得锁,开启自动续期 + if (getLock) { + renewLockTime(time, unit); + } + return getLock; + } + + private void renewLockTime(long time, TimeUnit unit) { + String script = "if redis.call('hexists', KEYS[1], ARGV[1]) == 1 " + + "then " + + " return redis.call('expire', KEYS[1], ARGV[2]) " + + "else " + + " return 0 " + + "end"; + + long expire = unit.toSeconds(time); + new Timer().schedule(new TimerTask() { + @Override + public void run() { + Boolean renewed = Optional.ofNullable( + redisTemplate.execute( + new DefaultRedisScript<>(script, Boolean.class), + Collections.singletonList(lockKey), + uniqueId, String.valueOf(expire)) + ).orElse(Boolean.FALSE); + // 续期成功,代表未被解锁,则需要进行下一次续期 + if (renewed) { + renewLockTime(time, unit); + } + } + }, expire * 1000 / 3); + } + + @Override + public void unlock() { + String script = "if redis.call('hexists', KEYS[1], ARGV[1]) == 0 " + + "then " + + " return nil " + + "elseif redis.call('hincrby', KEYS[1], ARGV[1], -1) == 0 " + + "then " + + " return redis.call('del', KEYS[1]) " + + "else " + + " redis.call('expire', KEYS[1], ARGV[2]) " + + " return 0 " + + "end"; + + // 如果解锁,发现是重入的,需要重新续期 + long expire = timeUnit.toSeconds(lockTime); + Long flag = this.redisTemplate.execute( + new DefaultRedisScript<>(script, Long.class), + Collections.singletonList(lockKey), + uniqueId, String.valueOf(expire) + ); + if (flag == null) { + throw new IllegalMonitorStateException("this lock doesn't belong to you!"); + } + } + +} diff --git a/demo-distributed-lock/demo-distributed-lock-redis/src/main/java/com/xkcoding/distributed/lock/autoconfigure/RedisDistributedLockAutoConfiguration.java b/demo-distributed-lock/demo-distributed-lock-redis/src/main/java/com/xkcoding/distributed/lock/autoconfigure/RedisDistributedLockAutoConfiguration.java new file mode 100644 index 0000000..48d4f57 --- /dev/null +++ b/demo-distributed-lock/demo-distributed-lock-redis/src/main/java/com/xkcoding/distributed/lock/autoconfigure/RedisDistributedLockAutoConfiguration.java @@ -0,0 +1,23 @@ +package com.xkcoding.distributed.lock.autoconfigure; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.core.StringRedisTemplate; + +/** + *+ * 基于 RedisTemplate 分布式锁自动装配类 + *
+ * + * @author yangkai.shen + * @date 2022-09-03 15:43 + */ +@Configuration(proxyBeanMethods = false) +public class RedisDistributedLockAutoConfiguration { + + @Bean + public RedisDistributedLockClient distributedLockClient(StringRedisTemplate redisTemplate) { + return new RedisDistributedLockClient(redisTemplate); + } + +} diff --git a/demo-distributed-lock/demo-distributed-lock-redis/src/main/java/com/xkcoding/distributed/lock/autoconfigure/RedisDistributedLockClient.java b/demo-distributed-lock/demo-distributed-lock-redis/src/main/java/com/xkcoding/distributed/lock/autoconfigure/RedisDistributedLockClient.java new file mode 100644 index 0000000..b6373f8 --- /dev/null +++ b/demo-distributed-lock/demo-distributed-lock-redis/src/main/java/com/xkcoding/distributed/lock/autoconfigure/RedisDistributedLockClient.java @@ -0,0 +1,44 @@ +package com.xkcoding.distributed.lock.autoconfigure; + +import com.xkcoding.distributed.lock.api.DistributedLock; +import com.xkcoding.distributed.lock.api.DistributedLockClient; +import org.springframework.data.redis.core.StringRedisTemplate; + +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +/** + *+ * 获取一把 RedisTemplate 实现的分布式锁 + *
+ * + * @author yangkai.shen + * @date 2022-09-03 12:35 + */ +public class RedisDistributedLockClient implements DistributedLockClient { + private final StringRedisTemplate redisTemplate; + + /** + * 唯一标识前缀,用于集群环境下的主机标识,会在 Bean 初始化到 Spring 容器的时候设置 + */ + private final String uniqueIdPrefix; + + public RedisDistributedLockClient(StringRedisTemplate redisTemplate) { + this.redisTemplate = redisTemplate; + uniqueIdPrefix = UUID.randomUUID().toString(); + } + + + /** + * 获取一把锁 + * + * @param lockKey 锁的标识 + * @param lockTime 锁的时间 + * @param timeUnit 锁的时间单位 + * @return 锁 + */ + @Override + public DistributedLock getLock(String lockKey, long lockTime, TimeUnit timeUnit) { + return new RedisDistributedLock(redisTemplate, uniqueIdPrefix, lockKey, lockTime, timeUnit); + } +} diff --git a/demo-distributed-lock/demo-distributed-lock-redis/src/main/resources/application.yml b/demo-distributed-lock/demo-distributed-lock-redis/src/main/resources/application.yml new file mode 100644 index 0000000..6d472b7 --- /dev/null +++ b/demo-distributed-lock/demo-distributed-lock-redis/src/main/resources/application.yml @@ -0,0 +1,19 @@ +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 + redis: + host: 127.0.0.1 + port: 6379