在现代微服务架构中,分布式锁是一种常用的技术手段,用于确保在分布式系统中,同一时间只有一个服务实例能够执行某个特定的操作。
这对于防止并发问题、保证数据一致性至关重要。在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表达式计算得出的,这里使用了方法的参数orderid
。leasetime
和waittime
分别设置了锁的超时时间和等待时间。
当多个服务实例尝试同时创建同一个订单时,由于分布式锁的存在,只有一个实例能够成功执行createorder
方法,其他实例将会在等待一段时间后失败,或者执行lock
注解中定义的失败逻辑。
这种使用注解的方式,使得分布式锁的集成变得非常简单和直观。开发者不需要关心锁的具体实现细节,只需要在需要加锁的方法上添加lock
注解,并设置相应的参数即可。
通过这三个组件,我们可以在spring boot应用中非常优雅地实现分布式锁。lock
注解提供了一种声明式的方式,让开发者可以轻松地为方法添加分布式锁。lockaspect
切面确保了锁的逻辑在方法执行前后被正确地处理。而redislockutils
工具类则负责与redis交互,确保锁的原子性和一致性。
在实现这些组件时,我们还需要注意一些细节,比如如何处理锁的键值解析、如何处理锁获取失败的情况、如何确保锁的释放等。
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。
发表评论