分布式锁在Spring Boot应用中的实现过程

2025-08-02 20:50

本文主要是介绍分布式锁在Spring Boot应用中的实现过程,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

《分布式锁在SpringBoot应用中的实现过程》文章介绍在SpringBoot中通过自定义Lock注解、LockAspect切面和RedisLockUtils工具类实现分布式锁,确保多实例并发操作...

在现代微服务架构中,分布式锁是一种常用的技术手段,用于确保在分布式系统中,同一时间只有一个服务实例能够执行某个特定的操作。

这对于防止并发问题、保证数据一致性至关重要。在Spring Boot应用中,我们可以通过自定义注解和切面的方式,来实现一个既简洁又强大的分布式锁机制。

Lock注解

首先,我们定义一个Lock注解,用于标记需要加锁的方法。这个注解包含了锁的键值、超时时间和等待时间等信息。

import Java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.concurrent.TimeUnit;
 
/**
 * @author tangzx
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Lock {
 
    /**
     * 锁的键值
     *
     * @return 锁的键值
     */
    String value() default "";
 
    /**
     * 锁的键值
     *
     * @return 锁的键值
     */
    String key() default "";
 
    /**
     * 超时时间
     *
     * @return 超时时间
     */
    long leaseTime() default 30L;
 
    /**
     * 等待时间
     *
     * @return 等待时间
     */
    long waitTime() default 0L;
 
    /**
     * 超时时间单http://www.chinasem.cn位(默认秒)
     *
     * @return 超时时间单位
     */
    TimeUnit leaseTimeTimeUnit() default TimeUnit.SECONDS;
 
    /**
     * 等待时间单位(默认秒)
     *
     * @return 等待时间单位
     */
    TimeUnit waitTimeTimeUnit() default TimeUnit.SECONDS;
 
}

LockAspect切面

接下来,我们创建一个LockAspect切面类,用于处理Lock注解。

这个切面会在方法执行前尝试获取锁,如果获取成功,则执行方法体;如果获取失败,则执行相应的失败逻辑。

import com.lock.core.exception.AppException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaLuationContext;
import org.springframework.stereotype.Component;
 
import java.util.concurrent.atomic.AtomicReference;
 
/**
 * @author tangzx
 */
@Slf4j
@Aspect
@Component
public class LockAspect {
 
    @Around("@annotation(lock)")
    public Objecjst around(ProceedingJoinPoint joinPoint, Lock lock) throws Throwable {
        String value = lock.value();
        String key = lock.key();
        long leaseTimeMs = lock.leaseTimeTimeUnit().toMillis(lock.leaseTime());
        long waitTimeMs = lock.waitTimeTimeUnit().toMillis(lock.waitTime());
        String lockKey = resolveLockKey(value, key, joinPoint);
        AtomicReference<Object> result = new AtomicReference<>(null);
        AtomicReference<Throwable> throwable = new AtomicReference<>(null);
        RedisUtils.LockOps.execute(lockKey, leaseTimeMs, waitTimeMs, () -> {
            try {
                result.set(joinPoint.proceed());
            } catch (Throwable t) {
                throwable.set(t);
            }
        }, () -> {
            AppLogger.append("未获取到Lock锁[{}]", lockKey);
            throw new AppException("正在处理中,请稍后再试");
        });
        if (null != throwable.get()) {
            throw throwable.get();
        }
        return result.get();
    }
 
    public String resolveLockKey(String lockName, String key, ProceedingJoinPoint joinPoint) {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        String[] parameterNames = methodSignature.getParameterNames();
        Object[] args = joinPoint.getArgs();
 
        ExpressionParser parser = new SpelExpressionParser();
        Expression expression = parser.parseExpression(key);
        StandardEvaluationContext context = new StandardEvaluationContext();
        for (int i = 0; i < args.length; i++) {
            context.setVariable(parameterNames[i], args[i]);
        }
        String value = expression.getValue(context, String.class);
        if (StringUtils.isNotBlank(value)) {
            return lockName + ":" + value;
        }
        if (log.isWarnEnabled()) {
            log.warn("lockName={},根据规则[key={}],未在参数中获取到对应的值,默认使用lockName作为key", lockName, key);
        }
        return lockName;
    }
 
}

RedisLockUtils工具类

最后,我们实现一个RedisLockUtils工具类,用于与Redis交互,实现锁的获取和释放。

这个类会使用Redisson客户端来简化分布式锁的操作。

import com.redis.utils.ServiceLocator;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
 
