项目实战-面向切面编程-注解动态设置分布式锁

 
        Long userId = UserContext.getUser();
        String key="lock:coupon:uid:"+userId;

RLock lock = redissonClient.getLock(key);
      //尝试获取锁
      boolean success = lock.tryLock();
    //获取锁失败直接抛异常
      if (!success){
          throw new BadRequestException("请求频繁!");
        }

        try {
            //使用代理对象来调用此方法,避免事务失效
            IUserCouponService userCouponService =(IUserCouponService)AopContext.currentProxy();
            //检验是否超领,并且生成用户券
         userCouponService.checkAndCreateCoupon(coupon, userId);
       } finally {
            lock.unlock();
@Transactional
    @Override
        public void checkAndCreateCoupon(Coupon coupon,Long userId){
            //4.是否超出限领数量
            Integer count = lambdaQuery()
                    .eq(UserCoupon::getUserId,userId)
                    .eq(UserCoupon::getCouponId, coupon.getId()).count();
            if (count!=null&&count>=coupon.getUserLimit()){
                throw new BadRequestException("用户超领!");
            }
            //优惠券已发放数量+1

            int num = couponMapper.incrIssueNum(coupon.getId());

            if (num==0){
                throw new BadRequestException("优惠券库存不足!");
            }
            //生成用户券

            UserCoupon userCoupon=new UserCoupon();
            userCoupon.setUserId(userId);
            userCoupon.setCouponId(coupon.getId());
            userCoupon.setStatus(UserCouponStatus.UNUSED);
            LocalDateTime issueBeginTime = coupon.getIssueBeginTime();
            LocalDateTime issueEndTime = coupon.getIssueEndTime();
            //判断是否是以天数为有效期
            if (issueBeginTime==null){
                LocalDateTime now = LocalDateTime.now();
                issueBeginTime= now;
                issueEndTime=issueBeginTime.plusDays(coupon.getTermDays());
            }
            userCoupon.setTermBeginTime(issueBeginTime);
            userCoupon.setTermEndTime(issueEndTime);

            save(userCoupon);

        }

序言

在上述代码中,我们使用redisson构建了简单分布式锁,勉强解决了在集群环境用户抢券发生的一系列并发问题,但是我们并不能动态获取锁。

Redisson的分布式锁使用并不复杂,基本步骤包括:

  • 1)创建锁对象

  • 2)尝试获取锁

  • 3)处理业务

  • 4)释放锁

但是,除了第3步以外,其它都是非业务代码,对业务的侵入较多:

 可以发现,非业务代码格式固定,每次获取锁总是在重复编码。我们可不可以对这部分代码进行抽取和简化呢?

所以,我们可以采用aop切面编程模式来抽取与业务代码无关并且可能出现重复的代码,

可是,我们需要一个切入点,最常见的办法,就是使用注解的方式

于是

1.自定义注解

关键参数:

  • 锁名称

  • 锁等待时间

  • 锁超时时间

  • 时间单位

  • 锁类型(枚举)

  • 失败策略(枚举)

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyLock {
    //- 锁名称
    String name();
    //- 锁等待时间
    long waitTime() default 1;
    //- 锁超时时间
    long leaseTime() default -1;
    //- 时间单位
    TimeUnit unit() default TimeUnit.SECONDS;
    //锁类型
    MyLockType lockType() default MyLockType.RE_ENTRANT_LOCK;

    //失败策略
    MyLockStrategy myLockStrategy() default MyLockStrategy.FAIL_AFTER_RETRY_TIMEOUT;


}

2.锁类型-枚举

public enum MyLockType {
    RE_ENTRANT_LOCK,
    FAIR_LOCK,
    READ_Lock,
    WRITE_LOCK;

}

3.自定义切面

