title: “分佈式鎖:Redis+註解”
url: “https://wsk1103.github.io/”
tags:
- 學習筆記
- 分佈式鎖
1. 起因
使用 redis 來設計分佈式鎖,經常需要在每個方法開始執行 setNx(key) ,然後在方法運行結束後 del(key) 。而且當系統需要改進使用分佈式鎖的時候,需要修改的地方偏多,這樣造成了過程繁雜,而且有時候又忘記 del(key) ,導致間隔比較短請求被攔截。所以考慮設計一個結合 註解 和 AOP 來完成分佈式鎖。
2. 思路流程圖
3. 代碼設計
3.1 增加依賴
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
3.2 redis.setNx() 和 redis.del()
/**
* 分佈式鎖
* 設置成功,返回1,
*
* @param key
* @param value
* @param time 過期時間,單位秒
* @return
*/
public static boolean setNx(String key, String value, int time) {
Jedis jedis = null;
try {
if (StringUtils.isBlank(key)) {
throw new NullPointerException("The key is not allowed null");
}
jedis = RedisDB.getPool().getResource();
long result = jedis.setnx(key, value);
if (result == 1) {
// ==1 ,設置成功
jedis.expire(key, time);
return true;
} else {
return false;
}
} catch (Exception e) {
ILogUtil.error("setNx fail:{}", e);
return false;
} finally {
RedisDB.getPool().returnResourceObject(jedis);
}
}
/**
* 刪除redis裏的主鍵
*
* @param key redis主鍵
*/
public static void del(String key) {
Jedis jedis = RedisDB.getPool().getResource();
try {
jedis.del(key);
} finally {
// 使用完後,將連接放回連接池
RedisDB.getPool().returnResourceObject(jedis);
}
}
3.3 註解 @MyLock
import java.lang.annotation.*;
/**
* @author sk
* @time 2020/1/15
* @desc say
**/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyLock {
}
3.4 設計 AOP
package com.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
* @author sk
* @time 2020/1/15
* @desc say
**/
@Aspect
@Component
public class LockAspect {
@Pointcut("@annotation(com.anno.MyLock)")
public void myLock() {
}
@Before("myLock()")
public void before(JoinPoint joinPoint) throws Exception {
System.out.println("before");
Object[] objects = joinPoint.getArgs();
for (Object o : objects) {
if (o instanceof String) {
String method = joinPoint.getSignature().getName();
String key = method + o.toString();
boolean b = setNx(key, "", 60);
if (!b) {
throw new Exception("請勿重複操作");
} else {
System.out.println("=======獲取lock成功!!!!!!========");
}
break;
}
}
}
@AfterReturning("myLock()")
public void after(JoinPoint joinPoint) throws Exception {
System.out.println("after");
Object[] objects = joinPoint.getArgs();
for (Object o : objects) {
if (o instanceof String) {
String method = joinPoint.getSignature().getName();
String key = method + o.toString();
del(method + o.toString());
System.out.println("釋放鎖成功");
break;
}
}
}
}
3.5 方法注入
import com.anno.MyLock;
import org.springframework.stereotype.Service;
/**
* @author sk
* @time 2020/1/15
* @desc say
**/
@Service
public class MyService {
@MyLock
public String go(String go) {
System.out.println(go);
return "dd";
}
}
4. 注意和設計優化
- 4.1. 在AOP攔截時,是通過該方法入參中的第一個 String 來作爲 lock 的 key ,所以在調用方法時,需要使該 String 能夠識別用戶,所以該 key 一般爲用戶的身份標識,例如 sessionId 。
- 4.2. 或者將入參統一修改爲一個 抽象類 ,該抽象類裏面有一個 用戶唯一標識 的字段,然後其他類繼承該類。
例如
import lombok.Data;
/**
* @author wsk1103
* @date 2020/1/15
* @description 描述
*/
@Data
public abstract class AbstractRequest {
private String sessionId;
}
對應的 AOP 修改爲:
@Before("myLock()")
public void before(JoinPoint joinPoint) throws Exception {
System.out.println("before");
Object[] objects = joinPoint.getArgs();
for (Object o : objects) {
if (o instanceof AbstractRequest) {
String method = joinPoint.getSignature().getName();
String key = method + ((AbstractRequest)o).getSessionId();
boolean b = setNx(key, "", 60);
if (!b) {
throw new Exception("請勿重複操作");
} else {
System.out.println("=======獲取lock成功!!!!!!========");
}
break;
}
}
}
@After("myLock()")
public void after(JoinPoint joinPoint) throws Exception {
System.out.println("after");
Object[] objects = joinPoint.getArgs();
for (Object o : objects) {
if (o instanceof AbstractRequest) {
String method = joinPoint.getSignature().getName();
String key = method + ((AbstractRequest)o).getSessionId();
del(method + o.toString());
System.out.println("釋放鎖成功");
break;
}
}
}