
一,背景
在日常项目中经常会涉及到一些秒杀的情况,需要保证不超卖,库存等数据正确,这时候就需要引入分布式锁了。下面介绍基于redisson的分布式锁实现。
二,实现
1,引入依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
<dependency> <groupId>org.redisson</groupId> <artifactId>redisson-spring-boot-starter</artifactId> <exclusions> <exclusion> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> </exclusion> </exclusions> </dependency>
2,添加注解
            
            
              java
              
              
            
          
          import com.xxx.common.enumeration.LockFailStrategyEnum;
import com.xxx.common.enumeration.LockTypeEnum;
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;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DistributedLock {
    /**
     * 用来区分场景,可以为空
     */
    String prefix() default "";
    /**
     * 锁的key,支持SpEL表达式
     */
    String key();
    /**
     * 锁的类型
     */
    LockTypeEnum lockType() default LockTypeEnum.REENTRANT;
    /**
     * 获取锁的等待时间
     * 等待获取锁请求时间,这个值为负数的话表示一致等待
     */
    long waitTime() default -1;
    /**
     * 持有锁的时间(leaseTime)
     * 自动释放锁的时间,这个值为负数表示需要手动释放锁
     */
    long leaseTime() default -1;
    /**
     * 时间单位
     */
    TimeUnit timeUnit() default TimeUnit.SECONDS;
    /**
     * 获取锁失败时的处理策略
     */
    LockFailStrategyEnum failStrategy() default LockFailStrategyEnum.EXCEPTION;
    /**
     * 获取锁失败时抛出的异常信息
     */
    String failMessage() default "获取分布式锁失败";
}
        3, 实现注解
            
            
              java
              
              
            
          
          import lombok.RequiredArgsConstructor;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.redisson.config.Config;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
@Configuration
@EnableConfigurationProperties(RedissonProperties.class)
@RequiredArgsConstructor
public class RedissonConfig {
    private final RedissonProperties redissonProperties;
    @Bean
    public RedissonClient redissonClient() {
        Config config = new Config();
        // 单节点模式
        config.useSingleServer()
            .setIdleConnectionTimeout(redissonProperties.getSingleServerConfig().getIdleConnectionTimeout())
            .setConnectTimeout(redissonProperties.getSingleServerConfig().getConnectTimeout())
            .setTimeout(redissonProperties.getSingleServerConfig().getTimeout())
            .setRetryAttempts(redissonProperties.getSingleServerConfig().getRetryAttempts())
            .setRetryInterval(redissonProperties.getSingleServerConfig().getRetryInterval())
            .setPassword(redissonProperties.getSingleServerConfig().getPassword())
            .setAddress(redissonProperties.getSingleServerConfig().getAddress());
        return Redisson.create(config);
    }
}
        
            
            
              java
              
              
            
          
          import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Data
@Configuration
@ConfigurationProperties(prefix = "spring.redis.redisson.config")
public class RedissonProperties {
    private RedissonSingleServerConfig singleServerConfig;
}
@Data
class RedissonSingleServerConfig {
    private int idleConnectionTimeout;
    private int connectTimeout;
    private int timeout;
    private int retryAttempts;
    private int retryInterval;
    private String password;
    private String address;
}
        
            
            
              java
              
              
            
          
          import com.xxx.common.enumeration.LockTypeEnum;
import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Component;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
@Component
public class RedissonLockManager {
    private final RedissonClient redissonClient;
    private final Map<String, RLock> lockCache = new ConcurrentHashMap<>();
    public RedissonLockManager(RedissonClient redissonClient) {
        this.redissonClient = redissonClient;
    }
    /**
     * 获取指定类型的锁
     *
     * @param key 锁的key
     * @param lockType 锁类型
     * @return 对应的锁对象
     */
    public Lock getLock(String key, LockTypeEnum lockType) {
        switch (lockType) {
            case REENTRANT:
                return redissonClient.getLock(key);
            case FAIR:
                return redissonClient.getFairLock(key);
            case READ:
                RReadWriteLock readWriteLock = redissonClient.getReadWriteLock(key);
                return readWriteLock.readLock();
            case WRITE:
                RReadWriteLock rwLock = redissonClient.getReadWriteLock(key);
                return rwLock.writeLock();
            default:
                return redissonClient.getLock(key);
        }
    }
    /**
     * 尝试获取锁
     *
     * @param lock 锁对象
     * @param waitTime 等待时间
     * @param leaseTime 持有锁时间
     * @param timeUnit 时间单位
     * @return 是否获取成功
     * @throws InterruptedException 中断异常
     */
    public boolean tryLock(Lock lock, long waitTime, long leaseTime, TimeUnit timeUnit) throws InterruptedException {
        if (lock instanceof RLock) {
            RLock rLock = (RLock) lock;
            if (waitTime > 0) {
                if (leaseTime > 0) {
                    return rLock.tryLock(waitTime, leaseTime, timeUnit);
                } else {
                    return rLock.tryLock(waitTime, timeUnit);
                }
            } else {
                if (leaseTime > 0) {
                    rLock.lock(leaseTime, timeUnit);
                    return true;
                } else {
                    rLock.lock();
                    return true;
                }
            }
        } else {
            // 对于非RLock类型,使用简单实现
            if (waitTime > 0) {
                return lock.tryLock(waitTime, timeUnit);
            } else {
                lock.lock();
                return true;
            }
        }
    }
    /**
     * 释放锁
     *
     * @param lock 锁对象
     */
    public void unlock(Lock lock) {
        if (lock instanceof RLock) {
            RLock rLock = (RLock) lock;
            if (rLock.isHeldByCurrentThread()) {
                rLock.unlock();
            }
        } else {
            lock.unlock();
        }
    }
}
        
            
            
              java
              
              
            
          
          import com.xxx.annotation.DistributedLock;