import java.util.Optional;
import java.util.concurrent.TimeUnit;
 
/**
 * @author :Tzx
 * @date :Created in 2021/8/2 18:09
 * @description: redis锁
 * @version: 1.0
 */
@Slf4j
public class RedisLockUtils {
 
    private final static String REDIS_LOCK_HANDLER_PREFIX = RedisLockUtils.class.getSimpleName().toLowerCase() + ":";
 
    private static volatile RedissonClient redissonClient;
 
    /**
     * 获取分布式锁执行
     *
     * @param redisKey      redisKey
     * @param codeToExecute 获取锁执行
     */
    public static void execute(String redisKey, Runnable codeToExecute) {
        execute(redisKey, null, null, codeToExecute, null);
    }
 
    /**
     * 获取分布式锁执行
     *
     * @param redisKey              redisKey
     * @param codeToExecute         获取锁执行
     * @param codeIfLockNotAcquired 未获取到锁执行
     */
    puphpblic static void execute(String redisKey, Runnable codeToExecute, Runnable codeIfLockNotAcquired) {
        execute(redisKey, null, null, codeToExecute, codeIfLockNotAcquired);
    }
 
    /**
     * 获取分布式锁执行
     *
     * @param key                   redisKey
     * @param leaseTimeMs           锁超时时间
     * @param waitTimeMs            获取锁等待时间
     * @param codeToExecute         获取锁执行
     * @param codeIfLockNotAcquired 未获取到锁执行
     */
    public static void execute(String key, Long leaseTimeMs, Long waitTimeMs, Runnable codeToExecute, Runnable codeIfLockNotAcquired) {
        waitTimeMs = Optional.ofNullable(waitTimeMs).orElse(0L);
 
        String lockKey = REDIS_LOCK_HANDLER_PREFIX + key;
        RLock lock = getRedissonClient().getLock(lockKey);
        boolean tryLock = false;
        try {
            if (null != leandroidaseTimeMs && leaseTimeMs > 0L) {
                tryLock = lock.tryLock(waitTimeMs, leaseTimeMs, TimeUnit.MILLISECONDS);
            } else {
                tryLock = lock.tryLock(waitTimeMs, TimeUnit.MILLISECONDS);
            }
        } catch (InterruptedException interruptedException) {
            log.warn("获取锁异常", interruptedException);
            Thread.currentThread().interrupt();
        }
        if (tryLock) {
            try {
                codeToExecute.run();
                return;
            } finally {
                if (lock.isHeldByCurrentThread()) {
                    lock.unlock();
                }
            }
        }
        if (log.isDebugEnabled()) {
            log.debug("未获取到锁[{}]", key);
        }
        Optional.ofNullable(codeIfLockNotAcquired).ifPresent(Runnable::run);
    }
 
    private static RedissonClient getRedissonClient() {
        if (null == redissonClient) {
            synchronized (RedisLockUtils.class) {
                if (null == redissonClient) {
                    redissonClient = ServiceLocator.getService(RedissonClient.class);
                }
            }
        }
        return redissonClientphp;
    }
 
}

下面是一个使用Lock注解的示例,展示了如何在Spring Boot应用中实现分布式锁。

假设我们有一个OrderService服务,其中包含一个方法createOrder,这个方法需要保证在多服务实例中同时只有一个能够被执行,以防止创建重复的订单。

import org.springframework.stereotype.Service;
@Service
public class OrderService {
    @Lock(value = "order", key = "#orderId", leaseTime = 10, waitTime = 5)
    public void createOrder(String orderId) {
        // 业务逻辑,比如创建订单、保存订单等
        System.out.println("Creating order: " + orderId);
    }
}

在这个示例中,createOrder方法使用了Lock注解。当这个方法被调用时,LockAspect切面会拦截这个调用,并尝试获取一个分布式锁。锁的键值是由key属性的SpEL表达式计算得出的,这里使用了方法的参数orderIdleaseTimewaitTime分别设置了锁的超时时间和等待时间。

当多个服务实例尝试同时创建同一个订单时,由于分布式锁的存在,只有一个实例能够成功执行createOrder方法,其他实例将会在等待一段时间后失败,或者执行Lock注解中定义的失败逻辑。

这种使用注解的方式,使得分布式锁的集成变得非常简单和直观。开发者不需要关心锁的具体实现细节,只需要在需要加锁的方法上添加Lock注解,并设置相应的参数即可。

