基于AOP实现Redis分布式锁

  |   0 评论   |   0 浏览

基于AOP实现Redis分布式锁

0.场景描述

随着业务发展的需要,原单体单机部署的系统被演化成分布式集群系统后,由于分布式系统多线程、多进程并且分布在不同机器上,这将使原单机部署情况下的并发控制锁策略失效,单纯的Java API并不能提供分布式锁的能力。为了解决这个问题就需要一种跨JVM的互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题!

1.大体思路

基于注解和aop环绕的方式实现,

1.自定义一个注解:定义key的生成规则、key的过期时间、重试次数、重试间隔等

  • 首先我们需要确定redis的key值,可以时直接指定的字符串也可以是通过SpringEl表达式获取参数的值做key
  • 考虑不通业务场景需要执行的时间并不固定,key的过期时间作为注解的配置项,更灵活
  • 添加重试策略

2.AOP环绕拦截目标方法,执行目标方法之前,我们需要先获取锁

3.获取锁成功执行目标方法,反之则不执行目标方法

4.执行完目标方法之后,释放锁(手动释放(推荐),自动释放:redis key过期)

2.准备工作

1.引入相关依赖

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

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <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>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.github.xingfudeshi</groupId>
            <artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
            <version>4.6.0</version>
        </dependency>

2.redis配置

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;

import java.time.Duration;

@EnableCaching
@Configuration
public class RedisConfig {

    @Bean
    @ConditionalOnMissingBean(name = "redisTemplate")
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);

        // 配置键序列化器
        RedisSerializer<String> stringSerializer = new StringRedisSerializer();
        redisTemplate.setKeySerializer(stringSerializer);
        redisTemplate.setHashKeySerializer(stringSerializer);

        // 配置值序列化器(在构造器中指定 ObjectMapper,替代 setObjectMapper 方法)
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.activateDefaultTyping(
                LaissezFaireSubTypeValidator.instance,
                ObjectMapper.DefaultTyping.NON_FINAL,
                JsonTypeInfo.As.WRAPPER_ARRAY
        );
        
        // 关键修改:通过构造器传入 ObjectMapper,而非使用 setObjectMapper()
        Jackson2JsonRedisSerializer<Object> jacksonSerializer = new Jackson2JsonRedisSerializer<>(objectMapper, Object.class);

        redisTemplate.setValueSerializer(jacksonSerializer);
        redisTemplate.setHashValueSerializer(jacksonSerializer);

        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }

    @Bean
    @ConditionalOnMissingBean(CacheManager.class)
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        // 配置值序列化器(同样通过构造器传入 ObjectMapper)
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.activateDefaultTyping(
                LaissezFaireSubTypeValidator.instance,
                ObjectMapper.DefaultTyping.NON_FINAL,
                JsonTypeInfo.As.WRAPPER_ARRAY
        );
        Jackson2JsonRedisSerializer<Object> jacksonSerializer = new Jackson2JsonRedisSerializer<>(objectMapper, Object.class);

        // 缓存配置
        RedisCacheConfiguration cacheConfig = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofSeconds(600))
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jacksonSerializer))
                .disableCachingNullValues();

        return RedisCacheManager.builder(redisConnectionFactory)
                .cacheDefaults(cacheConfig)
                .build();
    }
}

3.application.yaml配置

server:
  port: 8080
  servlet:
    context-path: /api
spring:
  application:
    name: distributedLock
  data:
    redis:
      host: localhost
      port: 6379


# springdoc-openapi项目配置
springdoc:
  swagger-ui:
    path: /swagger-ui.html
    tags-sorter: alpha
    operations-sorter: alpha
  api-docs:
    path: /v3/api-docs
  group-configs:
    - group: 'default'
      paths-to-match: '/**'
      packages-to-scan: com.llp.llpdistributedlock
# knife4j增强配置
knife4j:
  enable: true
  setting:
    language: zh_cn

3.代码实现

1.自定义注解

import com.llp.llpdistributedlock.keyresolver.DistributedLockKeyResolver;
import com.llp.llpdistributedlock.keyresolver.impl.DefaultDistributedLockKeyResolver;

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;

