lock-spring-boot-starter
分布式鎖 starter
介紹
說明
工程接口(擴展點):
接口->com.javacoo.lock.client.api.Lock
基于xkernel 提供的SPI機制,結(jié)合SpringBoot注解 ConditionalOnBean,ConditionalOnProperty實現(xiàn)。
類關(guān)系圖
Lock.png
項目結(jié)構(gòu)
lock-spring-boot-starter
└── src
├── main
│ ├── java
│ │ └── com.javacoo
│ │ ├────── lock
│ │ │ ├──────client
│ │ │ │ ├── api
│ │ │ │ │ ├── annotation
│ │ │ │ │ │ └── MethodLock 鎖注解
│ │ │ │ │ ├── client
│ │ │ │ │ │ └── Lock 鎖接口
│ │ │ │ │ ├── aspect
│ │ │ │ │ │ └── LockAspect 鎖切面
│ │ │ │ │ ├── config
│ │ │ │ │ │ └── LockConfig 鎖配置
│ │ │ │ │ ├── exception
│ │ │ │ │ └── LockException 鎖異常
│ │ │ │ └── internal 接口內(nèi)部實現(xiàn)
│ │ │ │ ├── redis
│ │ │ │ ├── RedissionConfig Redission配置類
│ │ │ │ └── RedssionLock 鎖接口實現(xiàn)類
│ │ │ └──────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>
-
配置參數(shù),如果使用默認實現(xiàn),則無需配置,如要擴展則需要,配置如下:
#lock是否可用,默認可用 lock.enabled = true #lock實現(xiàn),默認內(nèi)部實現(xiàn) lock.impl = default
方法加注解,如:
注解說明
字段 | 類型 | 說明 |
---|---|---|
fieldName | String[] | 指定需要加入鎖的字段,默認為空 |
timeInSecond | int | 鎖的有效時間,單位為秒,默認值為60 |
waitTime | int | 等待時間,單位為秒,默認值為0 |
paramIndex | int | 指定參數(shù)在參數(shù)列表中的索引,默認值為0 |
coolingTime | int | 方法冷卻時間,多久能調(diào)用一次該方法,單位為秒,默認值為0,不限制 |
示例
//參數(shù)級鎖
@MethodLock(fieldName = "applNo")
@Override
public BaseResponse gjjloanConfirm(LoanConfirmRequest request) {
...
}
//參數(shù)級鎖,帶冷卻時間
@MethodLock(fieldName = "username",coolingTime = 10)
public ResponseEntity login(@RequestBody UserLoginRequest loginRequest, HttpServletRequest request) {
...
}
//方法級鎖
@MethodLock
public void divisionCase() {
...
}
SPI擴展
基于xkernel 提供的SPI機制,擴展非常方便,大致步驟如下:
實現(xiàn)鎖接口:如 com.xxxx.xxxx.MyLockImpl
-
配置鎖接口:
在項目resource目錄新建包->META-INF->services
-
創(chuàng)建com.javacoo.lock.client.api.Lock文件,文件內(nèi)容:實現(xiàn)類的全局限定名,如:
myLock=com.xxxx.xxxx.MyLockImpl
修改配置文件,添加如下內(nèi)容:
#lock實現(xiàn) lock.impl = myLock
默認實現(xiàn)
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、實現(xiàn)鎖接口,如默認實現(xiàn):
/**
* 鎖接口實現(xiàn)類
* <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、鎖注解
/**
* 服務(wù)鎖注解
* <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;
/**
* 指定參數(shù)在參數(shù)列表中的索引
* <li></li>
* @author duanyong
* @date 2020/10/14 9:22
* @return: int
*/
int paramIndex() default 0;
/**
* 方法冷卻時間
* <li>多久能調(diào)用一次該方法,單位為秒,默認值為0,不限制</li>
* @author duanyong
* @date 2023/1/9 15:52
* @return: int
*/
int coolingTime() default 0;
}
4、鎖切面
/**
* 業(yè)務(wù)攔截器
* <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("[加鎖交易]執(zhí)行方法發(fā)生異常,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注解的方法進行重復(fù)訪問限制
* @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("[交易系統(tǒng)攔截器]加鎖成功,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("[交易系統(tǒng)攔截器]方法凍結(jié)成功,KEY:{}",coolTimeKey);
}
return isDisabled;
}
return true;
}
/**
* 生成鎖的key key=類名-方法名-參數(shù)集合
* <li></li>
* @author duanyong
* @date 2021/4/29 13:08
* @param args: 參數(shù)
* @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{
//根據(jù)參數(shù)列表索引獲取入?yún)?默認為第一個參數(shù)
StringBuilder keyBuilder = new StringBuilder();
//參數(shù)為空,則為方法鎖
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("初始化分布式鎖,實現(xiàn)類名稱:{}",lockConfig.getImpl());
LockHolder.lock = ExtensionLoader.getExtensionLoader(Lock.class).getExtension(lockConfig.getImpl());
log.info("初始化分布式鎖成功,實現(xiàn)類:{}",LockHolder.lock);
return LockHolder.lock;
}
@Bean
public LockAspect createLockAspect() {
return new LockAspect();
}
}
一些信息
路漫漫其修遠兮,吾將上下而求索
碼云:https://gitee.com/javacoo
QQ群:164863067
作者/微信:javacoo
郵箱:xihuady@126.com