SpringBoot 自定义注解+AOP+Redis 实现分布式锁

前言

分布式环境下多个不同线程需要对共享资源进行同步,那么用Java的锁机制就无法实现了,这个时候就必须借助分布式锁来解决分布式环境下共享资源的同步问题。

aop 通过设置切面,当切面设置的目标类的方法被调用时,aop 框架会拦截此次调用,源码中 pointCut 类里有两个核心属性,即 ClassFilter 类过滤器与MethodMatcher 方法匹配器,aop基于其两个核心来进行拦截,拦截之后aop机制会通过jdk或cglib生成动态代理对象,调用增强类的增强方法进行功能织入。

@AspectJ 是一种将切面声明为带有注解的常规 Java 类的样式。 @AspectJ 样式是AspectJ project作为 AspectJ 5 版本的一部分引入的。 Spring 使用 AspectJ 提供的用于切入点解析和匹配的库来解释与 AspectJ 5 相同的 注解。但是,AOP 运行时仍然是纯 Spring AOP,并且不依赖于 AspectJ 编译器或编织器。

注解类

所要加锁的方法加的注解

package com.test.redislock.annotation;

import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 * 分布式锁注解(API)
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DistributedLockApi {


    /**
     * redis分布式锁的前缀,必填
     *
     * @return redis分布式锁的前缀
     */
    String prefix();

    /**
     * 锁value
     */
    String value() default "lock_value";

    /**
     * 锁超时时间
     */
    long timeout() default 10;

    /**
     * 超时时间单位
     */
    TimeUnit timeUnit() default TimeUnit.MINUTES;

    /**
     * 被加锁方法执行完是否立即释放锁
     */
    boolean immediatelyUnLock() default true;

    /**
     * 等待获取锁时间(秒)
     */
    long waitLockSecondTime() default 0;

    /**
     * 最大重试次数
     */
     long maxRetryTimes () default 0;

    /**
     * 是否等待锁释放,默认不等待直接报错
     *
     * @return 是否等待锁释放
     */
    boolean retry() default false;
}

BusinessId 注解对于一个任务比如task1 有唯一标识id 加这个使锁粒度最小化

package com.test.redislock.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author liu
 * @date 2022年05月21日 9:06
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface BusinessId {
}


redis加锁解锁操作类

接口

package com.test.redislock.service;


import lombok.NonNull;

/**
 * @author liu
 * @date 2022年05月21日 8:56
 */
public interface RedisLockService {
    /**
     * 获取锁
     *
     * @param lock 锁信息
     * @throws RedisLockException 获取锁失败时抛出异常
     */
    void lock(@NonNull RedisLockDto lock) throws RedisLockException;

    /**
     * 释放锁
     *
     * @param lock 锁信息
     */
    void unlock(@NonNull RedisLockDto lock);

    /**
     * 锁续命
     *
     * @param lock 锁信息
     * @return 续命是否成功
     */
    boolean extend(@NonNull RedisLockDto lock);
}


实现类

package com.test.redislock.service;


import lombok.AllArgsConstructor;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;

import java.util.concurrent.TimeUnit;

/**
 * @author liu
 * @date 2022年05月21日 8:58
 */
@AllArgsConstructor
@Slf4j
public class RedisLockServiceImpl implements RedisLockService {

    /**
     * redis template
     */
    private final RedisTemplate<String, String> redisTemplate;

    /**
     * 最大重试次数
     */
    private final int maxRetryTimes;

    /**
     * 等待时间
     */
    private final long delayTime;

    @Override
    public void lock(@NonNull RedisLockDto lock) throws RedisLockException {
        if (lock.getRetry()) {
            lockWithRetry(lock);
        } else {
            lockWithoutRetry(lock);
        }
    }

    /**
     * 不带重试的获取锁
     *
     * @param lock 锁
     * @throws RedisLockException 当获取不到锁时抛出异常
     */
    private void lockWithoutRetry(@NonNull RedisLockDto lock) throws RedisLockException {
        if (!tryLock(lock)) {
            log.warn("failed get lock , key: {}", this.getLockKey(lock));
            throw new RedisLockException(lock.getPrefix(), lock.getBusinessId(), lock, getLockValue(lock));
        }
        log.info("successfully get lock , key: {}", this.getLockKey(lock));
    }