/**
 * 分布式锁注解
 * 基于Redis实现的分布式锁,支持自动加锁和释放锁
 * 使用Lua脚本保证原子性操作
 *
 * @author llp
 * @date 2025-09-25
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface DistributedLock {
    
    /**
     * 锁的key,支持SpEL表达式
     * 例如:
     * - "'product:' + #productId"
     * - "user:operation:' + #userId"
     * - "'complex:' + #request.userId + '_' + #request.resourceType + '_' + #request.resourceId"
     */
    String key();
    
    /**
     * 锁key的前缀,默认为"distributed:lock"
     */
    String prefix() default "distributed:lock";
    
    /**
     * 等待获取锁的时间,默认3秒
     * 超过这个时间还没获取到锁就放弃
     */
    long waitTime() default 3L;
    
    /**
     * 锁的租期时间,默认30秒
     * 到期后自动释放锁,防止死锁
     */
    long leaseTime() default 30L;
    
    /**
     * 时间单位,默认为秒
     */
    TimeUnit timeUnit() default TimeUnit.SECONDS;
    
    /**
     * 获取锁失败时的提示信息
     */
    String message() default "获取分布式锁失败,请稍后重试";
    
    /**
     * 是否自动释放锁,默认为true
     * 如果为false,需要手动调用unlock方法释放锁
     */
    boolean autoUnlock() default true;
    
    /**
     * 使用的Key解析器
     */
    Class<? extends DistributedLockKeyResolver> keyResolver() default DefaultDistributedLockKeyResolver.class;
    
    /**
     * 锁的重试策略
     * FAIL_FAST: 快速失败,不重试
     * RETRY: 重试获取锁
     */
    RetryStrategy retryStrategy() default RetryStrategy.FAIL_FAST;
    
    /**
     * 重试次数,当retryStrategy为RETRY时生效,默认3次
     */
    int retryCount() default 3;
    
    /**
     * 重试间隔时间(毫秒),默认100ms
     */
    long retryInterval() default 100L;
    
    /**
     * 重试策略枚举
     */
    enum RetryStrategy {
        /**
         * 快速失败,不重试
         */
        FAIL_FAST,
        
        /**
         * 重试获取锁
         */
        RETRY
    }
}

2.AOP环绕

import com.llp.llpdistributedlock.annotation.DistributedLock;
import com.llp.llpdistributedlock.aspect.distributedLock.DistributedLockService;
import com.llp.llpdistributedlock.keyresolver.DistributedLockKeyResolver;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

/**
 * 分布式锁切面
 * <p>
 * 拦截声明了 {@link DistributedLock} 注解的方法,实现分布式锁功能
 * <p>
 * 功能特性:
 * 1. 自动获取和释放锁
 * 2. 支持自定义锁key解析器
 * 3. 支持重试策略
 * 4. 异常情况下自动释放锁
 * 5. 支持可配置的超时时间
 *
 * @author llp
 * @date 2025-09-25
 */
@Component
@Aspect
@Slf4j
public class DistributedLockAspect {

    private final DistributedLockService distributedLockService;

    /**
     * DistributedLockKeyResolver 集合
     */
    private final Map<Class<? extends DistributedLockKeyResolver>, DistributedLockKeyResolver> keyResolvers;

    @Autowired
    public DistributedLockAspect(DistributedLockService distributedLockService,
                                 List<DistributedLockKeyResolver> keyResolvers) {
        this.distributedLockService = distributedLockService;

        // 构建key解析器映射
        Map<Class<? extends DistributedLockKeyResolver>, DistributedLockKeyResolver> resolverMap = new HashMap<>();
        for (DistributedLockKeyResolver keyResolver : keyResolvers) {
            resolverMap.put(keyResolver.getClass(), keyResolver);
        }
        this.keyResolvers = resolverMap;
    }

