干货,springboot自定义注解实现分布式锁详解_勇往直前的专栏-CSDN博客_分布式锁注解

背景

在互联网的很多场景下,会产生资源竞争,如果是单机环境,简单加个锁就能解决问题;但是在集群环境下(分布式环境),多个客户端在一个很短的时间内竞争同一服务端资源(如抢购场景),或者同一客户端重复提交请求,如果请求不具备幂等性,就需要用到分布式锁的解决方案。

背景知识

关于分布式锁,可以看看我之前的文章《基于Spring boot 2.1 使用redisson实现分布式锁》,当时只是利用redisson的API实现了功能,现在,我们要利用自定义注解方式,让分布锁功能更方便使用。关于自定义注解,请移步《深入理解Java:注解(Annotation)自定义注解入门

解决方案

1、引入redis和redisson的依赖

<dependency>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-starter-data-redis</artifactId>    <exclusions>        <exclusion>            <groupId>redis.clients</groupId>            <artifactId>jedis</artifactId>        </exclusion>        <exclusion>            <groupId>io.lettuce</groupId>            <artifactId>lettuce-core</artifactId>        </exclusion>    </exclusions></dependency>                <dependency>    <groupId>redis.clients</groupId>    <artifactId>jedis</artifactId></dependency>        <!--spring2.X集成redis所需common-pool2,使用jedis必须依赖它 --><dependency>    <groupId>org.apache.commons</groupId>    <artifactId>commons-pool2</artifactId></dependency><dependency>    <groupId>org.redisson</groupId>    <artifactId>redisson</artifactId>    <version>3.10.5</version></dependency>

2、接口RedissonLocker.java,定义分布式锁的一些常用API

public interface RedissonLocker {        RLock lock(String lockKey);     RLock lock(String lockKey, int timeout);     RLock lock(String lockKey, TimeUnit unit, int timeout);     boolean tryLock(String lockKey, TimeUnit unit, LockConstant lockTime);        boolean fairLock(String lockKey, TimeUnit unit, LockConstant lockTime);     void unlock(String lockKey);     void unlock(RLock lock); }

3、实现类RedissonLockerImpl.java,接口RedissonLocker的实现类

