Theme NexT works best with JavaScript enabled
0%

基于AOP和redis实现接口锁

在一些新增接口或者更新接口中,由于服务器可能反应稍微有点延迟,导致同一个操作被发送多次让服务器执行,为了实现接口的幂等性,使用redis锁来实现,为了方便在多个接口实现该功能,使用aop来实现加锁。

该方法适用于单个服务的项目。

一、引入依赖

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

用于操作redis,在使用前,需要根据项目判断是否自定义redisTemplate

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Configuration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
public class RedisConfig {
@Bean(name = "stringTemplate")
public RedisTemplate<String, String> stringRedisTemplate(RedisConnectionFactory redisConnectionFactory){
RedisTemplate<String, String> template = new RedisTemplate<>();
template.setValueSerializer(new StringRedisSerializer());
template.setKeySerializer(new StringRedisSerializer());
template.setConnectionFactory(redisConnectionFactory);
return template;
}

@Bean(name = "memberTemplate")
public RedisTemplate<String, Member> memberRedisTemplate(RedisConnectionFactory redisConnectionFactory){
RedisTemplate<String, Member> template = new RedisTemplate<>();
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new FastJsonRedisSerializer<>(Member.class));
template.setConnectionFactory(redisConnectionFactory);
return template;
}


}

二、定义切点注解

此注解添加在需要加锁的方法上,根据需要添加参数,该参数用于生成锁的key值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
* 被此注解标注的方法,将被使用redis的接口锁,用于aop对方法的识别
* @author baiyu
* @data 2020-07-08 15:39
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ApiLock {
/**
* 如果type是直接参数,那么,直接从此获取参数
* @return
*/
String[] paramNames();

/**
* 是直接参数还是间接参数
* @return
*/
ApiLockEnum type();

/**
* 如果不是直接参数@ApiLockEnum.UNDIRECT,就先找到此参数,在从此参数中获取上面paramNames的具体参数
* @return
*/
String outerParam() default "";

}

用于标记直接参数或者间接参数的常量

1
2
3
public enum ApiLockEnum {
DIRECT, UNDIRECT;
}

三、AOP

实现加锁解锁的aop主逻辑,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
/**
* 接口redis锁的aop,redis锁的获取与释放的统一管理
* @author zhengguochun
* @data 2020-07-08 15:02
*/
@Aspect
@Component
@Slf4j
public class ApiLockAop {

@Autowired
@Qualifier("stringTemplate")
private RedisTemplate<String, String> redisTemplate;

private static final String CURRENT_AOP_TYPE_PREFIX = "ApiLock AOP : ";

@Pointcut("@annotation(com.ymkj.property.annotation.ApiLock)")
public void cutApiLock(){
}
@Around("cutApiLock()")
public Object doAround(ProceedingJoinPoint pjp) throws NoSuchFieldException, IllegalAccessException {
log.info("in aop ");
MethodSignature signature =(MethodSignature) pjp.getSignature();
// 1. 获取propertyId
Class targetClass = pjp.getTarget().getClass();
Class superClass = targetClass.getSuperclass();
Field field = superClass.getDeclaredField("propertyId");
field.setAccessible(true);
Object propertyIdObj = field.get(pjp.getTarget());
if (null == propertyIdObj){
throw new ParameterException(ResponseCode.PROPERTY_ID_NULL);
}
Integer propertyId = (Integer) propertyIdObj;
// 2. 获取ApiLock注解中的参数,判断是否是直接参数
ApiLock apiLock = signature.getMethod().getAnnotation(ApiLock.class);
// 3. 获取ParameterName , args,根据ApiLock中的参数获取参数名
String[] parameterNames = signature.getParameterNames();
Object[] args = pjp.getArgs();
List<String> paraNames = new ArrayList<>(Arrays.asList(apiLock.paramNames()));
List<String> finalResult = new ArrayList<>();
if (ApiLockEnum.DIRECT.equals(apiLock.type())){
// 2.1 是直接参数, 则直接去方法参数中取
int paramSize = parameterNames.length;
for (int i = 0; i < paramSize; i++){
if (paraNames.contains(parameterNames[i])){
finalResult.add(args[i].toString());
}
}
}else{
// 2.2 不是直接参数,则先去apiLock.outerParam中取参数
String outerParam = apiLock.outerParam();
int paramSize = parameterNames.length;
for (int i = 0; i < paramSize; i++){
if (outerParam.equals(parameterNames[i])){
Object outerParamObject = args[i];
for (String paraName : paraNames) {
Field currentParam = outerParamObject.getClass().getDeclaredField(paraName);
currentParam.setAccessible(true);
Object paramObj = currentParam.get(outerParamObject);
if (null != paramObj){
finalResult.add(paramObj.toString());
}else {
log.error("param {} 为空", paraName);
throw new ParameterException(ResponseCode.PARAMETER_NULL_EXCEPTION);
}
}
}
}
}
String redisKey = RedisDistributedLock.generateApiKey(propertyId, finalResult);
log.info("redis key : {}", redisKey);
// 5. 将参数传入acquireLock方法,加锁
RedisDistributedLock redisDistributedLock = new RedisDistributedLock(redisTemplate,redisKey);
String result = redisDistributedLock.aquireNeverWaitLock();
log.info("get lock result : {}", result);
if (null == result){
throw new RedisDistributedLockException(ResponseCode.CHECK_IN_HANDLING);
}
Object response = null;
// 6. 执行接口
try {
log.info("开始执行接口");
response = pjp.proceed();
} catch (Throwable throwable) {
log.error("{}, message {}", CURRENT_AOP_TYPE_PREFIX , throwable.getMessage());
throw new AopException(ResponseBase.expectationFailed(throwable.getMessage()));
}finally {
// 释放锁
log.info("release lock");
boolean re = redisDistributedLock.release();
if (re){
log.info("释放成功");
}else {
log.info("释放失败");
}
}
return response;
}
}