    /**
     * 带重试的获取锁
     *
     * @param lock 锁
     * @throws RedisLockException 如果尝试获取锁超过指定次数时,抛出异常
     */
    private void lockWithRetry(RedisLockDto lock) throws RedisLockException {
        int retryTimes = 0;
        while (!tryLock(lock)) {
            ++retryTimes;

            long realMaxRetryTimes;
            if (lock.getMaxRetryTimes() != null) {
                realMaxRetryTimes = lock.getMaxRetryTimes();
            } else {
                realMaxRetryTimes = this.maxRetryTimes;
            }
            if (realMaxRetryTimes >= 0 && retryTimes >= realMaxRetryTimes) {
                log.warn("failed get lock ,key: {}", this.getLockKey(lock));
                throw new RedisLockException(lock.getPrefix(), lock.getBusinessId(), lock, getLockValue(lock));
            }

            long realDelayTime;
            if (lock.getDelayTime() != null) {
                realDelayTime = lock.getDelayTime();
            } else {
                realDelayTime = this.delayTime;
            }

            try {
                Thread.sleep(realDelayTime);
            } catch (InterruptedException | IllegalArgumentException e) {
                // 什么也不做
            }
        }
        log.info("successfully get lock ,key: {}", this.getLockKey(lock));
    }

    @Override
    public void unlock(@NonNull RedisLockDto lock) {
        if (lock.getLockValue().equals(redisTemplate.opsForValue().get(this.getLockKey(lock)))) {
            redisTemplate.delete(lock.getKey());
            log.info("successfully delete lock , key:{}", this.getLockKey(lock));
        }
    }

    /**
     * 尝试获取锁
     *
     * @param lock 锁
     * @return 获取结果
     */
    private boolean tryLock(@NonNull RedisLockDto lock) {
        long start = System.currentTimeMillis();
        if(lock.getDelayTime()>0 && (System.currentTimeMillis()-start > 1000* (lock.getDelayTime()))){
            return false;
        }
        if(System.currentTimeMillis()-start > lock.getDelayTime()*1000){
            return false;
        }
        Boolean aBoolean = redisTemplate.opsForValue()
                .setIfAbsent(lock.getKey(), lock.getLockValue(), lock.getExpiredTime(), lock.getTimeUnit());
        if(!aBoolean){
            return tryLock(lock);
        }
        return Boolean.TRUE.equals(aBoolean );
    }

    /**
     * 获取锁当前的值
     *
     * @param lock 锁
     * @return 获取结果
     */
    private String getLockValue(@NonNull RedisLockDto lock) {
        return redisTemplate.opsForValue().get(this.getLockKey(lock));
    }

    /**
     * 锁续命
     *
     * @return 续命是否成功
     */
    @Override
    public boolean extend(@NonNull RedisLockDto lock) {
        return Boolean.TRUE.equals(
                redisTemplate.expire(this.getLockKey(lock), lock.getExpiredTime(), TimeUnit.MILLISECONDS)
        );
    }

    /**
     * 获取锁的key
     *
     * @param lock 锁
     * @return 锁的key
     */
    private String getLockKey(RedisLockDto lock) {
        return lock.getKey();
    }
}


异常类


import lombok.Getter;

/**
 * @author liu
 * @date 2022年05月21日 9:02
 */
public class RedisLockException extends Exception {
    /**
     * redis锁前缀
     */
    @Getter
    private String prefix;

    /**
     * 业务id
     */
    @Getter
    private String businessId;

    /**
     * 锁配置对象
     */
    @Getter
    private RedisLockDto lock;

    /**
     * 锁的值
     */
    @Getter
    private String lockValue;

    public RedisLockException(String prefix, String businessId, RedisLockDto lock, String lockValue) {
        super("failed to acquire redis lock,prefix is " + prefix + ",business id is " + businessId + ",lock value is " + lockValue);
        this.prefix = prefix;
        this.businessId = businessId;
        this.lock = lock;
        this.lockValue = lockValue;
    }
}


