diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a3e1a7..18ef1b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,3 +45,11 @@ | 序号 | 变更类型 | 说明 | 时间 | 备注 | |:---|:---|:---|:---|:--| | 1 | A | 整合 spring | 2022-12-7 14:45:40 | | + +# release_1.2.0 + +| 序号 | 变更类型 | 说明 | 时间 | 备注 | +|:---|:---|:---|:---|:--| +| 1 | A | 添加 keyFormat | 2022-12-8 14:45:40 | | +| 2 | A | 添加锁释放失败 | 2022-12-8 14:45:40 | | +| 3 | O | 加锁处理优化,接口统一优化 | 2022-12-8 14:45:40 | | diff --git a/README.md b/README.md index 6fdc148..9a82742 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # 项目简介 -为 java 设计的锁。 +[lock](https://github.com/houbb/lock) 为 java 设计的分布式锁。 [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.github.houbb/lock/badge.svg)](http://mvnrepository.com/artifact/com.github.houbb/lock) [![Build Status](https://www.travis-ci.org/houbb/lock.svg?branch=master)](https://www.travis-ci.org/houbb/lock?branch=master) @@ -8,15 +8,15 @@ ## 目的 +- 开箱即用,支持注解式和过程式调用 + - 基于 redis 的分布式锁 - 整合 spring - 整合 spring-boot -- 开箱即用,支持注解。 - -- 支持多种 redis 的声明方式 +- 支持多种 redis 的整合方式 # 变更日志 @@ -36,7 +36,7 @@ maven 3.x+ com.github.houbb lock-core - 1.1.0 + 1.2.0 ``` @@ -45,22 +45,36 @@ maven 3.x+ 基于本地 redis 的测试案例。 ```java -ILock lock = LockBs.newInstance() - .init(); - -String key = "ddd"; -try { - // 加锁 - lock.tryLock(key); - System.out.println("业务处理"); -} catch (Exception e) { - throw new RuntimeException(e); -} finally { - // 释放锁 - lock.unlock(key); +public void helloTest() { + ILock lock = LockBs.newInstance(); + String key = "ddd"; + try { + // 加锁 + lock.tryLock(key); + System.out.println("业务处理"); + } catch (Exception e) { + throw new RuntimeException(e); + } finally { + // 释放锁 + lock.unlock(key); + } } ``` +### 配置化 + +为了便于拓展,LockBs 的配置支持自定义: + +```java +LockBs.newInstance() + .id(Ids.uuid32()) //id 生成策略 + .cache(JedisRedisServiceFactory.pooled("127.0.0.1", 6379)) //缓存策略 + .lockSupport(new RedisLockSupport()) // 锁实现策略 + .lockKeyFormat(new LockKeyFormat()) // 针对 key 的格式化处理策略 + .lockReleaseFailHandler(new LockReleaseFailHandler()) //释放锁失败处理 + ; +``` + # 整合 spring ## maven 引入 @@ -69,7 +83,7 @@ try { com.github.houbb lock-spring - 1.1.0 + 1.2.0 ``` @@ -87,9 +101,58 @@ public class SpringConfig { } ``` +`EnableLock` 注解说明,和引导类对应: + +```java +public @interface EnableLock { + + /** + * 唯一标识生成策略 + * @return 结果 + * @since 1.1.0 + */ + String id() default "lockId"; + + /** + * 缓存实现策略 bean 名称 + * + * 默认引入 redis-config 中的配置 + * + * @return 实现 + * @since 1.1.0 + */ + String cache() default "springRedisService"; + + /** + * 加锁 key 格式化策略 + * @return 策略 + * @since 1.2.0 + */ + String lockKeyFormat() default "lockKeyFormat"; + + /** + * 锁释放失败处理类 + * @return 结果 + * @since 1.2.0 + */ + String lockReleaseFailHandler() default "lockReleaseFailHandler"; + +} +``` + +其中 `springRedisService` 使用的是 [redis-config](https://github.com/houbb/redis-config) 中的实现。 + +redis 的配置信息如下: + +| 配置 | 说明 | 默认值 +|:---|:---|:----| +| redis.address | redis 地址 | 127.0.0.1 | +| redis.port | redis 端口 | 6379 | +| redis.password | redis 密码 | | + ### 使用 LockBs -我们可以直接 `LockBs` 的引导类。 +我们可以直接 `LockBs` 的引导类,这种适合一些更加灵活的场景。 ```java @ContextConfiguration(classes = SpringConfig.class) @@ -124,7 +187,7 @@ public class SpringServiceRawTest { 当然,我们可以在方法上直接指定注解 `@Lock`,使用更加方便。 -支持 SPEL 表达式。 +直接使用,AOP 切面生效即可。 ```java @Service @@ -140,9 +203,44 @@ public class UserService { } ``` -直接使用,AOP 切面生效即可。 +`@Lock` 属性说明,value 用于指定 key,支持 SPEL 表达式。 -# springboot 整合 +其他属性,和引导类的方法参数一一对应。 + +```java +public @interface Lock { + + /** + * 缓存的 key 策略,支持 SpEL + * @return 结果 + */ + String value() default ""; + + /** + * 时间单位 + * @return 单位 + * @since 1.2.0 + */ + TimeUnit timeUnit() default TimeUnit.SECONDS; + + /** + * 等待锁时间 + * @return 等待锁时间 + * @since 1.2.0 + */ + long waitLockTime() default 10; + + /** + * 业务加锁时间 + * @return 加锁时间 + * @since 1.2.0 + */ + long lockTime() default 60; + +} +``` + +# spring boot 整合 ## maven 引入 @@ -150,7 +248,7 @@ public class UserService { com.github.houbb lock-springboot-starter - 1.1.0 + 1.2.0 ``` @@ -164,5 +262,4 @@ public class UserService { 持有锁的线程可以多次获取锁 -- [x] 分布式锁注解支持 - +- [x] 分布式锁注解支持 \ No newline at end of file diff --git a/lock-api/src/main/java/com/github/houbb/lock/api/core/ILock.java b/lock-api/src/main/java/com/github/houbb/lock/api/core/ILock.java index e0b80a4..f6ae518 100644 --- a/lock-api/src/main/java/com/github/houbb/lock/api/core/ILock.java +++ b/lock-api/src/main/java/com/github/houbb/lock/api/core/ILock.java @@ -12,13 +12,33 @@ public interface ILock { /** * 尝试加锁,如果失败,会一直尝试。 * - * @param time 时间 - * @param unit 当为 * @param key key + * @param timeUnit 时间单位 + * @param waitLockTime 等待锁时间 + * @param lockTime 加锁时间 * @return 返回 * @since 0.0.1 */ - boolean tryLock(long time, TimeUnit unit, String key); + boolean tryLock(String key, TimeUnit timeUnit, long lockTime, long waitLockTime); + + /** + * 尝试加锁,只加锁一次 + * @param key key + * @param timeUnit 时间单位 + * @param lockTime 加锁时间 + * @return 返回 + * @since 0.0.1 + */ + boolean tryLock(String key, TimeUnit timeUnit, long lockTime); + + /** + * 尝试加锁,只加锁一次 + * @param key key + * @param lockTime 加锁时间 + * @return 返回 + * @since 0.0.1 + */ + boolean tryLock(String key, long lockTime); /** * 尝试加锁,只加锁一次 @@ -32,6 +52,7 @@ public interface ILock { * 解锁 * @param key key * @since 0.0.1 + * @return 是否释放锁成功 */ boolean unlock(String key); diff --git a/lock-api/src/main/java/com/github/houbb/lock/api/core/ILockKeyFormat.java b/lock-api/src/main/java/com/github/houbb/lock/api/core/ILockKeyFormat.java new file mode 100644 index 0000000..d326afb --- /dev/null +++ b/lock-api/src/main/java/com/github/houbb/lock/api/core/ILockKeyFormat.java @@ -0,0 +1,20 @@ +package com.github.houbb.lock.api.core; + +/** + * 分布式锁 KEY 格式化处理 + * + * @author binbin.hou + * @since 0.0.1 + */ +public interface ILockKeyFormat { + + /** + * 对 key 进行格式化处理,避免缓存命名空间处理等问题 + * + * @param formatContext 上下文 + * @return 返回 + * @since 1.2.0 + */ + String format(ILockKeyFormatContext formatContext); + +} diff --git a/lock-api/src/main/java/com/github/houbb/lock/api/core/ILockKeyFormatContext.java b/lock-api/src/main/java/com/github/houbb/lock/api/core/ILockKeyFormatContext.java new file mode 100644 index 0000000..b8b6a79 --- /dev/null +++ b/lock-api/src/main/java/com/github/houbb/lock/api/core/ILockKeyFormatContext.java @@ -0,0 +1,19 @@ +package com.github.houbb.lock.api.core; + +/** + * 分布式锁 KEY 格式化处理上下文 + * + * @author binbin.hou + * @since 0.0.1 + */ +public interface ILockKeyFormatContext { + + /** + * 原始的 key + * + * @return 返回 + * @since 1.2.0 + */ + String rawKey(); + +} diff --git a/lock-api/src/main/java/com/github/houbb/lock/api/core/ILockReleaseFailHandler.java b/lock-api/src/main/java/com/github/houbb/lock/api/core/ILockReleaseFailHandler.java new file mode 100644 index 0000000..24763bc --- /dev/null +++ b/lock-api/src/main/java/com/github/houbb/lock/api/core/ILockReleaseFailHandler.java @@ -0,0 +1,18 @@ +package com.github.houbb.lock.api.core; + +/** + * 释放锁失败处理类 + * + * @author binbin.hou + * @since 1.2.0 + */ +public interface ILockReleaseFailHandler { + + /** + * 处理 + * @param context 上下文 + * @since 1.2.0 + */ + void handle(ILockReleaseFailHandlerContext context); + +} diff --git a/lock-api/src/main/java/com/github/houbb/lock/api/core/ILockReleaseFailHandlerContext.java b/lock-api/src/main/java/com/github/houbb/lock/api/core/ILockReleaseFailHandlerContext.java new file mode 100644 index 0000000..ce441a2 --- /dev/null +++ b/lock-api/src/main/java/com/github/houbb/lock/api/core/ILockReleaseFailHandlerContext.java @@ -0,0 +1,18 @@ +package com.github.houbb.lock.api.core; + +/** + * 释放锁失败处理类 + * + * @author binbin.hou + * @since 1.2.0 + */ +public interface ILockReleaseFailHandlerContext { + + /** + * 释放锁对应的 key + * @return 结果 + * @since 1.2.0 + */ + String key(); + +} diff --git a/lock-api/src/main/java/com/github/houbb/lock/api/core/ILockSupport.java b/lock-api/src/main/java/com/github/houbb/lock/api/core/ILockSupport.java index 38b439b..37ab907 100644 --- a/lock-api/src/main/java/com/github/houbb/lock/api/core/ILockSupport.java +++ b/lock-api/src/main/java/com/github/houbb/lock/api/core/ILockSupport.java @@ -12,30 +12,21 @@ public interface ILockSupport { /** * 尝试加锁,如果失败,会一直尝试。 * - * @param time 时间 - * @param unit 单位 - * @param key key + * 1. 如果等待时间小于等于0,则直接执行加锁 + * 2. 如果等待时间大于等于0,则尝试循环等待。 + * * @param context 上下文 * @return 返回 * @since 0.0.1 */ - boolean tryLock(long time, TimeUnit unit, String key, final ILockSupportContext context); - - /** - * 尝试加锁,只加锁一次 - * @param key key - * @param context 上下文 - * @return 返回 - * @since 0.0.1 - */ - boolean tryLock(String key, final ILockSupportContext context); + boolean tryLock(final ILockSupportContext context); /** * 解锁 - * @param key key * @param context 上下文 * @since 0.0.1 + * @return 结果 */ - boolean unlock(String key, final ILockSupportContext context); + boolean unlock(final ILockSupportContext context); } diff --git a/lock-api/src/main/java/com/github/houbb/lock/api/core/ILockSupportContext.java b/lock-api/src/main/java/com/github/houbb/lock/api/core/ILockSupportContext.java index 7cff82f..ca57ffd 100644 --- a/lock-api/src/main/java/com/github/houbb/lock/api/core/ILockSupportContext.java +++ b/lock-api/src/main/java/com/github/houbb/lock/api/core/ILockSupportContext.java @@ -3,6 +3,8 @@ package com.github.houbb.lock.api.core; import com.github.houbb.common.cache.api.service.ICommonCacheService; import com.github.houbb.id.api.Id; +import java.util.concurrent.TimeUnit; + /** * 分布式锁接口定义 * @author binbin.hou @@ -11,21 +13,60 @@ import com.github.houbb.id.api.Id; public interface ILockSupportContext { /** + * 标识 + * * @return 标识策略 * @since 0.0.4 */ Id id(); /** + * 缓存 * @return 缓存策略 * @since 0.0.4 */ ICommonCacheService cache(); /** - * 锁的过期时间 - * @return 结果 + * 时间单位 + * @return 时间单位 */ - int lockExpireMills(); + TimeUnit timeUnit(); + + /** + * 加锁时间 + * @return 时间 + * @since 1.2.0 + */ + long lockTime(); + + /** + * 等待加锁时间 + * @return 等待时间 + * @since 1.2.0 + */ + long waitLockTime(); + + /** + * 缓存对应的 key + * @return 结果 + * @since 1.2.0 + */ + String key(); + + /** + * 锁 key 格式化 + * @since 1.2.0 + * @return 格式化 + */ + ILockKeyFormat lockKeyFormat(); + + /** + * 锁释放失败处理类 + * @since 1.2.0 + * @return 失败处理 + */ + ILockReleaseFailHandler lockReleaseFailHandler(); + } diff --git a/lock-core/src/main/java/com/github/houbb/lock/core/bs/LockBs.java b/lock-core/src/main/java/com/github/houbb/lock/core/bs/LockBs.java index 862aec6..ad3f2d8 100644 --- a/lock-core/src/main/java/com/github/houbb/lock/core/bs/LockBs.java +++ b/lock-core/src/main/java/com/github/houbb/lock/core/bs/LockBs.java @@ -4,10 +4,9 @@ import com.github.houbb.common.cache.api.service.ICommonCacheService; import com.github.houbb.heaven.util.common.ArgUtil; import com.github.houbb.id.api.Id; import com.github.houbb.id.core.core.Ids; -import com.github.houbb.lock.api.core.ILock; -import com.github.houbb.lock.api.core.ILockSupport; -import com.github.houbb.lock.api.core.ILockSupportContext; -import com.github.houbb.lock.core.constant.LockConst; +import com.github.houbb.lock.api.core.*; +import com.github.houbb.lock.core.support.format.LockKeyFormat; +import com.github.houbb.lock.core.support.handler.LockReleaseFailHandler; import com.github.houbb.lock.core.support.lock.LockSupportContext; import com.github.houbb.lock.core.support.lock.RedisLockSupport; import com.github.houbb.redis.config.core.factory.JedisRedisServiceFactory; @@ -20,48 +19,49 @@ import java.util.concurrent.TimeUnit; * @author binbin.hou * @since 0.0.4 */ -public final class LockBs implements ILock{ +public final class LockBs implements ILock { - private LockBs(){} + private LockBs() { + } public static LockBs newInstance() { return new LockBs(); } - /** - * 加锁锁定时间 - * @since 0.0.4 - */ - private int lockExpireMills = LockConst.DEFAULT_EXPIRE_MILLS; - /** * 标识策略 + * * @since 0.0.4 */ private Id id = Ids.uuid32(); /** * 缓存策略 + * * @since 0.0.4 */ private ICommonCacheService cache = JedisRedisServiceFactory.pooled("127.0.0.1", 6379); /** * 锁支持策略 + * * @since 1.0.0 */ private ILockSupport lockSupport = new RedisLockSupport(); /** - * 锁上下文 - * @since 1.0.0 + * 锁 key 格式化 + * + * @since 1.2.0 */ - private ILockSupportContext lockSupportContext = null; + private ILockKeyFormat lockKeyFormat = new LockKeyFormat(); - public LockBs lockExpireMills(int lockExpireMills) { - this.lockExpireMills = lockExpireMills; - return this; - } + /** + * 锁释放失败处理类 + * + * @since 1.2.0 + */ + private ILockReleaseFailHandler lockReleaseFailHandler = new LockReleaseFailHandler(); public LockBs id(Id id) { ArgUtil.notNull(id, "id"); @@ -84,48 +84,67 @@ public final class LockBs implements ILock{ return this; } + public LockBs lockKeyFormat(ILockKeyFormat lockKeyFormat) { + ArgUtil.notNull(lockKeyFormat, "lockKeyFormat"); - /** - * 初始化 - */ - public LockBs init() { - this.lockSupportContext = LockSupportContext.newInstance() - .id(id) - .cache(cache) - .lockExpireMills(lockExpireMills); - + this.lockKeyFormat = lockKeyFormat; return this; } + public LockBs lockReleaseFailHandler(ILockReleaseFailHandler lockReleaseFailHandler) { + ArgUtil.notNull(lockReleaseFailHandler, "lockReleaseFailHandler"); + + this.lockReleaseFailHandler = lockReleaseFailHandler; + return this; + } + + public ILockReleaseFailHandler lockReleaseFailHandler() { + return lockReleaseFailHandler; + } + @Override - public boolean tryLock(long time, TimeUnit unit, String key) { - ArgUtil.notEmpty(key, "key"); - this.checkInitStatus(); + public boolean tryLock(String key, TimeUnit timeUnit, long lockTime, long waitLockTime) { + ILockSupportContext supportContext = buildLockSupportContext(key, timeUnit, lockTime, waitLockTime); + return this.lockSupport.tryLock(supportContext); + } + @Override + public boolean tryLock(String key, TimeUnit timeUnit, long lockTime) { + return this.tryLock(key, timeUnit, lockTime, 0); + } - return this.lockSupport.tryLock(time, unit, key, lockSupportContext); + @Override + public boolean tryLock(String key, long lockTime) { + return this.tryLock(key, TimeUnit.SECONDS, lockTime); } @Override public boolean tryLock(String key) { - ArgUtil.notEmpty(key, "key"); - this.checkInitStatus(); - - return this.lockSupport.tryLock(key, lockSupportContext); + return this.tryLock(key, 60); } @Override public boolean unlock(String key) { + ILockSupportContext supportContext = buildLockSupportContext(key, TimeUnit.SECONDS, 0, 0); + return this.lockSupport.unlock(supportContext); + } + + /** + * 构建上下文 + * + * @param key key + * @param timeUnit 时间 + * @param lockTime 加锁时间 + * @param waitLockTime 等待加锁时间 + * @return 结果 + * @since 1.0.0 + */ + private ILockSupportContext buildLockSupportContext(String key, TimeUnit timeUnit, long lockTime, long waitLockTime) { ArgUtil.notEmpty(key, "key"); - this.checkInitStatus(); + ArgUtil.notNull(timeUnit, "timeUnit"); - return this.lockSupport.unlock(key, lockSupportContext); + ILockSupportContext context = LockSupportContext.newInstance().id(id).cache(cache).lockKeyFormat(lockKeyFormat).lockReleaseFailHandler(lockReleaseFailHandler).key(key).timeUnit(timeUnit).lockTime(lockTime).waitLockTime(waitLockTime); + return context; } - - private void checkInitStatus() { - ArgUtil.notNull(lockSupportContext, "please init() first!"); - } - - } diff --git a/lock-core/src/main/java/com/github/houbb/lock/core/support/format/LockKeyFormat.java b/lock-core/src/main/java/com/github/houbb/lock/core/support/format/LockKeyFormat.java new file mode 100644 index 0000000..f9c299e --- /dev/null +++ b/lock-core/src/main/java/com/github/houbb/lock/core/support/format/LockKeyFormat.java @@ -0,0 +1,21 @@ +package com.github.houbb.lock.core.support.format; + +import com.github.houbb.lock.api.core.ILockKeyFormat; +import com.github.houbb.lock.api.core.ILockKeyFormatContext; + +/** + * 简单的格式化处理 + * @since 1.2.0 + * @author dh + */ +public class LockKeyFormat implements ILockKeyFormat { + + @Override + public String format(ILockKeyFormatContext formatContext) { + String rawKey = formatContext.rawKey(); + + String format = "DISTRIBUTED-LOCK:%s"; + return String.format(format, rawKey); + } + +} diff --git a/lock-core/src/main/java/com/github/houbb/lock/core/support/format/LockKeyFormatContext.java b/lock-core/src/main/java/com/github/houbb/lock/core/support/format/LockKeyFormatContext.java new file mode 100644 index 0000000..3e4c044 --- /dev/null +++ b/lock-core/src/main/java/com/github/houbb/lock/core/support/format/LockKeyFormatContext.java @@ -0,0 +1,33 @@ +package com.github.houbb.lock.core.support.format; + +import com.github.houbb.lock.api.core.ILockKeyFormatContext; + +/** + * 上下文 + * @since 1.2.0 + */ +public class LockKeyFormatContext implements ILockKeyFormatContext { + + + /** + * 原始的 key + * + * @since 1.2.0 + */ + private String rawKey; + + public static LockKeyFormatContext newInstance() { + return new LockKeyFormatContext(); + } + + @Override + public String rawKey() { + return rawKey; + } + + public LockKeyFormatContext rawKey(String rawKey) { + this.rawKey = rawKey; + return this; + } + +} diff --git a/lock-core/src/main/java/com/github/houbb/lock/core/support/handler/LockReleaseFailHandler.java b/lock-core/src/main/java/com/github/houbb/lock/core/support/handler/LockReleaseFailHandler.java new file mode 100644 index 0000000..673b780 --- /dev/null +++ b/lock-core/src/main/java/com/github/houbb/lock/core/support/handler/LockReleaseFailHandler.java @@ -0,0 +1,23 @@ +package com.github.houbb.lock.core.support.handler; + +import com.github.houbb.lock.api.core.ILockReleaseFailHandler; +import com.github.houbb.lock.api.core.ILockReleaseFailHandlerContext; +import com.github.houbb.log.integration.core.Log; +import com.github.houbb.log.integration.core.LogFactory; + +/** + * 释放锁失败处理类 + * + * @author binbin.hou + * @since 1.2.0 + */ +public class LockReleaseFailHandler implements ILockReleaseFailHandler { + + private static final Log LOG = LogFactory.getLog(LockReleaseFailHandler.class); + + @Override + public void handle(ILockReleaseFailHandlerContext context) { + LOG.error("[LOCK] release lock failed {}", context.key()); + } + +} diff --git a/lock-core/src/main/java/com/github/houbb/lock/core/support/handler/LockReleaseFailHandlerContext.java b/lock-core/src/main/java/com/github/houbb/lock/core/support/handler/LockReleaseFailHandlerContext.java new file mode 100644 index 0000000..e7d89d8 --- /dev/null +++ b/lock-core/src/main/java/com/github/houbb/lock/core/support/handler/LockReleaseFailHandlerContext.java @@ -0,0 +1,31 @@ +package com.github.houbb.lock.core.support.handler; + +import com.github.houbb.lock.api.core.ILockReleaseFailHandlerContext; + +/** + * 锁释放失败上下文 + * @since 1.2.0 + */ +public class LockReleaseFailHandlerContext implements ILockReleaseFailHandlerContext { + + /** + * 释放锁对应的 key + * @since 1.2.0 + */ + private String key; + + public static LockReleaseFailHandlerContext newInstance() { + return new LockReleaseFailHandlerContext(); + } + + @Override + public String key() { + return key; + } + + public LockReleaseFailHandlerContext key(String key) { + this.key = key; + return this; + } + +} diff --git a/lock-core/src/main/java/com/github/houbb/lock/core/support/lock/LockSupportContext.java b/lock-core/src/main/java/com/github/houbb/lock/core/support/lock/LockSupportContext.java index 75145d3..d58ff7b 100644 --- a/lock-core/src/main/java/com/github/houbb/lock/core/support/lock/LockSupportContext.java +++ b/lock-core/src/main/java/com/github/houbb/lock/core/support/lock/LockSupportContext.java @@ -2,7 +2,13 @@ package com.github.houbb.lock.core.support.lock; import com.github.houbb.common.cache.api.service.ICommonCacheService; import com.github.houbb.id.api.Id; +import com.github.houbb.lock.api.core.ILockKeyFormat; +import com.github.houbb.lock.api.core.ILockReleaseFailHandler; import com.github.houbb.lock.api.core.ILockSupportContext; +import com.github.houbb.lock.core.support.format.LockKeyFormat; +import com.github.houbb.lock.core.support.handler.LockReleaseFailHandler; + +import java.util.concurrent.TimeUnit; /** * 分布式锁接口定义 @@ -25,13 +31,43 @@ public class LockSupportContext implements ILockSupportContext { * 缓存策略 * @since 0.0.4 */ - private ICommonCacheService commonCacheService; + private ICommonCacheService cache; /** - * 锁的过期时间 - * @since 1.0.0 + * 时间单位 */ - private int lockExpireMills; + private TimeUnit timeUnit; + + /** + * 加锁时间 + * @since 1.2.0 + */ + private long lockTime; + + /** + * 等待加锁时间 + * @since 1.2.0 + */ + private long waitLockTime; + + /** + * 缓存对应的 key + * @since 1.2.0 + */ + private String key; + + /** + * 锁 key 格式化 + * @since 1.2.0 + */ + private ILockKeyFormat lockKeyFormat; + + /** + * 锁释放失败处理类 + * @since 1.2.0 + */ + private ILockReleaseFailHandler lockReleaseFailHandler; + @Override public Id id() { @@ -45,21 +81,72 @@ public class LockSupportContext implements ILockSupportContext { @Override public ICommonCacheService cache() { - return commonCacheService; + return cache; } - public LockSupportContext cache(ICommonCacheService commonCacheService) { - this.commonCacheService = commonCacheService; + public LockSupportContext cache(ICommonCacheService cache) { + this.cache = cache; return this; } @Override - public int lockExpireMills() { - return lockExpireMills; + public TimeUnit timeUnit() { + return timeUnit; } - public LockSupportContext lockExpireMills(int lockExpireMills) { - this.lockExpireMills = lockExpireMills; + public LockSupportContext timeUnit(TimeUnit timeUnit) { + this.timeUnit = timeUnit; return this; } + + @Override + public long lockTime() { + return lockTime; + } + + public LockSupportContext lockTime(long lockTime) { + this.lockTime = lockTime; + return this; + } + + @Override + public long waitLockTime() { + return waitLockTime; + } + + public LockSupportContext waitLockTime(long waitLockTime) { + this.waitLockTime = waitLockTime; + return this; + } + + @Override + public String key() { + return key; + } + + public LockSupportContext key(String key) { + this.key = key; + return this; + } + + @Override + public ILockKeyFormat lockKeyFormat() { + return lockKeyFormat; + } + + public LockSupportContext lockKeyFormat(ILockKeyFormat lockKeyFormat) { + this.lockKeyFormat = lockKeyFormat; + return this; + } + + @Override + public ILockReleaseFailHandler lockReleaseFailHandler() { + return lockReleaseFailHandler; + } + + public LockSupportContext lockReleaseFailHandler(ILockReleaseFailHandler lockReleaseFailHandler) { + this.lockReleaseFailHandler = lockReleaseFailHandler; + return this; + } + } diff --git a/lock-core/src/main/java/com/github/houbb/lock/core/support/lock/RedisLockSupport.java b/lock-core/src/main/java/com/github/houbb/lock/core/support/lock/RedisLockSupport.java index 1a8a307..114a8e2 100644 --- a/lock-core/src/main/java/com/github/houbb/lock/core/support/lock/RedisLockSupport.java +++ b/lock-core/src/main/java/com/github/houbb/lock/core/support/lock/RedisLockSupport.java @@ -1,10 +1,13 @@ package com.github.houbb.lock.core.support.lock; import com.github.houbb.common.cache.api.service.ICommonCacheService; +import com.github.houbb.heaven.util.lang.StringUtil; import com.github.houbb.id.api.Id; import com.github.houbb.id.core.util.IdThreadLocalHelper; +import com.github.houbb.lock.api.core.ILockKeyFormat; import com.github.houbb.lock.api.core.ILockSupport; import com.github.houbb.lock.api.core.ILockSupportContext; +import com.github.houbb.lock.core.support.format.LockKeyFormatContext; import com.github.houbb.log.integration.core.Log; import com.github.houbb.log.integration.core.LogFactory; import com.github.houbb.redis.config.core.constant.JedisConst; @@ -22,25 +25,27 @@ public class RedisLockSupport implements ILockSupport { private final Log log = LogFactory.getLog(RedisLockSupport.class); @Override - public boolean tryLock(long time, TimeUnit unit, String key, ILockSupportContext context) { + public boolean tryLock(ILockSupportContext context) { long startTimeMills = System.currentTimeMillis(); // 一次获取,直接成功 - boolean result = this.tryLock(key, context); + boolean result = this.doLock(context); if(result) { return true; } // 时间判断 - if(time <= 0) { + final TimeUnit timeUnit = context.timeUnit(); + final long waitLockTime = context.waitLockTime(); + if(waitLockTime <= 0) { return false; } - long durationMills = unit.toMillis(time); + long durationMills = timeUnit.toMillis(waitLockTime); long endMills = startTimeMills + durationMills; // 循环等待 while (System.currentTimeMillis() < endMills) { - result = tryLock(key, context); + result = this.doLock(context); if(result) { return true; } @@ -49,33 +54,48 @@ public class RedisLockSupport implements ILockSupport { try { TimeUnit.MILLISECONDS.sleep(1); } catch (InterruptedException e) { - throw new RuntimeException(e); + log.debug("[LOCK] try lock wait 1 mills."); } } return false; } - @Override - public boolean tryLock(String key, ILockSupportContext context) { + /** + * 执行加锁 + * @param context 上下文 + * @return 结果 + */ + protected boolean doLock(ILockSupportContext context) { + final String key = this.getActualKey(context); + // 生成当前线程的唯一标识 - Id id = context.id(); + final Id id = context.id(); final String requestId = id.id(); IdThreadLocalHelper.put(requestId); log.info("[LOCK] BEGIN TRY LOCK key: {} requestId: {}", key, requestId); final ICommonCacheService commonCacheService = context.cache(); - final int lockExpireMills = context.lockExpireMills(); + final TimeUnit timeUnit = context.timeUnit(); + final long lockTime = context.lockTime(); + final int lockExpireMills = (int) timeUnit.toMillis(lockTime); String result = commonCacheService.set(key, requestId, JedisConst.SET_IF_NOT_EXIST, JedisConst.SET_WITH_EXPIRE_TIME, lockExpireMills); - log.info("[LOCK] END TRY LOCK key: {}, requestId: {}, result: {}", key, requestId, result); + log.info("[LOCK] END TRY LOCK key: {}, requestId: {}, lockExpireMills: {}, result: {}", key, requestId, lockExpireMills, result); return JedisConst.OK.equalsIgnoreCase(result); } @Override - public boolean unlock(String key, ILockSupportContext context) { + public boolean unlock(ILockSupportContext context) { + final String key = this.getActualKey(context); + String requestId = IdThreadLocalHelper.get(); log.info("[LOCK] BEGIN UN LOCK key: {} requestId: {}", key, requestId); + if(StringUtil.isEmpty(requestId)) { + log.warn("[LOCK] UNLOCK requestId not found, ignore"); + return false; + } + final ICommonCacheService commonCacheService = context.cache(); String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; Object result = commonCacheService.eval(script, Collections.singletonList(key), Collections.singletonList(requestId)); @@ -83,4 +103,21 @@ public class RedisLockSupport implements ILockSupport { return JedisConst.RELEASE_SUCCESS.equals(result); } + /** + * 构建真正的 key + * @param context 上下文 + * @return 结果 + * @since 1.2.0 + */ + private String getActualKey(ILockSupportContext context) { + final String rawKey = context.key(); + final ILockKeyFormat keyFormat = context.lockKeyFormat(); + LockKeyFormatContext formatContext = LockKeyFormatContext.newInstance() + .rawKey(rawKey); + + String key = keyFormat.format(formatContext); + log.info("[LOCK] format rawKey: {} to key: {}", rawKey, key); + return key; + } + } diff --git a/lock-spring/src/main/java/com/github/houbb/lock/spring/annotation/EnableLock.java b/lock-spring/src/main/java/com/github/houbb/lock/spring/annotation/EnableLock.java index e4ae0d5..445bb30 100644 --- a/lock-spring/src/main/java/com/github/houbb/lock/spring/annotation/EnableLock.java +++ b/lock-spring/src/main/java/com/github/houbb/lock/spring/annotation/EnableLock.java @@ -18,13 +18,6 @@ import java.lang.annotation.*; @EnableAspectJAutoProxy public @interface EnableLock { - /** - * 加锁过期时间 - * @return 时间 - * @since 1.1.0 - */ - int lockExpireMills() default 60 * 1000; - /** * 唯一标识生成策略 * @return 结果 @@ -42,5 +35,18 @@ public @interface EnableLock { */ String cache() default "springRedisService"; + /** + * 加锁 key 格式化策略 + * @return 策略 + * @since 1.2.0 + */ + String lockKeyFormat() default "lockKeyFormat"; + + /** + * 锁释放失败处理类 + * @return 结果 + * @since 1.2.0 + */ + String lockReleaseFailHandler() default "lockReleaseFailHandler"; } diff --git a/lock-spring/src/main/java/com/github/houbb/lock/spring/annotation/Lock.java b/lock-spring/src/main/java/com/github/houbb/lock/spring/annotation/Lock.java index ac00652..0e33f92 100644 --- a/lock-spring/src/main/java/com/github/houbb/lock/spring/annotation/Lock.java +++ b/lock-spring/src/main/java/com/github/houbb/lock/spring/annotation/Lock.java @@ -1,6 +1,7 @@ package com.github.houbb.lock.spring.annotation; import java.lang.annotation.*; +import java.util.concurrent.TimeUnit; /** * 分布式加锁注解 @@ -20,9 +21,24 @@ public @interface Lock { String value() default ""; /** - * 尝试获取锁等待时间 - * @return 结果 + * 时间单位 + * @return 单位 + * @since 1.2.0 */ - long tryLockMills() default 10 * 1000; + TimeUnit timeUnit() default TimeUnit.SECONDS; + + /** + * 等待锁时间 + * @return 等待锁时间 + * @since 1.2.0 + */ + long waitLockTime() default 10; + + /** + * 业务加锁时间 + * @return 加锁时间 + * @since 1.2.0 + */ + long lockTime() default 60; } diff --git a/lock-spring/src/main/java/com/github/houbb/lock/spring/aop/LockAspect.java b/lock-spring/src/main/java/com/github/houbb/lock/spring/aop/LockAspect.java index 6086faa..25be52e 100644 --- a/lock-spring/src/main/java/com/github/houbb/lock/spring/aop/LockAspect.java +++ b/lock-spring/src/main/java/com/github/houbb/lock/spring/aop/LockAspect.java @@ -6,6 +6,7 @@ import com.github.houbb.heaven.util.lang.StringUtil; import com.github.houbb.heaven.util.util.ArrayUtil; import com.github.houbb.lock.api.exception.LockException; import com.github.houbb.lock.core.bs.LockBs; +import com.github.houbb.lock.core.support.handler.LockReleaseFailHandlerContext; import com.github.houbb.lock.spring.annotation.Lock; import com.github.houbb.log.integration.core.Log; import com.github.houbb.log.integration.core.LogFactory; @@ -24,9 +25,7 @@ import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.stereotype.Component; import java.lang.reflect.Method; -import java.lang.reflect.Parameter; import java.util.Arrays; -import java.util.concurrent.TimeUnit; /** * @author binbin.hou @@ -61,16 +60,14 @@ public class LockAspect { boolean isLockAnnotation = method.isAnnotationPresent(Lock.class); if(isLockAnnotation) { - Lock lock = method.getAnnotation(Lock.class); + Lock lockAnnotation = method.getAnnotation(Lock.class); // 如果构建 key? - String lockKey = buildLockKey(lock, point); + String lockKey = buildLockKey(lockAnnotation, point); boolean tryLockFlag = false; try { - long tryLockMills = lock.tryLockMills(); - - tryLockFlag = lockBs.tryLock(tryLockMills, TimeUnit.MILLISECONDS, lockKey); + tryLockFlag = lockBs.tryLock(lockKey, lockAnnotation.timeUnit(), lockAnnotation.lockTime(), lockAnnotation.waitLockTime()); if(!tryLockFlag) { log.warn("[LOCK] TRY LOCK FAILED {}", lockKey); throw new LockException("[LOCK] TRY LOCK FAILED " + lockKey); @@ -84,7 +81,9 @@ public class LockAspect { // 只有获取锁的情况下,才尝试释放锁 if(tryLockFlag) { boolean unLockFlag = lockBs.unlock(lockKey); - // 异常处理等 + + // 释放锁结果 + this.lockReleaseFailHandle(unLockFlag, lockKey); } } } else { @@ -92,6 +91,24 @@ public class LockAspect { } } + /** + * 锁释放失败 + * @param unLockFlag 释放结果 + * @param lockKey 加锁信息 + */ + private void lockReleaseFailHandle(boolean unLockFlag, + String lockKey) { + if(unLockFlag) { + return; + } + + // 触发通知,便于用户自定义处理。 + // 比如报警 + LockReleaseFailHandlerContext handlerContext = LockReleaseFailHandlerContext.newInstance() + .key(lockKey); + this.lockBs.lockReleaseFailHandler().handle(handlerContext); + } + /** * 构建加锁的 key * diff --git a/lock-spring/src/main/java/com/github/houbb/lock/spring/config/LockAopConfig.java b/lock-spring/src/main/java/com/github/houbb/lock/spring/config/LockAopConfig.java index 8cb4820..a56d9a1 100644 --- a/lock-spring/src/main/java/com/github/houbb/lock/spring/config/LockAopConfig.java +++ b/lock-spring/src/main/java/com/github/houbb/lock/spring/config/LockAopConfig.java @@ -4,6 +4,8 @@ import com.github.houbb.common.cache.api.service.ICommonCacheService; import com.github.houbb.heaven.util.lang.StringUtil; import com.github.houbb.id.api.Id; import com.github.houbb.id.core.core.Ids; +import com.github.houbb.lock.api.core.ILockKeyFormat; +import com.github.houbb.lock.api.core.ILockReleaseFailHandler; import com.github.houbb.lock.core.bs.LockBs; import com.github.houbb.lock.spring.annotation.EnableLock; import com.github.houbb.redis.config.core.factory.JedisRedisServiceFactory; @@ -28,15 +30,16 @@ public class LockAopConfig implements ImportAware, BeanFactoryPostProcessor { @Bean("lockBs") public LockBs lockBs() { - int lockExpireMills = (int) enableLockAttributes.get("lockExpireMills"); ICommonCacheService commonCacheService = beanFactory.getBean(enableLockAttributes.getString("cache"), ICommonCacheService.class); Id id = beanFactory.getBean(enableLockAttributes.getString("id"), Id.class); + ILockKeyFormat lockKeyFormat = beanFactory.getBean(enableLockAttributes.getString("lockKeyFormat"), ILockKeyFormat.class); + ILockReleaseFailHandler lockReleaseFailHandler = beanFactory.getBean(enableLockAttributes.getString("lockReleaseFailHandler"), ILockReleaseFailHandler.class); return LockBs.newInstance() .cache(commonCacheService) .id(id) - .lockExpireMills(lockExpireMills) - .init(); + .lockKeyFormat(lockKeyFormat) + .lockReleaseFailHandler(lockReleaseFailHandler); } /** diff --git a/lock-spring/src/main/java/com/github/houbb/lock/spring/config/LockBeanConfig.java b/lock-spring/src/main/java/com/github/houbb/lock/spring/config/LockBeanConfig.java index 65651ad..211e4f3 100644 --- a/lock-spring/src/main/java/com/github/houbb/lock/spring/config/LockBeanConfig.java +++ b/lock-spring/src/main/java/com/github/houbb/lock/spring/config/LockBeanConfig.java @@ -2,6 +2,10 @@ package com.github.houbb.lock.spring.config; import com.github.houbb.id.api.Id; import com.github.houbb.id.core.core.Ids; +import com.github.houbb.lock.api.core.ILockKeyFormat; +import com.github.houbb.lock.api.core.ILockReleaseFailHandler; +import com.github.houbb.lock.core.support.format.LockKeyFormat; +import com.github.houbb.lock.core.support.handler.LockReleaseFailHandler; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @@ -21,4 +25,14 @@ public class LockBeanConfig { return Ids.uuid32(); } + @Bean("lockKeyFormat") + public ILockKeyFormat lockKeyFormat() { + return new LockKeyFormat(); + } + + @Bean("lockReleaseFailHandler") + public ILockReleaseFailHandler lockReleaseFailHandler() { + return new LockReleaseFailHandler(); + } + } diff --git a/lock-test/src/test/java/com/github/houbb/lock/test/core/LockBsTest.java b/lock-test/src/test/java/com/github/houbb/lock/test/core/LockBsTest.java index b6f9261..ef1f686 100644 --- a/lock-test/src/test/java/com/github/houbb/lock/test/core/LockBsTest.java +++ b/lock-test/src/test/java/com/github/houbb/lock/test/core/LockBsTest.java @@ -1,17 +1,20 @@ package com.github.houbb.lock.test.core; +import com.github.houbb.id.core.core.Ids; import com.github.houbb.lock.api.core.ILock; import com.github.houbb.lock.core.bs.LockBs; +import com.github.houbb.lock.core.support.format.LockKeyFormat; +import com.github.houbb.lock.core.support.handler.LockReleaseFailHandler; +import com.github.houbb.lock.core.support.lock.RedisLockSupport; +import com.github.houbb.redis.config.core.factory.JedisRedisServiceFactory; import org.junit.Test; public class LockBsTest { @Test public void helloTest() { - ILock lock = LockBs.newInstance() - .init(); - + ILock lock = LockBs.newInstance(); String key = "ddd"; try { // 加锁 @@ -25,4 +28,15 @@ public class LockBsTest { } } + @Test + public void configTest() { + LockBs.newInstance() + .id(Ids.uuid32()) //id 生成策略 + .cache(JedisRedisServiceFactory.pooled("127.0.0.1", 6379)) //缓存策略 + .lockSupport(new RedisLockSupport()) // 锁实现策略 + .lockKeyFormat(new LockKeyFormat()) // 针对 key 的格式化处理策略 + .lockReleaseFailHandler(new LockReleaseFailHandler()) //释放锁失败处理 + ; + } + }