import com.xxx.common.constant.RedisKey;
import com.xxx.common.exception.RedissonLockException;
import lombok.extern.slf4j.Slf4j;
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.EvaluationContext;
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.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.concurrent.locks.Lock;
@Aspect
@Component
@Slf4j
public class RedissonLockAspect {
    private final RedissonLockManager lockManager;
    private final ExpressionParser parser = new SpelExpressionParser();
    public RedissonLockAspect(RedissonLockManager lockManager) {
        this.lockManager = lockManager;
    }
    @Around("@annotation(distributedLock)")
    public Object around(ProceedingJoinPoint joinPoint, DistributedLock distributedLock) throws Throwable {
        // 解析锁的key
        String key = RedisKey.LOCK_BASE + distributedLock.prefix() + "_" +parseKey(distributedLock.key(), joinPoint);
        log.info("获取锁: {}", key);
        // 获取对应类型的锁
        Lock lock = lockManager.getLock(key, distributedLock.lockType());
        boolean locked = false;
        try {
            // 尝试获取锁
            locked = lockManager.tryLock(
                lock,
                distributedLock.waitTime(),
                distributedLock.leaseTime(),
                distributedLock.timeUnit()
            );
            if (locked) {
                log.info("获取锁成功,开始执行目标方法");
                // 执行目标方法
                return joinPoint.proceed();
            } else {
                log.info("获取锁失败,返回失败信息");
                return handleLockFail(joinPoint, distributedLock);
            }
        } finally {
            // 释放锁
            if (locked) {
                lockManager.unlock(lock);
            }
        }
    }
    /**
     * 解析锁的key,支持SpEL表达式
     */
    private String parseKey(String keyExpression, ProceedingJoinPoint joinPoint) {
        if (!keyExpression.contains("#")) {
            return keyExpression;
        }
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        Parameter[] paramNames = method.getParameters();
        Object[] args = joinPoint.getArgs();
        EvaluationContext context = new StandardEvaluationContext();
        if (paramNames != null) {
            for (int i = 0; i < paramNames.length; i++) {
                context.setVariable(paramNames[i].getName(), args[i]);
            }
        }
        return parser.parseExpression(keyExpression).getValue(context, String.class);
    }
    /**
     * 处理获取锁失败的情况
     */
    private Object handleLockFail(ProceedingJoinPoint joinPoint, DistributedLock distributedLock) throws Throwable {
        switch (distributedLock.failStrategy()) {
            case RETURN_NULL:
                Class<?> returnType = ((MethodSignature) joinPoint.getSignature()).getReturnType();
                if (returnType.isPrimitive()) {
                    // 原始类型无法返回null,抛出异常
                    throw new RedissonLockException(distributedLock.failMessage());
                }
                return null;
            case CONTINUE:
                return joinPoint.proceed();
            case EXCEPTION:
            default:
                throw new RedissonLockException(distributedLock.failMessage());
        }
    }
}
        
            
            
              java
              
              
            
          
          public enum LockTypeEnum {
    /**
     * 可重入锁
     */
    REENTRANT,
    /**
     * 公平锁
     */
    FAIR,
    /**
     * 读锁
     */
    READ,
    /**
     * 写锁
     */
    WRITE
}
        
            
            
              java
              
              
            
          
          public enum LockFailStrategyEnum {
    /**
     * 抛出异常
     */
    EXCEPTION,
    /**
     * 返回null(仅适用于有返回值的方法)
     */
    RETURN_NULL,
    /**
     * 忽略锁直接执行
     */
    CONTINUE
}
        4,使用注解
下面只是一个例子,实际场景中可能是锁订单号或者锁商品id或仓库id之类的。
            
            
              java
              
              
            
          
          @DistributedLock(prefix = "oauth", key =  "#id + ':' + #loginCondition.account", lockType = LockTypeEnum.REENTRANT)
    @Override
    public RestResponse refreshToken(String id, LoginCondition loginCondition) {
        log.info("刷新token");
        return oAuthFeign.refreshToken();
    }
        prefix可以自己定义为场景值,或者为空都行,它只是redis key中的一段儿,没啥意义。
关键在于key参数,key是支持SpEL表达式,具体怎么设置可以看下面,可以根据自己的需要来修改。
// 1. 简单参数引用
@DistributedLock(key = "#userId")
// 2. 多个参数拼接
@DistributedLock(key = "#userId + ':' + #orderId")
// 3. 对象属性引用
@DistributedLock(key = "#user.id")
// 4. 复杂对象属性拼接
@DistributedLock(key = "#user.id + ':' + #order.orderNo")
// 5. 使用方法调用
@DistributedLock(key = "#user.getId().concat('-').concat(#type)")
// 6. 字面量和参数混合
@DistributedLock(key = "'order_lock:' + #orderId")