切面类

;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.UUID;

/**

 * 分布式锁切面
 */
@SuppressWarnings("DuplicatedCode")
@Slf4j
@Aspect
@Component
public class DistributedLockAspect {

    /**
     * StringRedisTemplate
     */
    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    RedisLockService redisLockService;

    /**
     * 最大重试次数
     */
    @Value("${ngsoc.redis-lock.maxRetryTimes:10}")
    private int maxRetryTimes;

    /**
     * 等待时间
     */
    @Value("${ngsoc.redis-lock.delayTime:100}")
    private long delayTime;

  

    /**
     * API切点
     */
    @Pointcut("@annotation(com.andon.springbootdistributedlock.annotation.DistributedLockApi)")
    public void apiPointCut() {
    }


    /**
     * API加分布式锁
     */
    @SneakyThrows
    @Around(value = "apiPointCut() && @annotation(distributedLockApi)")
    public Object apiAround(ProceedingJoinPoint joinPoint, DistributedLockApi distributedLockApi) throws  Exception{
        // 1.从注解获取redis lock的配置参数
        RedisLockDto lock = this.getRedisLockFromAnnotation(joinPoint);
        // 2.尝试获取锁
        try {
            redisLockService.lock(lock);
            // 放行,目标方法的执行
            return joinPoint.proceed();
        } finally {
            // 3.释放锁
            redisLockService.unlock(lock);
        }
       // throw new DistributedLockException("请勿重复操作!!");
    }



    /**
     * 从注解获取分布式锁相关信息
     *
     * @param joinPoint 切点
     * @return redis锁对象
     */
    private RedisLockDto getRedisLockFromAnnotation(ProceedingJoinPoint joinPoint) {


        // 获取目标方法
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();

        // 获取方法上指定类型的注解
        DistributedLockApi redisLock = method.getAnnotation(DistributedLockApi.class);

        // 获取目标类名方法名
        String className = method.getDeclaringClass().getName();
        String methodName = method.getName();
        // 从注解对象中获取元素的值
        String prefix = redisLock.prefix();
        String keyPre = prefix + "_" + className + "_" + methodName;
        if (StringUtils.isEmpty(redisLock.prefix())) {
            // 如果redis分布式锁的key的前缀为空,则使用目标方法的名称作为前缀
            prefix = method.getName();
        }

        // 获取方法中每个参数前加的注解的值,一个方法可能有多个参数,一个参数前可能加多个注解,所以为二维数组
        Annotation[][] annotations = method.getParameterAnnotations();
        StringBuilder businessIdBuilder = new StringBuilder();
        for (int i = 0; i < annotations.length; i++) {
            // 获取目标方法的第i个参数
            Object param = joinPoint.getArgs()[i];
            // 获取目标方法的第i个参数前所加的所有注解
            Annotation[] paramAnnotations = annotations[i];
            if (param == null || paramAnnotations.length == 0) {
                continue;
            }
            // 遍历目标方法第i个参数前的所有注解
            for (Annotation annotation : paramAnnotations) {
                // 判断注解类型是否BusinessId
                if (annotation.annotationType().equals(BusinessId.class)) {
                    businessIdBuilder.append(param);
                }
            }
        }

        return RedisLockDto.builder()
                .prefix(prefix)
                .key(keyPre + businessIdBuilder.toString())
                .timeUnit(redisLock.timeUnit())
                .delayTime(redisLock.waitLockSecondTime()>0?redisLock.waitLockSecondTime():delayTime)
                .expiredTime(redisLock.timeout())
                .retry(redisLock.retry())
                .maxRetryTimes(redisLock.maxRetryTimes())
                .lockValue(UUID.randomUUID().toString())
                .businessId(businessIdBuilder.toString())
                .build();
    }


}

配置类


import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import lombok.Getter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * @author liu
 * @date 2022年05月21日 9:11
 */
@Configuration
public class RedisLockAutoConfiguration {

    /**
     * 最大重试次数
     */
    @Value("${ngsoc.redis-lock.maxRetryTimes:10}")
    @Getter
    private int maxRetryTimes;

