注解方式优雅实现Redission

1、背景

实际开发过程中,一些高并发场景需要保证接口执行的一致性,通常采用加锁的方式,本地锁Reentrantlock和Synchnorized虽然可以实现但是不适用于分布式部署模式,而redis的setnx锁无法保证原子性,故而采用redission锁,它不仅适用于分布式服务还能保证原子性。

2、Redisson分布式锁常规使用

2.1 Lock

getLock获取锁,lock.lock进行加锁,会出现的问题就是lock拿不到锁一直等待,会进入阻塞状态,显然这样是不好的。

public void getLock(){
    //获取锁
    RLock lock = redissonClient.getLock(lockKey);
    try {
        // 2.加锁
        lock.lock();
    } catch (InterruptedException e) {
        e.getStackTrace();
    } finally {
        // 3.解锁
        lock.unlock();
        System.out.println("Finally,释放锁成功");
    }

2.2 TryLock

返回boolean类型,尝试获取锁,获取到就返回true,获取失败就返回false,不会使获不到锁的线程一直处于等待状态,返回false可以继续执行下面的业务逻辑。

自带watchDog看门狗机制,主要作用就是给快过期的锁进行续期,主要用途就是使拿到锁的有限时间让业务执行完,再进行锁释放。

    public void test() {
        String lockKey = "redisson:lock";
        RLock lock = null;
        try {
            // 使用redisson,自带看门狗
            lock = redissonClient.getLock(lockKey);
            boolean tryLock = lock.tryLock(2, TimeUnit.SECONDS);
            if (tryLock) {
                Thread.sleep(1000);
                System.out.println("恭喜,抢到锁了!");
            } else {
                throw new RuntimeException("很遗憾,没抢到锁");
            }
        } catch (InterruptedException e) {
            System.out.println("锁异常");
        } finally {
            // try finally不能包含加锁的那段代码,否则加锁失败会走到finally里,从而释放别的线程的锁
            System.out.println("业务结束,释放锁!");
            if (null != lock && lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }

3、自定义注解实现锁机制

通常我们都会将redisson实例注入到方法类里面,然后调用加锁方法进行加锁,如果其他业务方法也需要加锁执行,将会产生很多重复代码,由此采用AOP切面的方式,只需要通过注解的方式就能将方法进行加锁处理。

3.1 自定义注解

@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 "系统正常处理,请稍后提交";
​
    int waitTime() default 1;
}

3.2 切面类实现

@Aspect
@Component
public class DistributedLockHandler {
    private static final Logger log = LoggerFactory.getLogger(DistributedLockHandler.class);
    @Resource
    private RedissonClient redissonClient;
​
    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 = null;
        RLock lock = null;
        try {
            // 使用redisson,自带看门狗
            lock = redissonClient.getLock(lockName);
            boolean tryLock = lock.tryLock(waitTime, leaseTime, TimeUnit.SECONDS);
            if (tryLock) {
                Thread.sleep(1000);
                log.info("恭喜,抢到锁了!");
            } else {
                throw new RuntimeException(errorDesc);
            }
            var8 = joinPoint.proceed();
        } catch (InterruptedException e) {
            log.error("锁异常!");
        } finally {
            // try finally不能包含加锁的那段代码,否则加锁失败会走到finally里,从而释放别的线程的锁
            log.info("业务结束,释放锁!");
            if (null != lock && lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
        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;
            }
        }
    }
}

3.3 具体使用

注解方式优雅实现Redission_第1张图片

方法头加自定义注解,key参数代表需要加锁的key,errorDesc获取锁失败提示报错信息。

这边我将项目通过修改端口启动了两个服务,分别是8899和9255

注解方式优雅实现Redission_第2张图片

注解方式优雅实现Redission_第3张图片

通过apifox我们分别调用这两个接口,结果如下,8899端口获取到了锁,9255端口tryLock获取锁失败,实现了加锁逻辑。

image-20231128140650294

注解方式优雅实现Redission_第4张图片

4、总结

分布式锁的使用场景还是需要多注意下,根据业务场景来,并发量不大的情况下,其实没有必要加,可能在移动端操作比较频繁的情况下需要注意并发,切勿不要盲目加锁,多少会影响一些性能。

你可能感兴趣的:(redis)