当前位置: 代码网 > it编程>编程语言>Java > 分布式锁在Spring Boot应用中的实现过程

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

2025年08月02日 Java 我要评论
在现代微服务架构中,分布式锁是一种常用的技术手段,用于确保在分布式系统中,同一时间只有一个服务实例能够执行某个特定的操作。这对于防止并发问题、保证数据一致性至关重要。在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注解提供了一种声明式的方式,让开发者可以轻松地为方法添加分布式锁。lockaspect切面确保了锁的逻辑在方法执行前后被正确地处理。而redislockutils工具类则负责与redis交互,确保锁的原子性和一致性。

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

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com