    /**
     * 等待时间
     */
    @Value("${ngsoc.redis-lock.delayTime:100}")
    @Getter
    private long delayTime;

    /**
     * 配置RedisLockAspect
     */
    /*@Bean
    public RedisLockAspect redisLockAspect() {
        return new RedisLockAspect();
    }*/

    /**
     * 配置 RedisTemplate 及其序列化方式
     */
    @Bean(value = "redisTemplate")
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(connectionFactory);
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(
                Object.class
        );
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.activateDefaultTyping(
                LaissezFaireSubTypeValidator.instance,
                ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY
        );
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
        StringRedisSerializer redisSerializer = new StringRedisSerializer();
        redisTemplate.setKeySerializer(redisSerializer);
        redisTemplate.setHashKeySerializer(redisSerializer);
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }

    /**
     * 配置 RedisLockService
     */
    @Bean
    public RedisLockService redisLockService(RedisTemplate<String, String> redisTemplate) {
        return new RedisLockServiceImpl(redisTemplate, maxRetryTimes, delayTime);
    }
}


dto传输对象

import lombok.Builder;
import lombok.Data;
import lombok.experimental.Tolerate;

import java.util.concurrent.TimeUnit;

/**
 * @author liu
 * @date 2022年05月21日 8:55
 */
@Data
@Builder
public class RedisLockDto {
    @Tolerate
    public RedisLockDto() {
    }

    /**
     * redis lock的前缀
     */
    private String prefix;
    /**
     * redis lock的前缀
     */
    private String key;

    /**
     * 过期时间(单位:毫秒)
     */
    private Long expiredTime;

    /**
     * 时间单位
     */
    private TimeUnit timeUnit;

    /**
     * 业务ID
     */
    private String businessId;

    /**
     * 是否等待重试
     */
    private Boolean retry = true;

    /**
     * 锁的值
     */
    private String lockValue;

    /**
     * 最大重试次数
     */
    private Long maxRetryTimes;

    /**
     * 等待时间
     */
    private Long delayTime;
}


启动类

@SpringBootApplication
public class SpringbootDistributedLockApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootDistributedLockApplication.class, args);
    }

}

配置文件

server:
  port: 8088
spring:
  #============== redis ===================
  redis:
    host: 127.0.0.1
    port: 6379
ngsoc:
  redis-lock:
    maxRetryTimes: 30
    delayTime: 3000

测试类


import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.TimeUnit;


@Slf4j
@RequestMapping(value = "/api")
@RestController
public class ApiController {

    @DistributedLockApi(prefix = "test_lock",timeout = 20, timeUnit = TimeUnit.SECONDS, immediatelyUnLock = false)
    @GetMapping(value = "/test")

    public String test(@BusinessId String taskId) {

        log.info("test!!");
        log.info("test!!");
        log.info("test!!");
        log.info("test!!");
        log.info("test!!");
        log.info("test!!");
        try {
            Thread.sleep(9999L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "test!!";
    }
    @DistributedLockApi(prefix = "test_lock",timeout = 120, timeUnit = TimeUnit.SECONDS, immediatelyUnLock = false, waitLockSecondTime = 15)
    @GetMapping(value = "/test2")

    public String test2(@BusinessId String taskId) {

        log.info("test!!");
        log.info("test!!");
        log.info("test!!");
        log.info("test!!");
        log.info("test!!");
        log.info("test!!");
        try {
            Thread.sleep(20000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "test!!";
    }
    @DistributedLockApi(prefix = "test_lock",timeout = 180,retry=true,maxRetryTimes=10, timeUnit = TimeUnit.SECONDS, immediatelyUnLock = false, waitLockSecondTime = 150)
    @GetMapping(value = "/test3")

    public String test3(@BusinessId String taskId) {

        log.info("test!!");
        log.info("test!!");
        log.info("test!!");
        log.info("test!!");
        log.info("test!!");
        log.info("test!!");
        try {
            Thread.sleep(20000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "test!!";
    }
}

猜你喜欢

转载自blog.csdn.net/liuerchong/article/details/124900206