/** * 基于Redisson实现分布式锁 *  * @author  * 2019年4月3日 *  * {@link https://github.com/redisson/redisson/wiki} * */@Componentpublic class RedissonLockerImpl implements RedissonLocker {     @Autowired    private RedissonClient redissonClient;         @Autowired    private RedisTemplate<Object, Object> redisTemplate;      /**     * key 值是否存在     *      * @param key     * @return     */    public boolean existKey(String key) {        return redisTemplate.hasKey(key);    }        /************************** 可重入锁 **************************/     /**     * 拿不到lock就不罢休,不然线程就一直block 没有超时时间,默认30s     *      * @param lockKey     * @return     */    @Override    public RLock lock(String lockKey) {        RLock lock = redissonClient.getLock(lockKey);        lock.lock();        return lock;    }     /**     * 自己设置超时时间     *      * @param lockKey 锁的key     * @param timeout 秒 如果是-1,直到自己解锁,否则不会自动解锁     * @return     */    @Override    public RLock lock(String lockKey, int timeout) {        RLock lock = redissonClient.getLock(lockKey);        lock.lock(timeout, TimeUnit.SECONDS);        return lock;    }     /**     * 自己设置超时时间     *      * @param lockKey 锁的key     * @param unit    锁时间单位     * @param timeout 超时时间     *      */    @Override    public RLock lock(String lockKey, TimeUnit unit, int timeout) {        RLock lock = redissonClient.getLock(lockKey);        lock.lock(timeout, unit);        return lock;    }     /**     * 尝试加锁,最多等待waitTime,上锁以后leaseTime自动解锁     *      * @param lockKey   锁key     * @param unit      锁时间单位     * @param waitTime  等到最大时间,强制获取锁     * @param leaseTime 锁失效时间     * @return 如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false     */    @Override    public boolean tryLock(String lockKey, TimeUnit unit, LockConstant lockTime) {        RLock lock = redissonClient.getLock(lockKey);        try {            boolean existKey =  existKey(lockKey);            if (existKey) {// 已经存在了,就直接返回                return false;            }            return lock.tryLock(lockTime.getWaitTime(), lockTime.getLeaseTime(), unit);        } catch (InterruptedException e) {            e.printStackTrace();        }        return false;    }     /************************** 公平锁 **************************/    /**     * 尝试加锁,最多等待waitTime,上锁以后leaseTime自动解锁     *      * @param lockKey   锁key     * @param unit      锁时间单位     * @param waitTime  等到最大时间,强制获取锁     * @param leaseTime 锁失效时间     * @return 如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false     */    public boolean fairLock(String lockKey, TimeUnit unit, LockConstant lockTime) {        RLock fairLock = redissonClient.getFairLock(lockKey);        try {            boolean existKey =  existKey(lockKey);            if (existKey) {// 已经存在了,就直接返回                return false;            }            return fairLock.tryLock(lockTime.getWaitTime(), lockTime.getLeaseTime(), unit);        } catch (InterruptedException e) {            e.printStackTrace();        }        return false;    }        /**     * 尝试加锁,最多等待waitTime,上锁以后leaseTime自动解锁     *      * @param lockKey   锁key     * @param unit      锁时间单位     * @param waitTime  等到最大时间,强制获取锁,默认是三秒钟     * @param leaseTime 锁失效时间     * @return 如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false     */    public boolean fairLock(String lockKey, TimeUnit unit, int leaseTime) {        RLock fairLock = redissonClient.getFairLock(lockKey);        try {            boolean existKey =  existKey(lockKey);            if (existKey) {// 已经存在了,就直接返回                return false;            }            return fairLock.tryLock(3, leaseTime, unit);        } catch (InterruptedException e) {            e.printStackTrace();        }        return false;    }     /**     * 释放锁     *      * @param lockKey 锁key     */    @Override    public void unlock(String lockKey) {        try {            RLock lock = redissonClient.getLock(lockKey);            lock.unlock();        } catch (Exception e) {        }    }     /**     * 释放锁     */    @Override    public void unlock(RLock lock) {        try {            lock.unlock();        } catch (Exception e) {        }    }  }

4、自定义注解 RlockRepeatSubmit

/** * 防止重复提交的注解 *  * @author  * 2019年6月18日 * */@Retention(RetentionPolicy.RUNTIME)@Target({ ElementType.METHOD })@Documentedpublic @interface RlockRepeatSubmit {     /**     * 分布式锁枚举类     * @return     */    LockConstant lockConstant();    }

5、枚举LockConstant.java,主要用于使用注解时传参,来区分不同的业务类型

/** * 分布式锁枚举类 * */public enum LockConstant {     CASHIER("cashierLock:", 3, 300, "请勿重复点击收银操作!!"), // 收银锁    SUBMIT_ORDER("submitOrderLock:", 3, 30, "请勿重复点击下单!!"), // 下单锁    ......     COMMON_LOCK("commonLock:", 3, 120, "请勿重复点击");// 通用锁常量     private String keyPrefix; // 分布式锁前缀    private int waitTime;// 等到最大时间,强制获取锁    private int leaseTime;// 锁失效时间    private String message;// 加锁提示     // 构造方法    private LockConstant(String keyPrefix, int waitTime, int leaseTime, String message) {        this.keyPrefix = keyPrefix;        this.waitTime = waitTime;        this.leaseTime = leaseTime;        this.message = message;    }     // 省略getter,setter }

6、RlockRepeatSubmitAspect.java,定义AOP类,解析自定义注解RlockRepeatSubmit,进行分布式锁操作

/** *  * 防止重复提交分布式锁拦截器 *  * @author  * 2019年6月18日 * */@Aspect@Componentpublic class RlockRepeatSubmitAspect {     @Resource    private RedissonLockerImpl redissonLocker;     /***     * 定义controller切入点拦截规则,拦截RlockRepeatSubmit注解的业务方法     */    @Pointcut("@annotation(xx.xxx.RlockRepeatSubmit)")    public void pointCut() {    }     /**     * AOP分布式锁拦截     *      * @param request     * @param response     * @param handler     * @return     * @throws Exception     */    @Around("pointCut()")    public Object rlockRepeatSubmit(ProceedingJoinPoint joinPoint) throws Throwable {        // 获取类里面的方法        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();        Method targetMethod = methodSignature.getMethod();        RlockRepeatSubmit repeatSubmit = targetMethod.getAnnotation(RlockRepeatSubmit.class);        // 如果添加了RlockRepeatSubmit这个注解,需要添加分布式锁        if (Objects.nonNull(repeatSubmit)) {            // 获取参数            Object[] args = joinPoint.getArgs();            // 进行一些参数的处理,比如获取订单号,操作人id等                        ......             HttpServletRequest request = getRequest();            if (null != request && request.getParameterMap().containsKey("accessToken")) {                StringBuffer lockKeyBuffer = new StringBuffer();                LockConstant lockConstant = repeatSubmit.lockConstant();                lockKeyBuffer.append(lockConstant.getKeyPrefix());                String accessToken = request.getParameterMap().get("accessToken")[0];// 当前用户的token                String path = request.getServletPath();                // 使用用户的token和请求路径作为唯一的标识,如果有orderNo,加上orderNo作为唯一标识                lockKeyBuffer.append("RlockRepeat");                String token =  accessToken + "." + path;                if (null != orderNo) {                    lockKeyBuffer.append(token.hashCode());                    lockKeyBuffer.append(".");                    lockKeyBuffer.append(orderNo);                } else {                    lockKeyBuffer.append(token);                }                // 公平加锁,lockTime后锁自动释放                boolean isLocked = false;                try {                    isLocked = redissonLocker.fairLock(lockKeyBuffer.toString(), TimeUnit.SECONDS, lockConstant);                    if (isLocked) { // 如果成功获取到锁就继续执行                        // 执行进程                        return joinPoint.proceed();                    } else { // 未获取到锁                        //异常处理                                                ......                    }                } catch (Exception e) {                    //异常处理                                          ......                } finally {                    if (isLocked) { // 如果锁还存在,在方法执行完成后,释放锁                        redissonLocker.unlock(lockKeyBuffer.toString());                    }                }            }        }        return joinPoint.proceed();    }     public static HttpServletRequest getRequest() {        ServletRequestAttributes ra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();        return ra.getRequest();    } }

7、在controller层的业务方法使用自定义注解

@RestController@Slf4j@RequestMapping("/order")public class OrderController {         /**     * 业务方法     *     * @param requestForm     * @return     * @throws Throwable     */        @RlockRepeatSubmit(lockConstant = LockConstant.SUBMIT_ORDER)    @RequestMapping(value = "/xxx", method = RequestMethod.POST)    public String submitOrder(Order order) throws Throwable {        //业务方法        ......        } }

OK,大功告成,以后如果哪个业务方法需要加分布式锁,直接在方法上加上自定义注解,并在枚举里定义相关属性即可,是不是很简单?

PS:关于怎么利用JMeter模拟并发请求测试业务方法和分布式锁,下次再讲。


原网址: 访问
创建于: 2021-03-31 17:19:28
目录: default
标签: 无

请先后发表评论
  • 最新评论
  • 总共0条评论