这样实现Redis分布式锁最简单

Posted javatiange

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了这样实现Redis分布式锁最简单相关的知识,希望对你有一定的参考价值。

分布式锁是实际应用中最常见的场景之一,Redisson虽然支持了分布式锁实现,但是使用上仍然不够方便。本文介绍了一种优雅的分布式锁的静态方法封装和注解式封装实现。

静态方法封装

为了方便在业务代码中使用分布式锁,可以通过静态方法直接调用来实现。结合函数式编程,完整接口定义如下:

/**
 * 分布式锁定执行,返回执行结果
 *
 * @param key         分布式锁标识
 * @param leaseTimeMs 自动释放时间
 * @param executable    可执行单元
 * @return 执行结果
 */
public static Object lockExecute(String key, Long leaseTimeMs, Executable executable);

/**
 * 分布式锁定执行,不返回执行结果
 *
 * @param key         分布式锁标识
 * @param leaseTimeMs 自动释放时间
 * @param runnable    可执行单元
 */
public static void lockRun(String key, Long leaseTimeMs, Runnable runnable); 

lockExecute()支持返回分布式锁加锁执行结果,lockRun()则无返回结果

完整代码如下:

/**
 * @author 陈添明
 */
@UtilityClass
@Slf4j
public class RLockUtils {


    /**
     * 分布式锁定执行,返回执行结果
     *
     * @param key         分布式锁标识
     * @param leaseTimeMs 自动释放时间
     * @param executable    可执行单元
     * @return 执行结果
     */
    @SneakyThrows
    public static Object lockExecute(String key, Long leaseTimeMs, Executable executable) {
        RLock lock = doLock(key, leaseTimeMs);
        try {
            return executable.execute();
        } finally {
            doUnlock(key, leaseTimeMs, lock);
        }
    }

    /**
     * 分布式锁定执行,不返回执行结果
     *
     * @param key         分布式锁标识
     * @param leaseTimeMs 自动释放时间
     * @param runnable    可执行单元
     */
    public static void lockRun(String key, Long leaseTimeMs, Runnable runnable) {
        RLock lock = doLock(key, leaseTimeMs);
        try {
            runnable.run();
        } finally {
            doUnlock(key, leaseTimeMs, lock);
        }
    }

    private static void doUnlock(String key, Long leaseTimeMs, RLock lock) {
        if (lock != null) {
            try {
                lock.unlock();
            } catch (Exception e) {
                log.error("分布式锁释放失败!不影响业务执行!key={}, leaseTimeMs={}", key, leaseTimeMs, e);
            }
        }
    }


    private static RLock doLock(String key, Long leaseTimeMs) {
        RLock lock = null;
        try {
            RedissonClient redissonClient = SpringUtil.getBean(RedissonClient.class);
            lock = redissonClient.getLock(key);
            lock.lock(leaseTimeMs, TimeUnit.MILLISECONDS);
        } catch (Exception e) {
            log.error("分布式锁加锁失败!不影响业务正常执行!key={}, leaseTimeMs={}", key, leaseTimeMs, e);
        }
        return lock;
    }

} 

注解式封装

注解式编程是日常工作中更常用的一种方式,下面重点介绍注解式的实现方案。

如果你觉得自己学习效率低,缺乏正确的指导,可以加入资源丰富,学习氛围浓厚的技术圈一起学习交流吧!
[Java架构群]
群内有许多来自一线的技术大牛,也有在小厂或外包公司奋斗的码农,我们致力打造一个平等,高质量的JAVA交流圈子,不一定能短期就让每个人的技术突飞猛进,但从长远来说,眼光,格局,长远发展的方向才是最重要的。

定义注解@RLock

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RLock {

    /**
     * 分布式锁标识,支持以下格式:
     * abc
     * #{param}
     * #{user.id}
     * abc_#{param}_#{user.id}
     * @return key
     */
    String key();

    /**
     * 自动释放锁时间,单位毫秒,默认5_000
     * @return leaseTime
     */
    long leaseTimeMs() default 5_000;
} 

定义切面实现

@Aspect
@Component
@Slf4j
@Order(0)
public class RLockAspect {

    ExpressionParser parser = new SpelExpressionParser();

    private final PropertyPlaceholderHelper defaultHelper = new PropertyPlaceholderHelper("#{", "}");

    private final ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();

    @Around("@annotation(rLock)")
    public Object lock(ProceedingJoinPoint joinPoint, RLock rLock) throws Throwable {
        String key;
        try {
            key = generateKey(joinPoint, rLock);
        } catch (Exception e) {
            log.error("生成分布式锁标识失败,执行单元将会直接执行!");
            return joinPoint.proceed();
        }
        return RLockUtils.lockExecute(key, rLock.leaseTimeMs(), joinPoint::proceed);
    }


    /**
     * 生成分布式锁标识
     *
     * @param joinPoint 连接点
     * @param rLock     锁注解
     * @return 分布式锁标识
     */
    private String generateKey(ProceedingJoinPoint joinPoint, RLock rLock) {
        String key = rLock.key();
        Assert.hasText(key, "key不能配置空白字符!");
        EvaluationContext context = new StandardEvaluationContext();
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        Object[] args = joinPoint.getArgs();
        String[] parameterNames = parameterNameDiscoverer.getParameterNames(method);
        if (parameterNames != null && parameterNames.length > 0) {
            for (int i = 0; i < parameterNames.length; i++) {
                context.setVariable(parameterNames[i], args[i]);
            }
        }
        return defaultHelper.replacePlaceholders(key, placeholderName -> {
            Expression expression = parser.parseExpression("#" + placeholderName);
            return String.valueOf(expression.getValue(context));
        });
    }

} 

切面将会自动拦截所有标注了@RLock的方法,并织入分布式锁的代码。重点有3个Spring相关类需要关注一下:

  • ParameterNameDiscoverer:用于获取方法参数名称。
  • PropertyPlaceholderHelper: 用于处理占位符解析。
  • ExpressionParser: 用于解析SpringEL表达式。

使用

在任意方法上,标注@RLock即可自动织入分布式锁:

@RLock(key = "abc_#{id}_#{person.name}_#{person.age}")
@SneakyThrows
public void testRLock(Long id, Person person) {
    // 做业务处理
    Thread.sleep(5000L);
} 

最后

给大家分享一篇一线开发大牛整理的java高并发核心编程神仙文档,里面主要包含的知识点有:多线程、线程池、内置锁、JMM、CAS、JUC、高并发设计模式、Java异步回调、CompletableFuture类等。

文档地址:一篇神文就把java多线程,锁,JMM,JUC和高并发设计模式讲明白了

码字不易,如果觉得本篇文章对你有用的话,请给我一键三连!关注作者,后续会有更多的干货分享,请持续关注!

以上是关于这样实现Redis分布式锁最简单的主要内容,如果未能解决你的问题,请参考以下文章

Redlock:Redis分布式锁最牛逼的实现

分布式解决选择

基于Redis的分布式锁的简单实现

基于redis的分布式锁的分析与实践

不用找了,基于 Redis 的分布式锁实战来了!

大家所推崇的 Redis 分布式锁,真的可以万无一失吗?