    /**
     * 环绕通知:处理分布式锁逻辑
     *
     * @param joinPoint       切点
     * @param distributedLock 分布式锁注解
     * @return 方法执行结果
     * @throws Throwable 可能抛出的异常
     */
    @Around("@annotation(distributedLock)")
    public Object around(ProceedingJoinPoint joinPoint, DistributedLock distributedLock) throws Throwable {
        // 解析锁的key
        String lockKey = resolveLockKey(joinPoint, distributedLock);

        if (!StringUtils.hasText(lockKey)) {
            throw new RuntimeException("分布式锁key不能为空");
        }

        // 完整的锁key(加上前缀)
        String fullLockKey = distributedLock.prefix() + ":" + lockKey;

        boolean lockAcquired = false;
        int retryCount = 0;
        int maxRetries = distributedLock.retryStrategy() == DistributedLock.RetryStrategy.RETRY ?
                distributedLock.retryCount() : 0;

        String lockValue = generateLockValue();
        try {
            // 尝试获取锁(支持重试)
            do {
                lockAcquired = distributedLockService.tryLock(
                        fullLockKey,
                        lockValue,
                        distributedLock.waitTime(),
                        distributedLock.leaseTime(),
                        distributedLock.timeUnit()
                );

                if (lockAcquired) {
                    log.info("获取分布式锁成功: key={}, retryCount={}", fullLockKey, retryCount);
                    break;
                }

                // 重试策略处理
                if (distributedLock.retryStrategy() == DistributedLock.RetryStrategy.RETRY && retryCount < maxRetries) {
                    retryCount++;
                    log.info("获取分布式锁失败,准备重试: key={}, retryCount={}/{}", fullLockKey, retryCount, maxRetries);

                    // 重试间隔
                    if (distributedLock.retryInterval() > 0) {
                        Thread.sleep(distributedLock.retryInterval());
                    }
                } else {
                    break;
                }
            } while (retryCount <= maxRetries);

            // 获取锁失败
            if (!lockAcquired) {
                String errorMsg = String.format("获取分布式锁失败: key=%s, 重试次数=%d, 消息=%s",
                        fullLockKey, retryCount, distributedLock.message());
                log.error(errorMsg);
                throw new RuntimeException(distributedLock.message());
            }

            // 执行目标方法
            log.info("开始执行业务方法: {}", joinPoint.getSignature().toShortString());
            Object result = joinPoint.proceed();
            log.info("业务方法执行完成: {}", joinPoint.getSignature().toShortString());

            return result;

        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            log.error("获取分布式锁时被中断: key={}", fullLockKey, e);
            throw new RuntimeException("获取分布式锁时被中断", e);
        } catch (RuntimeException e) {
            // 分布式锁异常直接抛出
            throw e;
        } catch (Throwable e) {
            log.error("执行业务方法时发生异常: key={}, method={}", fullLockKey, joinPoint.getSignature().toShortString(), e);
            throw e;
        } finally {
            // 释放锁
            if (lockAcquired && distributedLock.autoUnlock()) {
                try {
                    boolean unlocked = distributedLockService.unlock(fullLockKey, lockValue);
                    if (unlocked) {
                        log.info("释放分布式锁成功: key={}", fullLockKey);
                    } else {
                        log.warn("释放分布式锁失败,锁可能已过期: key={}", fullLockKey);
                    }
                } catch (Exception e) {
                    log.error("释放分布式锁时发生异常: key={}", fullLockKey, e);
                }
            }
        }
    }

    /**
     * 解析锁的key
     *
     * @param joinPoint       切点
     * @param distributedLock 分布式锁注解
     * @return 解析后的key
     */
    private String resolveLockKey(ProceedingJoinPoint joinPoint, DistributedLock distributedLock) {
        try {
            // 获取key解析器
            DistributedLockKeyResolver keyResolver = keyResolvers.get(distributedLock.keyResolver());
            if (keyResolver == null) {
                throw new RuntimeException("找不到对应的 DistributedLockKeyResolver: " + distributedLock.keyResolver().getName());
            }

            // 解析key
            String resolvedKey = keyResolver.resolver(joinPoint, distributedLock);

            if (!StringUtils.hasText(resolvedKey)) {
                throw new RuntimeException("解析的锁key不能为空");
            }

            return resolvedKey;

        } catch (Exception e) {
            log.error("解析分布式锁key失败: annotation={}", distributedLock, e);
            throw new RuntimeException("解析分布式锁key失败: " + e.getMessage(), e);
        }
    }

