Spring注解实现分布式锁

1、核心代码

我们日常是如何利用Redisson实现分布式锁的呢?
是否大多数情况都遵循了某种固定的“模式”或“模板”?既然这是一个常见的模式,那么我们为何不将其抽象出来,以便更加便捷和高效地使用呢?

// 尝试加锁,最多等待100秒,上锁以后10秒自动解锁
boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);
if (res) {
   try {
     ...业务代码
   } finally {
       lock.unlock();
   }
}

2、抽出分布式锁工具类

提炼出一个LockService方法,将分布式锁的通用模板封装于其中。这样一来,在需要加锁的地方,我们仅需指定锁的key,并通过supplier函数传入需在锁内执行的代码块。这种设计不仅显著提升了代码的复用性,还使得加锁逻辑更加清晰、易于管理。如此,我们可以更加高效、便捷地处理分布式锁的需求,从而优化整个系统的并发控制。

@Service
@Slf4j
public class LockService {

    @Autowired
    private RedissonClient redissonClient;

    public <T> T executeWithLock(String key, int waitTime, TimeUnit unit, SupplierThrow<T> supplier) throws Throwable {
        RLock lock = redissonClient.getLock(key);
        boolean lockSuccess = lock.tryLock(waitTime, unit);
        if (!lockSuccess) {
           //抛出自定义异常
           throw new Exception();
        }
        try {
            return supplier.get();//执行锁内的代码逻辑
        } finally {
            lock.unlock();
        }
    }
}

使用起来就方便了

lockService.executeWithLock(key, 10, TimeUnit.SECONDS, ()->{
            //执行业务逻辑
            。。。。。
            return null;
});

如果我们不需要排队等锁,甚至还能重载方法减少两个参数。

lockService.executeWithLock(key, ()->{
            //执行业务逻辑
            。。。。。
            return null;
});

3、注解实现分布式锁

锁工具类已全面涵盖了分布式锁的核心功能实现。我们引入注解的初衷,正是为了简化这一核心功能的使用方式,让开发者能够更轻松地运用分布式锁。如同诸多底层的SDK,它们常通过接口调用的方式来实现核心功能,而注解的加入则进一步增强了这些功能的便捷性。

现在,让我们深入探讨一个常见的场景:在分布式系统中,我们常需在controller层或service层的特定方法上应用分布式锁。多数情况下,加锁所需的key并非固定不变,而是需要根据方法的输入参数进行动态组合。那么,我们是否有可能利用EL表达式来实现key的动态组合呢?

答案无疑是肯定的。通过EL表达式,我们能够灵活地根据方法的输入参数来动态生成锁的key。这不仅增强了锁的灵活性,还使得锁的使用更加贴近实际需求。因此,结合注解与EL表达式,我们可以构建一个既强大又易用的分布式锁解决方案,为分布式系统的并发控制提供坚实支撑。

1.创建注解@RedissonLock
/**
 * 分布式锁注解
 */
@Retention(RetentionPolicy.RUNTIME)//运行时生效
@Target(ElementType.METHOD)//作用在方法上
public @interface RedissonLock {
    /**
     * key的前缀,默认取方法全限定名,除非我们在不同方法上对同一个资源做分布式锁,就自己指定
     *
     * @return key的前缀
     */
    String prefixKey() default "";

    /**
     * springEl 表达式
     *
     * @return 表达式
     */
    String key();

    /**
     * 等待锁的时间,默认-1,不等待直接失败,redisson默认也是-1
     *
     * @return 单位秒
     */
    int waitTime() default -1;

    /**
     * 等待锁的时间单位,默认毫秒
     *
     * @return 单位
     */
    TimeUnit unit() default TimeUnit.MILLISECONDS;

}

约定大于配置的思想,我们的大多数参数都是可以默认的。

很多时候我们的锁都是针对方法的,要锁同一处地方,调用同一个方法就好了,这样前缀可以直接默认根据类+方法名来实现,同样针对特例我们也提供了自己指定前缀的入口。

2.实现切面RedissonLockAspect
@Slf4j
@Aspect
@Component
@Order(0)//确保比事务注解先执行,分布式锁在事务外
public class RedissonLockAspect {
    @Autowired
    private LockService lockService;

    @Around("@annotation(common.annotation.RedissonLock)")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        RedissonLock redissonLock = method.getAnnotation(RedissonLock.class);
        String prefix = StrUtil.isBlank(redissonLock.prefixKey()) ? SpElUtils.getMethodKey(method) : redissonLock.prefixKey();//默认方法限定名+注解排名(可能多个)
        String key = SpElUtils.parseSpEl(method, joinPoint.getArgs(), redissonLock.key());
        return lockService.executeWithLock(prefix + ":" + key, redissonLock.waitTime(), redissonLock.unit(), joinPoint::proceed);
    }
}

切面其实很简单,构建key=前缀+el表达式,然后把参数都传进去,调用我们核心功能的工具类LockService
SpElUtils代码请查看https://blog.csdn.net/qq_40694890/article/details/137011707

3.使用
    @RedissonLock(key = "#uid")
    public void setMsgMark(Long uid){
        //业务代码
	}

使用起来就非常方便了,对uid加锁,只要一个注解搞定。如果需要等待,再加个等待时间就行。

  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值