SpringbootRedis分布式锁

SpringbootRedis分布式锁

SpringbootRedis分布式锁

1. 添加相关依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

2. 创建分布式锁注解

import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;

/**
 * @author devcxl
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DistributedLock {

    /**
     * 是否只针对请求
     *
     * @return
     */
    boolean api();

    /**
     * 锁key前缀
     */
    String keyPrefix() default "lock_key";

    /**
     * 锁value
     */
    String valuePrefix() default "lock_value";

    /**
     * 锁超时时间
     */
    long timeout() default 10;

    /**
     * 超时时间单位
     */
    TimeUnit timeUnit() default TimeUnit.SECONDS;
}

3. aop实现


import cn.devcxl.common.annotation.DistributedLock;
import cn.devcxl.common.constant.SysConstant;
import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;

/**
 * @author devcxl
 */
@Slf4j
@Aspect
@Component
public class DistributedLockAspect {
    /**
     * StringRedisTemplate
     */
    @Resource
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 定义切点
     */
    @Pointcut("@annotation(cn.devcxl.common.annotation.DistributedLock)")
    public void apiPointCut() {

    }

    /**
     * 切点
     * 加分布式锁
     */
    @Around(value = "apiPointCut() && @annotation(distributedLock)")
    public Object apiAround(ProceedingJoinPoint pjp, DistributedLock distributedLock) throws Exception {

        boolean api = distributedLock.api();
        String key = null;
        String value = null;

        if (api) {
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
            //访问者ip
            String clientIP = ServletUtil.getClientIP(request);
            //请求方式
            String method = request.getMethod();
            //请求路径
            String uri = request.getRequestURI();
            //获取入参
            Object[] args = pjp.getArgs();
            // 类名方法名作为分布式锁的key
            key = distributedLock.keyPrefix() + "_" + clientIP + "_" + method + "_" + uri + "_" + JSONUtil.toJsonStr(args);
            value = distributedLock.valuePrefix();
        } else {
            Signature signature = pjp.getSignature();
            String className = signature.getDeclaringTypeName();
            String methodName = signature.getName();
            // 类名方法名作为分布式锁的key
            key = distributedLock.keyPrefix() + "_" + className + "." + methodName;
            value = distributedLock.valuePrefix() + "_" + SysConstant.HOSTNAME;
        }

        // 获取锁
        Boolean status = stringRedisTemplate.opsForValue().setIfAbsent(key, value, distributedLock.timeout(), distributedLock.timeUnit());
        if (!ObjectUtils.isEmpty(status) && status.equals(Boolean.TRUE)) {
            try {
                Object proceed = pjp.proceed();
                // 处理完成后解锁
                stringRedisTemplate.delete(key);
                return proceed;
            } catch (Throwable throwable) {
                log.error("key failure!! key{} error:{}", key, throwable.getMessage());
            }
        }
        log.warn("getLock failure!! key{}", key);
        throw new Exception("请勿重复操作!!");
    }

}

4. 使用示例

@PostMapping("/register")
@DistributedLock(api = true, keyPrefix = "register_", timeout = 3)
public Resp<String> register(@RequestBody @Validated RegisterParam param) {
    registerServiceImpl.register(param);
    return Resp.ok();
}

or

@Slf4j
@Component
public class BatchHandlerJob{
    
    @Scheduled(cron = "0 0 18 * * ?")
    @DistributedLock(api = false, keyPrefix = "handler_job_xxx", timeout = 70)
    public void handler(){
        // handler work
    }
    // ...
}