本文主要是介绍分布式锁在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表达式计算得出的,这里使用了方法的参数orderId
。leaseTime
和waitTime
分别设置了锁的超时时间和等待时间。
当多个服务实例尝试同时创建同一个订单时,由于分布式锁的存在,只有一个实例能够成功执行createOrder
方法,其他实例将会在等待一段时间后失败,或者执行Lock
注解中定义的失败逻辑。
这种使用注解的方式,使得分布式锁的集成变得非常简单和直观。开发者不需要关心锁的具体实现细节,只需要在需要加锁的方法上添加Lock
注解,并设置相应的参数即可。
通过这三个组件,我们可以在Spring Boot应用中非常优雅地实现分布式锁。Lock
注解提供了一种声明式的方式,让开发者可以轻松地为方法添加分布式锁。LockAspect
切面确保了锁的逻辑在方法执行前后被正确地处理。而RedisLockUtils
工具类则负责与Redis交互,确保锁的原子性和一致性。
在实现这些组件时,我们还需要注意一些细节,比如如何处理锁的键值解析、如何处理锁获取失败的情况、如何确保锁的释放等。
总结
这篇关于分布式锁在Spring Boot应用中的实现过程的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!