    /**
     * 生成锁值(UUID)
     *
     * @return 锁值
     */
    private String generateLockValue() {
        return UUID.randomUUID().toString();
    }

}

3.分布式锁Key解析器接口

import com.llp.llpdistributedlock.annotation.DistributedLock;
import org.aspectj.lang.JoinPoint;

/**
 * 分布式锁Key解析器接口
 * 
 * 用于解析分布式锁的key,支持SpEL表达式
 * 
 * @author llp
 * @date 2025-09-25
 */
public interface DistributedLockKeyResolver {
    
    /**
     * 解析锁的key
     *
     * @param joinPoint 切点信息
     * @param distributedLock 分布式锁注解
     * @return 解析后的key
     */
    String resolver(JoinPoint joinPoint, DistributedLock distributedLock);
}
import com.llp.llpdistributedlock.annotation.DistributedLock;
import com.llp.llpdistributedlock.keyresolver.DistributedLockKeyResolver;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
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.lang.reflect.Method;

/**
 * 默认分布式锁Key解析器
 * 
 * 支持SpEL表达式解析,可以使用方法参数、对象属性等
 * 
 * 支持的表达式示例:
 * - "#{#userId}" - 直接使用参数
 * - "#{#user.id}" - 使用对象属性
 * - "#{T(System).currentTimeMillis()}" - 使用静态方法
 * 
 * @author llp
 * @date 2025-09-25
 */
@Component
@Slf4j
public class DefaultDistributedLockKeyResolver implements DistributedLockKeyResolver {
    
    /**
     * SpEL表达式解析器
     */
    private final ExpressionParser parser = new SpelExpressionParser();
    
    /**
     * 参数名发现器
     */
    private final DefaultParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();
    
    @Override
    public String resolver(JoinPoint joinPoint, DistributedLock distributedLock) {
        String key = distributedLock.key();
        
        // 如果key不包含SpEL表达式,直接返回
        if (!key.contains("#")) {
            return key;
        }
        
        try {
            // 获取方法信息
            MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
            Method method = methodSignature.getMethod();
            
            // 获取参数名和参数值
            String[] parameterNames = nameDiscoverer.getParameterNames(method);
            Object[] args = joinPoint.getArgs();
            
            // 创建SpEL上下文
            EvaluationContext context = new StandardEvaluationContext();
            
            // 设置参数到上下文中
            if (parameterNames != null && args != null) {
                for (int i = 0; i < parameterNames.length; i++) {
                    context.setVariable(parameterNames[i], args[i]);
                }
            }
            
            // 解析表达式
            Expression expression = parser.parseExpression(key);
            Object value = expression.getValue(context);
            
            return value != null ? value.toString() : "";
            
        } catch (Exception e) {
            log.error("解析分布式锁key失败,key: {}, error: {}", key, e.getMessage(), e);
            // 如果解析失败,返回方法名+参数值的组合作为fallback
            return generateFallbackKey(joinPoint);
        }
    }
    
    /**
     * 生成备用key
     * 
     * @param joinPoint 切点信息
     * @return 备用key
     */
    private String generateFallbackKey(JoinPoint joinPoint) {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        String className = methodSignature.getDeclaringTypeName();
        String methodName = methodSignature.getName();
        
        StringBuilder keyBuilder = new StringBuilder();
        keyBuilder.append(className).append(".").append(methodName);
        
        Object[] args = joinPoint.getArgs();
        if (args != null && args.length > 0) {
            keyBuilder.append(":");
            for (int i = 0; i < args.length; i++) {
                if (i > 0) {
                    keyBuilder.append(",");
                }
                keyBuilder.append(args[i] != null ? args[i].toString() : "null");
            }
        }
        
        return keyBuilder.toString();
    }
}

