当前位置: 首页 > news >正文

分布式锁在Spring Boot应用中的优雅实现

在现代微服务架构中,分布式锁是一种常用的技术手段,用于确保在分布式系统中,同一时间只有一个服务实例能够执行某个特定的操作。这对于防止并发问题、保证数据一致性至关重要。在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;/*** 超时时间单位(默认秒)** @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 Object 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 未获取到锁执行*/public 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 != leaseTimeMs && 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 redissonClient;}}

下面是一个使用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注解来实现分布式锁。如果您有任何疑问或需要进一步的帮助,请随时联系我们。

通过这三个组件,我们可以在Spring Boot应用中非常优雅地实现分布式锁。Lock注解提供了一种声明式的方式,让开发者可以轻松地为方法添加分布式锁。LockAspect切面确保了锁的逻辑在方法执行前后被正确地处理。而RedisLockUtils工具类则负责与Redis交互,确保锁的原子性和一致性。
在实现这些组件时,我们还需要注意一些细节,比如如何处理锁的键值解析、如何处理锁获取失败的情况、如何确保锁的释放等。

如果您对如何在Spring Boot应用中实现分布式锁感兴趣,请继续关注我们的微信公众号《码趣坊》,我们将为您提供更多精彩的内容和实战案例。让我们一起探索Spring Boot的无限可能!

http://www.lryc.cn/news/382878.html

相关文章:

  • 常用框架-Spring Boot
  • AttributeError: module ‘cv2‘ has no attribute ‘face‘
  • 不管你是普本还是双一流,建议你一定要尝试一下学习GIS开发
  • OurBMC大咖说丨第5期:BMC开发中的非标准化问题探讨
  • 空调制冷剂泄漏引发健康隐患,冷媒传感器实时监测至关重要
  • 开源TinyFSM状态机适用于嵌入式工业平台吗?
  • EE trade:利弗莫尔三步建仓法
  • Java中Callable的应用
  • 测试卡无法仪表注册问题分析
  • 【扩散模型(一)】Stable Diffusion中的重建分支(reconstruction branch)和条件分支(condition branch)
  • WPF——Binding
  • linux与windows环境下qt程序打包教程
  • LeetCode21-合并两个有序链表
  • 嵌入式学习——数据结构(双向无头无环链表)——day47
  • MYSQL 将某个字段赋值当前时间
  • ModelSim® SE Command Reference Manual : find命令的用法
  • PHPMailer发送的中文内容乱码如何解决
  • .npmrc配置文件
  • 无线桥接两个路由器 实现全屋网络全覆盖
  • qt开发-14_QListwidget 仿qq好友列表制作
  • 基于hutool的sm2非对称加密使用示例
  • 深入Scala的变量声明与类型推断:语法糖下的智能推导
  • ATA-4052C高压功率放大器在新能源汽车安全测试中的应用
  • liunx打开谷歌报错
  • ICMAN液位检测大盘点
  • 2024软件设计师笔记之考点版(一考就过):1-10
  • Java中的性能优化技巧
  • 一位Java软件开发工程师繁忙的一天
  • 容易上手的AI图片生成软件有哪些值得推荐?
  • 如何高效运营交友APP