lock-spring-boot-starter
分布式锁 starter
介绍
说明
工程接口(扩展点):
接口->com.javacoo.lock.client.api.Lock
基于xkernel 提供的SPI机制,结合SpringBoot注解 ConditionalOnBean,ConditionalOnProperty实现。
类关系图
项目结构
lock-spring-boot-starter
└── src
├── main
│ ├── java
│ │ └── com.javacoo
│ │ ├────── lock
│ │ │ ├──────client
│ │ │ │ ├── api
│ │ │ │ │ ├── annotation
│ │ │ │ │ │ └── MethodLock 锁注解
│ │ │ │ │ ├── client
│ │ │ │ │ │ └── Lock 锁接口
│ │ │ │ │ ├── aspect
│ │ │ │ │ │ └── LockAspect 锁切面
│ │ │ │ │ ├── config
│ │ │ │ │ │ └── LockConfig 锁配置
│ │ │ │ │ ├── exception
│ │ │ │ │ └── LockException 锁异常
│ │ │ │ └── internal 接口内部实现
│ │ │ │ ├── redis
│ │ │ │ ├── RedissionConfig Redission配置类
│ │ │ │ └── RedssionLock 锁接口实现类
│ │ │ └──────starter
│ │ │ ├── LockAutoConfiguration 自动配置类
│ │ │ └── LockHolder 锁接口对象持有者
│ └── resource
│ ├── META-INF
│ └── ext
│ └── internal
│ └── com.javacoo.lock.client.api.Lock
└── test 测试
如何使用
-
pom依赖:
<dependency> <groupId>com.javacoo</groupId> <artifactId>lock-spring-boot-starter</artifactId> <version>1.0.0</version> </dependency>
-
配置参数,如果使用默认实现,则无需配置,如要扩展则需要,配置如下:
#lock是否可用,默认可用 lock.enabled = true #lock实现,默认内部实现 lock.impl = default
方法加注解,如:
注解说明
字段 | 类型 | 说明 |
---|---|---|
fieldName | String[] | 指定需要加入锁的字段,默认为空 |
timeInSecond | int | 锁的有效时间,单位为秒,默认值为60 |
waitTime | int | 等待时间,单位为秒,默认值为0 |
paramIndex | int | 指定参数在参数列表中的索引,默认值为0 |
coolingTime | int | 方法冷却时间,多久能调用一次该方法,单位为秒,默认值为0,不限制 |
示例
//参数级锁
@MethodLock(fieldName = "applNo")
@Override
public BaseResponse gjjloanConfirm(LoanConfirmRequest request) {
...
}
//参数级锁,带冷却时间
@MethodLock(fieldName = "username",coolingTime = 10)
public ResponseEntity login(@RequestBody UserLoginRequest loginRequest, HttpServletRequest request) {
...
}
//方法级锁
@MethodLock
public void divisionCase() {
...
}
SPI扩展
基于xkernel 提供的SPI机制,扩展非常方便,大致步骤如下:
实现锁接口:如 com.xxxx.xxxx.MyLockImpl
-
配置锁接口:
在项目resource目录新建包->META-INF->services
-
创建com.javacoo.lock.client.api.Lock文件,文件内容:实现类的全局限定名,如:
myLock=com.xxxx.xxxx.MyLockImpl
修改配置文件,添加如下内容:
#lock实现 lock.impl = myLock
默认实现
1、锁接口:
/**
* 锁接口
* <li></li>
*
* @author: duanyong
* @since: 2020/6/22 10:19
*/
@Spi(LockConfig.DEFAULT_IMPL)
public interface Lock<T> {
/**超时时间*/
int TIMEOUT_SECOND = 60;
/**
* 对lockKey加锁
* <li></li>
* @author duanyong
* @date 2020/6/22 10:30
* @param lockKey:lockKey
* @return: T 锁对象
*/
T lock(String lockKey);
/**
* 对lockKey加锁,timeout后过期
* <li></li>
* @author duanyong
* @date 2020/6/22 10:31
* @param lockKey: lockKey
* @param timeout: 锁超时时间,单位:秒
* @return: T 锁对象
*/
T lock(String lockKey, int timeout);
/**
* 对lockKey加锁,指定时间单位,timeout后过期
* <li></li>
* @author duanyong
* @date 2020/6/22 10:33
* @param lockKey: lockKey
* @param unit: 时间单位
* @param timeout: 锁超时时间
* @return: T 锁对象
*/
T lock(String lockKey, TimeUnit unit , int timeout);
/**
* 尝试获取锁
* <li></li>
* @author duanyong
* @date 2020/6/22 10:35
* @param lockKey: lockKey
* @return: boolean 是否成功,成功返回:true
*/
boolean tryLock(String lockKey);
/**
* 尝试获取锁
* <li></li>
* @author duanyong
* @date 2020/6/22 10:35
* @param lockKey: lockKey
* @param waitTime:最多等待时间
* @param timeout:上锁后自动释放锁时间
* @return: boolean 是否成功,成功返回:true
*/
boolean tryLock(String lockKey, int waitTime, int timeout);
/**
* 尝试获取锁
* <li></li>
* @author duanyong
* @date 2020/6/22 10:36
* @param lockKey: lockKey
* @param unit:时间单位
* @param waitTime:最多等待时间
* @param timeout:上锁后自动释放锁时间
* @return: boolean 是否成功,成功返回:true
*/
boolean tryLock(String lockKey, TimeUnit unit, int waitTime, int timeout);
/**
* 释放锁
* <li></li>
* @author duanyong
* @date 2020/6/22 10:37
* @param lockKey: lockKey
* @return: void
*/
void unlock(String lockKey);
/**
* 释放锁
* <li></li>
* @author duanyong
* @date 2020/6/22 10:37
* @param lock:锁对象
* @return: void
*/
void unlock(T lock);
}
2、实现锁接口,如默认实现:
/**
* 锁接口实现类
* <li>基于Redssion</li>
*
* @author: duanyong
* @since: 2020/6/22 10:39
*/
@Slf4j
public class RedssionLock implements Lock<RLock> {
/**约定缓存前缀:appId:模块:key*/
public static final String CACHE_PREFIX_KEY ="javacoo:service:lock:";
@Autowired
private RedissonClient redissonClient;
/**
* 对lockKey加锁
* <li>阻塞获取锁</li>
*
* @param lockKey :lockKey
* @author duanyong
* @date 2020/6/22 10:30
* @return: T 锁对象
*/
@Override
public RLock lock(String lockKey) {
log.info(">>>>> lock key[{}]",lockKey);
Assert.hasText(lockKey,"lockKey 不能为空");
RLock lock = redissonClient.getLock(CACHE_PREFIX_KEY +lockKey);
lock.lock();
return lock;
}
/**
* 对lockKey加锁,timeout后过期
* <li></li>
*
* @param lockKey : lockKey
* @param timeout : 锁超时时间,单位:秒
* @author duanyong
* @date 2020/6/22 10:31
* @return: T 锁对象
*/
@Override
public RLock lock(String lockKey, int timeout) {
return lock(lockKey, TimeUnit.SECONDS,timeout);
}
/**
* 对lockKey加锁,指定时间单位,timeout后过期
* <li></li>
*
* @param lockKey : lockKey
* @param unit : 时间单位
* @param timeout : 锁超时时间
* @author duanyong
* @date 2020/6/22 10:33
* @return: T 锁对象
*/
@Override
public RLock lock(String lockKey, TimeUnit unit, int timeout) {
log.info(">>>>> lockKey:{},TimeUnit:{},timeout:{}",lockKey,unit,timeout);
Assert.hasText(lockKey,"lockKey 不能为空");
RLock lock = redissonClient.getLock(CACHE_PREFIX_KEY +lockKey);
lock.lock(timeout, unit);
return lock;
}
/**
* 尝试获取锁
* <li>获取锁失败立即返回,上锁后60秒自动释放锁</li>
*
* @param lockKey : lockKey
* @author duanyong
* @date 2020/6/22 10:35
* @return: boolean 是否成功,成功返回:true
*/
@Override
public boolean tryLock(String lockKey) {
return tryLock(lockKey,0,60);
}
/**
* 尝试获取锁
* <li></li>
*
* @param lockKey : lockKey
* @param waitTime :最多等待时间
* @param timeout :上锁后自动释放锁时间
* @author duanyong
* @date 2020/6/22 10:35
* @return: boolean 是否成功,成功返回:true
*/
@Override
public boolean tryLock(String lockKey, int waitTime, int timeout) {
return tryLock(lockKey, TimeUnit.SECONDS,waitTime,timeout);
}
/**
* 尝试获取锁
* <li></li>
*
* @param lockKey : lockKey
* @param unit :时间单位
* @param waitTime :最多等待时间
* @param timeout :上锁后自动释放锁时间
* @author duanyong
* @date 2020/6/22 10:36
* @return: boolean 是否成功,成功返回:true
*/
@Override
public boolean tryLock(String lockKey, TimeUnit unit, int waitTime, int timeout) {
Assert.hasText(lockKey,"lockKey 不能为空");
lockKey = CACHE_PREFIX_KEY +lockKey;
log.info(">>>>> tryLock lockKey:{},TimeUnit:{},waitTime:{},timeout:{}",lockKey,unit,waitTime,timeout);
RLock lock = redissonClient.getLock(lockKey);
try {
return lock.tryLock(waitTime, timeout, unit);
} catch (Exception e) {
e.printStackTrace();
log.error("尝试获取锁:{},TimeUnit:{},waitTime:{},timeout:{},失败",lockKey,unit,waitTime,timeout,e);
}
return false;
}
/**
* 释放锁
* <li></li>
*
* @param lockKey : lockKey
* @author duanyong
* @date 2020/6/22 10:37
* @return: void
*/
@Override
public void unlock(String lockKey) {
lockKey = CACHE_PREFIX_KEY +lockKey;
try {
RLock lock = redissonClient.getLock(lockKey);
if(!lock.isLocked()){
log.error(">>>>> unlock lockKey fail:{},isLocked:{}",lockKey,lock.isLocked());
return;
}
if(!lock.isHeldByCurrentThread()){
log.error(">>>>> unlock lockKey fail:{},isHeldByCurrentThread:{}",lockKey,lock.isHeldByCurrentThread());
return;
}
lock.unlock();
log.info(">>>>> unlock lockKey success:{}",lockKey);
} catch (Exception e) {
e.printStackTrace();
log.error("释放锁失败:{}",lockKey,e);
}
}
/**
* 释放锁
* <li></li>
*
* @param lock :锁对象
* @author duanyong
* @date 2020/6/22 10:37
* @return: void
*/
@Override
public void unlock(RLock lock) {
lock.unlock();
}
3、锁注解
/**
* 服务锁注解
* <li></li>
* @author duanyong
* @date 2020/10/14 9:20
*/
@Documented
@Inherited
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MethodLock {
/**
* 指定需要加入锁的字段
* <li></li>
* @author duanyong
* @date 2020/10/14 9:21
* @return: java.lang.String[]
*/
String[] fieldName() default {};
/**
* 锁的有效时间,单位为秒,默认值为60
* <li></li>
* @author duanyong
* @date 2020/10/14 9:21
* @return: int
*/
int timeInSecond() default 60;
/**
* 等待时间,单位为秒,默认值为0
* <li></li>
* @author duanyong
* @date 2022/10/8 9:34
* @param
* @return: int
*/
int waitTime() default 0;
/**
* 指定参数在参数列表中的索引
* <li></li>
* @author duanyong
* @date 2020/10/14 9:22
* @return: int
*/
int paramIndex() default 0;
/**
* 方法冷却时间
* <li>多久能调用一次该方法,单位为秒,默认值为0,不限制</li>
* @author duanyong
* @date 2023/1/9 15:52
* @return: int
*/
int coolingTime() default 0;
}
4、锁切面
/**
* 业务拦截器
* <li></li>
* @author duanyong
* @date 2021/3/1 15:56
*/
@Slf4j
@Aspect
@Component
public class LockAspect {
/**锁对象*/
@Autowired
private Lock lock;
@Around("@annotation(methodLock)")
public Object around(ProceedingJoinPoint joinPoint, MethodLock methodLock) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
// 获取被拦截的方法
Method method = signature.getMethod();
// 获取被拦截的类名
String className = signature.getDeclaringType().getSimpleName();
// 获取被拦截的方法名
String methodName = method.getName();
log.info("[加锁交易]请求开始,方法口名:{}", className+"."+methodName);
//生成锁KEY
Optional<String> lockKeyOptional = generateKey(joinPoint.getArgs(),className,methodName,methodLock);
if(lockKeyOptional.isPresent()){
String lockKey = lockKeyOptional.get();
if(tryLock(lockKey,methodLock) && enabled(lockKey,methodLock)){
try {
log.info("[加锁交易]加锁成功,MethodName:{},key:{}",methodName,lockKey);
return joinPoint.proceed();
} catch (Exception e) {
log.error("[加锁交易]执行方法发生异常,MethodName:{},lockKey:{},Exception:{}",methodName,lockKey,e);
throw e;
}finally {
lock.unlock(lockKey);
log.info("[加锁交易]方法解锁,MethodName:{},key:{}",methodName,lockKey);
}
}else{
log.error("[加锁交易]过滤频繁操作,加锁失败,Key:{}",lockKey);
return null;
}
}else{
return joinPoint.proceed();
}
}
/**
* 对添加RedisLock注解的方法进行重复访问限制
* @param cacheKey
* @param methodLock
*/
private boolean tryLock(String cacheKey,MethodLock methodLock) {
boolean isLocked = lock.tryLock(cacheKey, TimeUnit.SECONDS,methodLock.waitTime(),methodLock.timeInSecond());
if(isLocked){
log.info("[交易系统拦截器]加锁成功,KEY:{}",cacheKey);
}
return isLocked;
}
/**
* 方法是否可用
* <li></li>
* @author duanyong
* @date 2023/1/9 16:09
* @param cacheKey 缓存KEY
* @param methodLock 锁接口
* @return: boolean
*/
private boolean enabled(String cacheKey,MethodLock methodLock) {
if(methodLock.coolingTime() > 0){
String coolTimeKey = cacheKey+"_disabled";
boolean isDisabled = lock.tryLock(coolTimeKey, TimeUnit.SECONDS,methodLock.waitTime(),methodLock.coolingTime());
if(isDisabled){
log.info("[交易系统拦截器]方法冻结成功,KEY:{}",coolTimeKey);
}
return isDisabled;
}
return true;
}
/**
* 生成锁的key key=类名-方法名-参数集合
* <li></li>
* @author duanyong
* @date 2021/4/29 13:08
* @param args: 参数
* @param className: 类名
* @param methodName: 方法名
* @param methodLock: 锁接口
* @return: java.lang.String 锁的key
*/
private Optional<String> generateKey(Object[] args, String className, String methodName, MethodLock methodLock) throws Exception {
try{
//根据参数列表索引获取入参 默认为第一个参数
StringBuilder keyBuilder = new StringBuilder();
//参数为空,则为方法锁
if(args == null || args.length <= 0){
keyBuilder.append(className)
.append("-")
.append(methodName);
return Optional.of(keyBuilder.toString());
}
Object param = args[methodLock.paramIndex()];
List<String> fieldValueList = new ArrayList<>();
String[] fieldNames = methodLock.fieldName();
if(param instanceof String){
fieldValueList.add(String.valueOf(param));
}else if(param instanceof Long){
fieldValueList.add(String.valueOf(param));
}else{
String jsonString = JSON.toJSONString(param);
JSONObject jsonObject = JSON.parseObject(jsonString);
for (String filedName:fieldNames){
if(jsonObject.containsKey(filedName)){
fieldValueList.add(jsonObject.getString(filedName));
}
}
}
keyBuilder.append(className)
.append("-")
.append(methodName)
.append("-")
.append(fieldValueList);
return Optional.of(keyBuilder.toString());
}catch (Exception e){
log.error("生成锁的key失败",e);
}
return Optional.empty();
}
}
5、锁接口对象持有者
/**
* 锁接口对象持有者
* <li></li>
*
* @author: duanyong
* @since: 2021/3/16 8:56
*/
public class LockHolder {
/** 锁对象*/
static Lock lock;
public static Optional<Lock> getLock() {
return Optional.ofNullable(lock);
}
}
6、自动配置。
/**
* 自动配置类
* <li></li>
* @author duanyong
* @date 2021/3/5 9:50
*/
@Slf4j
@Configuration
@EnableConfigurationProperties(value = LockConfig.class)
@ConditionalOnClass(Lock.class)
@ConditionalOnProperty(prefix = LockConfig.PREFIX, value = LockConfig.ENABLED, matchIfMissing = true)
public class LockAutoConfiguration {
@Autowired
private LockConfig lockConfig;
@Bean
@ConditionalOnMissingBean(Lock.class)
public Lock createLock() {
log.info("初始化分布式锁,实现类名称:{}",lockConfig.getImpl());
LockHolder.lock = ExtensionLoader.getExtensionLoader(Lock.class).getExtension(lockConfig.getImpl());
log.info("初始化分布式锁成功,实现类:{}",LockHolder.lock);
return LockHolder.lock;
}
@Bean
public LockAspect createLockAspect() {
return new LockAspect();
}
}
一些信息
路漫漫其修远兮,吾将上下而求索
码云:https://gitee.com/javacoo
QQ群:164863067
作者/微信:javacoo
邮箱:xihuady@126.com