4.分布式锁接口

/**
 * 分布式锁服务接口
 * 
 * 基于Redis实现的分布式锁服务,支持自动过期、可重入等特性
 * 
 * @author llp
 * @date 2025-09-25
 */
public interface DistributedLockService {
    
    /**
     * 尝试获取锁(不等待)
     * 
     * @param key 锁的key
     * @param leaseTime 锁的租期时间
     * @param timeUnit 时间单位
     * @return 是否获取成功
     */
    boolean tryLock(String key, String value, long leaseTime, TimeUnit timeUnit);

    /**
     * 尝试获取锁(等待指定时间)
     * 
     * @param key 锁的key
     * @param waitTime 等待时间
     * @param leaseTime 锁的租期时间
     * @param timeUnit 时间单位
     * @return 是否获取成功
     */
    boolean tryLock(String key,String value, long waitTime, long leaseTime, TimeUnit timeUnit);
    
    /**
     * 释放锁
     * 
     * @param key 锁的key
     * @return 是否释放成功
     */
    boolean unlock(String key,String lockValue);

    /**
     * 获取锁的剩余过期时间
     *
     * @param key 锁的key
     * @param timeUnit 时间单位
     * @return 剩余过期时间,-1表示永不过期,-2表示key不存在
     */
    long getExpireTime(String key, TimeUnit timeUnit);

}
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.util.Collections;
import java.util.concurrent.TimeUnit;

/**
 * 分布式锁服务实现
 * 基于Redis和Lua脚本实现的分布式锁,特性:
 * 1. 原子性:使用Lua脚本保证获取锁和设置过期时间的原子性
 * 2. 防误删:使用UUID作为锁值,只能删除自己持有的锁
 * 3. 自动过期:支持设置锁的租期,防止死锁
 * 4. 可重入性:通过线程本地存储实现简单的可重入
 *
 * @author llp
 * @date 2025-09-25
 */
@Service
@Slf4j
@AllArgsConstructor
public class DistributedLockServiceImpl implements DistributedLockService {

    private final StringRedisTemplate stringRedisTemplate;

    /**
     * 获取锁的Lua脚本
     * 如果key不存在,则设置key和过期时间;如果key存在且值相同(可重入),则续期
     */
    private static final String LOCK_LUA_SCRIPT =
            "if redis.call('get', KEYS[1]) == ARGV[1] then " +
                    "    return redis.call('expire', KEYS[1], ARGV[2]) " +
                    "else " +
                    "    return redis.call('set', KEYS[1], ARGV[1], 'EX', ARGV[2], 'NX') " +
                    "end";

    /**
     * 释放锁的Lua脚本
     * 只有锁的值与传入的值相同时才删除,防止误删其他线程的锁
     */
    private static final String UNLOCK_LUA_SCRIPT =
            "if redis.call('get', KEYS[1]) == ARGV[1] then " +
                    "    return redis.call('del', KEYS[1]) " +
                    "else " +
                    "    return 0 " +
                    "end";

    @Override
    public boolean tryLock(String key, String value, long leaseTime, TimeUnit timeUnit) {
        return tryLock(key, value, 0, leaseTime, timeUnit);
    }

    @Override
    public boolean tryLock(String key, String lockValue, long waitTime, long leaseTime, TimeUnit timeUnit) {

        // 转换为秒
        long leaseSeconds = timeUnit.toSeconds(leaseTime);
        long waitMillis = timeUnit.toMillis(waitTime);

        long startTime = System.currentTimeMillis();

        try {
            do {
                // 尝试获取锁
                if (doTryLock(key, lockValue, leaseSeconds)) {
                    log.info("获取分布式锁成功: key={}, value={}, leaseTime={}s", key, lockValue, leaseSeconds);
                    return true;
                }

                // 如果需要等待,则休眠一段时间后重试
                if (waitMillis > 0) {
                    long elapsed = System.currentTimeMillis() - startTime;
                    if (elapsed >= waitMillis) {
                        break;
                    }

                    // 计算休眠时间,最多休眠100ms
                    long sleepTime = Math.min(100, waitMillis - elapsed);
                    Thread.sleep(sleepTime);
                }
            } while (waitMillis > 0);

            log.info("获取分布式锁失败: key={}, waitTime={}ms", key, waitMillis);
            return false;

        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            log.error("获取分布式锁被中断: key={}", key, e);
            return false;
        } catch (Exception e) {
            log.error("获取分布式锁异常: key={}", key, e);
            return false;
        }
    }

