注解方式優(yōu)雅的實(shí)現(xiàn) Redisson 分布式鎖
點(diǎn)擊關(guān)注公眾號(hào),Java干貨及時(shí)送達(dá)
來(lái)源:juejin.cn/post/7215142807861379109
1前言
日常開發(fā)中,難免遇到一些并發(fā)的場(chǎng)景,為了保證接口執(zhí)行的一致性,通常采用加鎖的方式,因?yàn)榉?wù)是分布式部署模式,本地鎖Reentrantlock和Synchnorized這些就先放到一邊了,Redis的setnx鎖存在無(wú)法抱保證原子性的問(wèn)題就暫時(shí)擱且到一邊,直接上大招Redisson也是我最近開發(fā)項(xiàng)目中基本都在用的緩存,并且也都是用它的分布式鎖機(jī)制。
2Redisson分布式鎖常規(guī)使用
關(guān)于Redisson的一些基本概念,本章就不做太詳細(xì)的說(shuō)明了,有興趣的小伙伴可以自己去了解下,主要說(shuō)下加鎖的常規(guī)使用,Redisson分布式鎖是基于Redis的Rlock鎖,實(shí)現(xiàn)了JavaJUC包下的Lock接口。
Lock
public void getLock(){
//獲取鎖
RLock lock = redisson.getLock("Lxlxxx_Lock");
try {
// 2.加鎖
lock.lock();
} catch (InterruptedException e) {
e.getStackTrace();
} finally {
// 3.解鎖
lock.unlock();
System.out.println("Finally,釋放鎖成功");
}
getLock獲取鎖,lock.lock進(jìn)行加鎖,會(huì)出現(xiàn)的問(wèn)題就是lock拿不到鎖一直等待,會(huì)進(jìn)入阻塞狀態(tài),顯然這樣是不好的。
TryLock
返回boolean類型,和Reentrantlock的tryLock是一個(gè)意思,嘗試獲取鎖,獲取到就返回true,獲取失敗就返回false,不會(huì)使獲不到鎖的線程一直處于等待狀態(tài),返回false可以繼續(xù)執(zhí)行下面的業(yè)務(wù)邏輯,當(dāng)然Ression鎖內(nèi)部也涉及到watchDog看門狗機(jī)制,主要作用就是給快過(guò)期的鎖進(jìn)行續(xù)期,主要用途就是使拿到鎖的有限時(shí)間讓業(yè)務(wù)執(zhí)行完,再進(jìn)行鎖釋放。
RLock lock = redisson.getLock(name);
try {
if (lock.tryLock(2, 10, TimeUnit.SECONDS)) {
//執(zhí)行業(yè)務(wù)邏輯
} else {
System.out.println("已存在");
}
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
//判斷當(dāng)前線程持有的鎖是不是處于鎖定狀態(tài),鎖定狀態(tài)再進(jìn)行釋放
if (this.redissonLock.isHeldByCurrentThread(lockName)) {
this.redissonLock.unlock(lockName);
}
}
3自定義注解實(shí)現(xiàn)鎖機(jī)制
通常我們都會(huì)將redisson實(shí)例注入到方法類里面,然后調(diào)用加鎖方法進(jìn)行加鎖,如果其他業(yè)務(wù)方法也需要加鎖執(zhí)行,將會(huì)產(chǎn)生很多重復(fù)代碼,由此采用AOP切面的方式,只需要通過(guò)注解的方式就能將方法進(jìn)行加鎖處理。
自定義注解
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface DistributedLock {
String key() default "";
int leaseTime() default 10;
boolean autoRelease() default true;
String errorDesc() default "系統(tǒng)正常處理,請(qǐng)稍后提交";
int waitTime() default 1;
}
切面類實(shí)現(xiàn)
@Aspect
@Component
public class DistributedLockHandler {
private static final Logger log = LoggerFactory.getLogger(DistributedLockHandler.class);
@Autowired
RedissonLock redissonLock;
public DistributedLockHandler() {
}
@Around("@annotation(distributedLock)")
public Object around(ProceedingJoinPoint joinPoint, DistributedLock distributedLock) throws Throwable {
String lockName = this.getRedisKey(joinPoint, distributedLock);
int leaseTime = distributedLock.leaseTime();
String errorDesc = distributedLock.errorDesc();
int waitTime = distributedLock.waitTime();
Object var8;
try {
boolean lock = this.redissonLock.tryLock(lockName, (long)leaseTime, (long)waitTime);
if (!lock) {
throw new RuntimeException(errorDesc);
}
var8 = joinPoint.proceed();
} catch (Throwable var12) {
log.error("執(zhí)行業(yè)務(wù)方法異常", var12);
throw var12;
} finally {
if (this.redissonLock.isHeldByCurrentThread(lockName)) {
this.redissonLock.unlock(lockName);
}
}
return var8;
}
/**
* 獲取加鎖的key
* @param joinPoint
* @param distributedLock
* @return
*/
private String getRedisKey(ProceedingJoinPoint joinPoint, DistributedLock distributedLock) {
String key = distributedLock.key();
Object[] parameterValues = joinPoint.getArgs();
MethodSignature signature = (MethodSignature)joinPoint.getSignature();
Method method = signature.getMethod();
DefaultParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();
String[] parameterNames = nameDiscoverer.getParameterNames(method);
if (StringUtils.isEmpty(key)) {
if (parameterNames != null && parameterNames.length > 0) {
StringBuffer sb = new StringBuffer();
int i = 0;
for(int len = parameterNames.length; i < len; ++i) {
sb.append(parameterNames[i]).append(" = ").append(parameterValues[i]);
}
key = sb.toString();
} else {
key = "redissionLock";
}
return key;
} else {
SpelExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression(key);
if (parameterNames != null && parameterNames.length != 0) {
EvaluationContext evaluationContext = new StandardEvaluationContext();
for(int i = 0; i < parameterNames.length; ++i) {
evaluationContext.setVariable(parameterNames[i], parameterValues[i]);
}
try {
Object expressionValue = expression.getValue(evaluationContext);
return expressionValue != null && !"".equals(expressionValue.toString()) ? expressionValue.toString() : key;
} catch (Exception var13) {
return key;
}
} else {
return key;
}
}
}
}
具體使用
方法頭加自定義注解,key參數(shù)代表需要加鎖的key,errorDesc獲取鎖失敗提示報(bào)錯(cuò)信息。
這邊我將項(xiàng)目通過(guò)修改端口啟動(dòng)了兩個(gè)服務(wù),分別是8460和8461
通過(guò)postman調(diào)用這兩個(gè)服務(wù),模擬兩個(gè)服務(wù)同時(shí)獲取一把鎖的場(chǎng)景,其中一個(gè)服務(wù)拿到鎖,另外一個(gè)服務(wù)獲取鎖失敗。
可以看到端口8460服務(wù)先拿到鎖,8461服務(wù)tryLock獲取鎖失敗,實(shí)現(xiàn)了加鎖邏輯。
4總結(jié)
分布式鎖的使用場(chǎng)景還是需要多注意下,根據(jù)業(yè)務(wù)場(chǎng)景來(lái),并發(fā)量不大的情況下,其實(shí)沒(méi)有必要加,可能在移動(dòng)端操作比較頻繁的情況下需要注意并發(fā),目前我做的b端項(xiàng)目,通過(guò)簡(jiǎn)單接口冪等性操作就可以避免重復(fù)提交,切勿不要盲目加鎖,多少會(huì)影響一些性能。
往 期 推 薦
1、如何搭建一個(gè)拖垮公司的技術(shù)架構(gòu)?【文末送書】
2、原來(lái),這才是 JDK 推薦的線程關(guān)閉方式
3、MySQL到底是 join 性能好,還是in一下更快呢?
![]()
點(diǎn)分享
![]()
點(diǎn)收藏
![]()
點(diǎn)點(diǎn)贊
![]()
點(diǎn)在看