@Component
@Aspect
@RequiredArgsConstructor
public class MyLockAspect implements Ordered {
   private final MyLockFactory myLockFactory;
    @Around("@annotation(myLock)")
    public Object tryLock(ProceedingJoinPoint pjp,MyLock myLock) throws Throwable {
        //创建锁对象
        RLock lock = myLockFactory.getLock(myLock.lockType(), myLock.name());

        //尝试获取锁
        boolean isLock = myLock.myLockStrategy().tryLock(lock,myLock);

        //判断锁是否获取成功
        if (!isLock){
            return null;
        }

        try {
            //执行业务
            return pjp.proceed();
        } finally {
            //释放锁
            lock.unlock();
        }
    }
    //排序,防止使我们的切面比事务的优先级高,保证在事务提交之后,再释放锁,避免锁失效问题
    @Override
    public int getOrder() {
        return 0;
    }
}

除此之外,创建锁对象使,我们采用工厂模式,保证我们在打上注解时,可以随意指定哪种模式的锁;

例如redisson中的锁大概分为:

1.可重入锁

2.公平锁

3.读写锁

所以

4.创建锁-工厂模式

@Component
public class MyLockFactory {

    private final Map<MyLockType,Function<String,RLock>> lockHandler;

    public MyLockFactory(RedissonClient redissonClient) {

        this.lockHandler=new EnumMap<>(MyLockType.class);
        lockHandler.put(RE_ENTRANT_LOCK, redissonClient::getLock);
        lockHandler.put(FAIR_LOCK,redissonClient::getFairLock);
        lockHandler.put(READ_Lock,name->redissonClient.getReadWriteLock(name).readLock());
        lockHandler.put(WRITE_LOCK,name->redissonClient.getReadWriteLock(name).writeLock());
    }

    public RLock getLock(MyLockType myLock,String name){


        return lockHandler.get(myLock).apply(name);
}
}

接下来就是获取锁,常规获取锁需要3个参数:

  • waitTime:获取锁的等待时间。当获取锁失败后可以多次重试,直到waitTime时间耗尽。waitTime默认-1,即失败后立刻返回,不重试。

  • leaseTime:锁超时释放时间。默认是30,同时会利用WatchDog来不断更新超时时间。需要注意的是,如果手动设置leaseTime值,会导致WatchDog失效。

  • TimeUnit:时间单位

 

 boolean isLock = lock.tryLock(1, 10, TimeUnit.SECONDS);

常规情况下,我们还需要写获取锁成功或者失败的逻辑,而我们可以将这些东西封装成一个失败策略,于是:

5.获取锁-策略模式

public enum MyLockStrategy {

    SKIP_FAST(){
        //快速结束
        @Override
        public boolean tryLock(RLock lock, MyLock prop) throws InterruptedException {
            return lock.tryLock(0,prop.leaseTime(),prop.unit());
        }
    },
    FAIL_FAST(){
        //快速失败
        @Override
        public boolean tryLock(RLock lock, MyLock prop) throws InterruptedException {
            boolean b = lock.tryLock(0, prop.leaseTime(), prop.unit());
            if (!b){
                throw new BizIllegalException("操作频繁!");
            }
            return true;
        }
    },
    KEEP_TRYING(){
        //无限重试
        @Override
        public boolean tryLock(RLock lock, MyLock prop) throws InterruptedException {
            boolean b = lock.tryLock( prop.leaseTime(), prop.unit());
            return true;
        }
    },
    SKIP_AFTER_RETRY_TIMEOUT(){
        //重试超时后结束
        @Override
        public boolean tryLock(RLock lock, MyLock prop) throws InterruptedException {

            return lock.tryLock(prop.waitTime(), prop.leaseTime(), prop.unit());
        }
    },
    FAIL_AFTER_RETRY_TIMEOUT(){
        //重试超时后异常
        @Override
        public boolean tryLock(RLock lock, MyLock prop) throws InterruptedException {
            boolean b = lock.tryLock(prop.waitTime(), prop.leaseTime(), prop.unit());
            if (!b){
                throw new BizIllegalException("操作频繁!");
            }
            return true;
        }
    } ;

 public abstract boolean tryLock(RLock lock, MyLock prop) throws InterruptedException;


}

最后,在我们的切入点打上注解(可以指定失败策略,锁类型)

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值