    @Override
    public boolean unlock(String key,String lockValue) {
        try {
            // 执行释放锁的Lua脚本
            DefaultRedisScript<Long> script = new DefaultRedisScript<>();
            script.setScriptText(UNLOCK_LUA_SCRIPT);
            script.setResultType(Long.class);

            Long result = stringRedisTemplate.execute(script, Collections.singletonList(key),lockValue);
            return result == 1;

        } catch (Exception e) {
            log.error("释放分布式锁异常: key={}", key, e);
            return false;
        }
    }


    @Override
    public long getExpireTime(String key, TimeUnit timeUnit) {
        try {
            long expireSeconds = stringRedisTemplate.getExpire(key, TimeUnit.SECONDS);
            return timeUnit.convert(expireSeconds, TimeUnit.SECONDS);
        } catch (Exception e) {
            log.error("获取锁过期时间异常: key={}", key, e);
            return -2;
        }
    }

    /**
     * 执行获取锁操作
     *
     * @param key          锁key
     * @param lockValue    锁值
     * @param leaseSeconds 租期(秒)
     * @return 是否获取成功
     */
    private boolean doTryLock(String key, String lockValue, long leaseSeconds) {
        DefaultRedisScript<String> script = new DefaultRedisScript<>();
        script.setScriptText(LOCK_LUA_SCRIPT);
        script.setResultType(String.class);

        String result = stringRedisTemplate.execute(script, Collections.singletonList(key), lockValue, String.valueOf(leaseSeconds));

        // 对于SET命令,成功返回"OK";对于EXPIRE命令,成功返回1
        return "OK".equals(result) || "1".equals(result);
    }

}

4.编写测试

import com.llp.llpdistributedlock.annotation.DistributedLock;
import com.llp.llpdistributedlock.model.Result;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.TimeUnit;

/**
 * 分布式锁测试
 */
@RestController
@RequestMapping("/test/distributed-lock")
@Tag(name = "分布式锁测试", description = "分布式锁功能测试接口")
@Slf4j
public class DistributedLockTestController {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 测试基础分布式锁功能
     *
     * 这个接口演示了最基本的分布式锁使用方式
     */
    @GetMapping("/basic/{productId}")
    @Operation(summary = "基础分布式锁测试", description = "测试基础的分布式锁功能,锁定特定商品的操作")
    @DistributedLock(
            key = "'product:' + #productId",
            waitTime = 3,
            leaseTime = 10,
            timeUnit = TimeUnit.SECONDS,
            message = "商品操作繁忙,请稍后重试"
    )
    public Result<String> testBasicLock(@Parameter(description = "商品ID") @PathVariable Long productId) {
        log.info("开始执行商品{}的业务逻辑", productId);

        try {
            // 模拟业务处理时间
            Thread.sleep(5000);

            // 模拟库存操作
            String key = "stock:product:" + productId;
            String currentStock = stringRedisTemplate.opsForValue().get(key);
            int stock = currentStock != null ? Integer.parseInt(currentStock) : 100;

            if (stock > 0) {
                stringRedisTemplate.opsForValue().set(key, String.valueOf(stock - 1));
                log.info("商品{}库存扣减成功,剩余库存:{}", productId, stock - 1);
                return Result.success("库存扣减成功,剩余库存:" + (stock - 1));
            } else {
                log.warn("商品{}库存不足", productId);
                return Result.failed("库存不足");
            }

        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            log.error("业务处理被中断", e);
            return Result.failed("处理被中断");
        }
    }