四、redis锁的工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
@Slf4j
public class RedisDistributedLock {
private static final String REDIS_LOCK_PREFIX = "redis_lock-";
private static final int MANEUVER_SLEEP_TIME = 100;
private static final String BATCH_CHECK_KEY = "batchCheckMember-";
private static final String API_CHECK_KEY = "api_key-";

private String lockKey;

private String value;

int expireTime = 10 * 1000;

int acquireTimeout = 1 * 1000;

private static final int NEVER_WAIT_LOCK_EXPIRE_TIME = 6;

private static final String COMPARE_AND_DELETE =
"if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";

/**
* 释放锁成功返回值
*/
private static final Long RELEASE_LOCK_SUCCESS_RESULT = 1L;

private RedisTemplate<String, String> template;

public RedisDistributedLock(RedisTemplate<String, String> template, String lockKey){
this.template = template;
this.lockKey = lockKey;
}

public RedisDistributedLock(RedisTemplate<String, String> template, String lockKey, int acquireTimeout) {
this.template = template;
this.lockKey = lockKey;
this.acquireTimeout = acquireTimeout;
}


public RedisDistributedLock(RedisTemplate<String, String> template, String lockKey, int acquireTimeout, int expireTime) {
this.template = template;
this.lockKey = lockKey;
this.acquireTimeout = acquireTimeout;
this.expireTime = expireTime;
}


public String acquire() throws RedisDistributedLockException {
// 获取锁的超时时间,超过这个时间则放弃获取锁
long end = System.currentTimeMillis() + acquireTimeout;
// 随机生成一个value
value = lockKey;
while (System.currentTimeMillis() < end) {
//String result = redisTemplate.set(lockKey, requireToken, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
Boolean result = false;
try {
result = template.opsForValue().setIfAbsent(lockKey ,value, expireTime, TimeUnit.MILLISECONDS);
} catch (Exception e) {
log.error("acquire lock due to error ", e);
throw new RedisDistributedLockException(ResponseCode.REDIS_LOCK_ACQUIRE_FAILED);
}
if (result) {
log.info("{} acquire lock", lockKey);
return value;
}
try {
Thread.sleep(MANEUVER_SLEEP_TIME);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
return null;
}


public boolean release() throws RedisDistributedLockException {
if(value == null){
return false;
}
List<String> keys = Collections.singletonList(lockKey);

try {
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(COMPARE_AND_DELETE, Long.class);
Long result = template.execute(redisScript,keys, value);
log.info("结果 {}", result);
if (Objects.equals(result, RELEASE_LOCK_SUCCESS_RESULT)){
log.info("{} release lock", lockKey);
return true;
}
}catch (Exception e){
throw new RedisDistributedLockException(ResponseCode.REDIS_LOCK_RELEASE_FAILED);
}
return false;
}

/**
* 不等待,不block的排他锁,有人拿到锁之后,在持有锁的期间,其他人不可以拿到锁,等待主动释放或者超时
* 超时时间6个小时
* @return
*/
public String aquireNeverWaitLock(){
Boolean result = false;
value = lockKey;
try {
result = template.opsForValue().setIfAbsent(lockKey ,value, NEVER_WAIT_LOCK_EXPIRE_TIME, TimeUnit.HOURS);
} catch (Exception e) {
log.error("acquire lock due to error ", e);
throw new RedisDistributedLockException(ResponseCode.REDIS_LOCK_ACQUIRE_FAILED);
}
if (result){
return lockKey;
}
return null;
}


public static String generateKey(Integer propertyId, String openId){
//return new StringBuilder(REDIS_LOCK_PREFIX).append(propertyId.toString()).append("-").append(openId).toString();
return REDIS_LOCK_PREFIX + propertyId + "-" + openId;
}

public static String generateBatchKey(int propertyId){
return REDIS_LOCK_PREFIX + BATCH_CHECK_KEY + propertyId;
}

public static String generateApiKey(int propertyId, List<String> param){
if (null == param || param.size() <= 0){
throw new ParameterException(ResponseCode.PARAMETER_NULL_EXCEPTION);
}
StringBuilder ids = new StringBuilder();
for (String oneId : param) {
ids.append(oneId);
}
String finalStr = "";
log.info("origin param : {}", ids.toString());
if (ids.length() > 32){
finalStr = DigestUtils.md5DigestAsHex(ids.toString().getBytes());
}else{
finalStr = ids.toString();
}
return REDIS_LOCK_PREFIX + API_CHECK_KEY + propertyId + "_" + finalStr;
}
}
坚持原创技术分享,您的支持将鼓励我继续创作!

欢迎关注我的其它发布渠道