通过这三个组件,我们可以在Spring Boot应用中非常优雅地实现分布式锁。Lock注解提供了一种声明式的方式,让开发者可以轻松地为方法添加分布式锁。LockAspect切面确保了锁的逻辑在方法执行前后被正确地处理。而RedisLockUtils工具类则负责与Redis交互,确保锁的原子性和一致性。

在实现这些组件时,我们还需要注意一些细节,比如如何处理锁的键值解析、如何处理锁获取失败的情况、如何确保锁的释放等。

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持China编程(www.chinasem.cn)。

这篇关于分布式锁在Spring Boot应用中的实现过程的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1155562

相关文章

Linux线程同步/互斥过程详解

《Linux线程同步/互斥过程详解》文章讲解多线程并发访问导致竞态条件,需通过互斥锁、原子操作和条件变量实现线程安全与同步,分析死锁条件及避免方法,并介绍RAII封装技术提升资源管理效率... 目录01. 资源共享问题1.1 多线程并发访问1.2 临界区与临界资源1.3 锁的引入02. 多线程案例2.1 为

批量导入txt数据到的redis过程

《批量导入txt数据到的redis过程》用户通过将Redis命令逐行写入txt文件,利用管道模式运行客户端,成功执行批量删除以Product*匹配的Key操作,提高了数据清理效率... 目录批量导入txt数据到Redisjs把redis命令按一条 一行写到txt中管道命令运行redis客户端成功了批量删除k

Java使用Thumbnailator库实现图片处理与压缩功能

《Java使用Thumbnailator库实现图片处理与压缩功能》Thumbnailator是高性能Java图像处理库,支持缩放、旋转、水印添加、裁剪及格式转换,提供易用API和性能优化,适合Web应... 目录1. 图片处理库Thumbnailator介绍2. 基本和指定大小图片缩放功能2.1 图片缩放的

Win10安装Maven与环境变量配置过程

《Win10安装Maven与环境变量配置过程》本文介绍Maven的安装与配置方法,涵盖下载、环境变量设置、本地仓库及镜像配置,指导如何在IDEA中正确配置Maven,适用于Java及其他语言项目的构建... 目录Maven 是什么?一、下载二、安装三、配置环境四、验证测试五、配置本地仓库六、配置国内镜像地址

Spring Boot集成/输出/日志级别控制/持久化开发实践

《SpringBoot集成/输出/日志级别控制/持久化开发实践》SpringBoot默认集成Logback,支持灵活日志级别配置(INFO/DEBUG等),输出包含时间戳、级别、类名等信息,并可通过... 目录一、日志概述1.1、Spring Boot日志简介1.2、日志框架与默认配置1.3、日志的核心作用

Python使用Tenacity一行代码实现自动重试详解

《Python使用Tenacity一行代码实现自动重试详解》tenacity是一个专为Python设计的通用重试库,它的核心理念就是用简单、清晰的方式,为任何可能失败的操作添加重试能力,下面我们就来看... 目录一切始于一个简单的 API 调用Tenacity 入门:一行代码实现优雅重试精细控制:让重试按我

破茧 JDBC:MyBatis 在 Spring Boot 中的轻量实践指南

《破茧JDBC:MyBatis在SpringBoot中的轻量实践指南》MyBatis是持久层框架,简化JDBC开发,通过接口+XML/注解实现数据访问,动态代理生成实现类,支持增删改查及参数... 目录一、什么是 MyBATis二、 MyBatis 入门2.1、创建项目2.2、配置数据库连接字符串2.3、入

Springboot项目启动失败提示找不到dao类的解决

《Springboot项目启动失败提示找不到dao类的解决》SpringBoot启动失败,因ProductServiceImpl未正确注入ProductDao,原因:Dao未注册为Bean,解决:在启... 目录错误描述原因解决方法总结***************************APPLICA编

深度解析Spring Security 中的 SecurityFilterChain核心功能

《深度解析SpringSecurity中的SecurityFilterChain核心功能》SecurityFilterChain通过组件化配置、类型安全路径匹配、多链协同三大特性,重构了Spri... 目录Spring Security 中的SecurityFilterChain深度解析一、Security

Redis客户端连接机制的实现方案

《Redis客户端连接机制的实现方案》本文主要介绍了Redis客户端连接机制的实现方案,包括事件驱动模型、非阻塞I/O处理、连接池应用及配置优化,具有一定的参考价值,感兴趣的可以了解一下... 目录1. Redis连接模型概述2. 连接建立过程详解2.1 连php接初始化流程2.2 关键配置参数3. 最大连