    /**
     * 测试用户级别的分布式锁
     *
     * 演示如何基于用户ID进行锁定
     */
    @PostMapping("/user/{userId}/operation")
    @Operation(summary = "用户级分布式锁测试", description = "测试基于用户ID的分布式锁")
    @DistributedLock(
            key = "'user:operation:' + #userId",
            prefix = "business:lock",
            waitTime = 1,
            leaseTime = 15,
            retryStrategy = DistributedLock.RetryStrategy.RETRY,
            retryCount = 1,
            timeUnit = TimeUnit.SECONDS,
            retryInterval = 500,
            message = "用户操作冲突,请稍后重试"
    )
    public Result<String> testUserLock(@Parameter(description = "用户ID") @PathVariable Long userId,
                                       @Parameter(description = "操作类型") @RequestParam String operation) {
        log.info("用户{}执行{}操作", userId, operation);

        try {
            // 模拟复杂的业务处理
            Thread.sleep(5000);

            // 记录操作日志
            String logKey = "user:log:" + userId;
            String currentTime = String.valueOf(System.currentTimeMillis());
            stringRedisTemplate.opsForValue().set(logKey, operation + ":" + currentTime, 1, TimeUnit.HOURS);

            log.info("用户{}的{}操作执行完成", userId, operation);
            return Result.success("操作执行成功");

        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            log.error("用户操作被中断", e);
            return Result.failed("操作被中断");
        }
    }

    /**
     * 测试复杂参数的分布式锁
     *
     * 演示如何使用对象属性作为锁的key
     */
    @PostMapping("/complex")
    @Operation(summary = "复杂参数分布式锁测试", description = "测试使用对象属性作为锁key的分布式锁")
    @DistributedLock(
            key = "'complex:' + #request.userId + '_' + #request.resourceType + '_' + #request.resourceId",
            waitTime = 2,
            leaseTime = 20,
            message = "资源访问冲突,请稍后重试"
    )
    public Result<String> testComplexLock(@RequestBody ComplexRequest request) {
        log.info("处理复杂请求: userId={}, resourceType={}, resourceId={}",
                request.getUserId(), request.getResourceType(), request.getResourceId());

        try {
            // 模拟复杂业务逻辑
            Thread.sleep(5500);

            String result = String.format("处理完成: 用户%d对%s类型的资源%d执行了操作",
                    request.getUserId(), request.getResourceType(), request.getResourceId());

            return Result.success(result);

        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return Result.failed("处理被中断");
        }
    }

    /**
     * 测试不自动释放锁的场景
     *
     * 演示手动控制锁释放的用法
     */
    @PostMapping("/manual-unlock/{taskId}")
    @Operation(summary = "手动释放锁测试", description = "测试手动控制锁释放的分布式锁")
    @DistributedLock(
            key = "'manual:task:' + #taskId",
            waitTime = 1,
            leaseTime = 60,
            autoUnlock = false,  // 不自动释放锁
            message = "任务正在处理中,请稍后重试"
    )
    public Result<String> testManualUnlock(@Parameter(description = "任务ID") @PathVariable String taskId) {
        log.info("开始处理任务: {}", taskId);

        try {
            // 模拟长时间任务处理
            Thread.sleep(2000);

            // 在某些条件下,可能需要保持锁不释放
            // 实际场景中,可以通过 DistributedLockService.unlock() 手动释放

            return Result.success("任务处理完成,锁仍保持(需要手动释放)");

        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return Result.failed("任务处理被中断");
        }
    }

    /**
     * 复杂请求对象
     */
    @Setter
    @Getter
    public static class ComplexRequest {
        private Long userId;
        private String resourceType;
        private Long resourceId;
        private String action;

    }
}

post请求: http://127.0.0.1:8080/api/test/distributed-lock/complex

请求示例:

{
  "userId": 0,
  "resourceType": "123",
  "resourceId": 0,
  "action": "123"
}

响应示例:

{
  "code": 201,
  "msg": "资源访问冲突,请稍后重试",
  "data": null,
  "ok": false
}

标题:基于AOP实现Redis分布式锁
作者:llp
地址:https://llinp.cn/articles/2025/09/26/1758866488502.html