【Springboot知识】Springboot结合redis实现分布式锁

Springboot结合redis实现分布式锁

使用Spring Boot结合Redis实现分布式锁,并通过接口注解来控制锁的行为,可以让我们在分布式系统中更加灵活地控制资源访问。以下是一个详细的实现步骤和代码示例,包括注解定义、切面处理、Redis锁实现以及SpEL表达式支持。

实现原理说明

  1. 注解定义 :定义一个自定义注解@DistributedLock,允许通过SpEL表达式指定锁的值。
  2. 切面处理:使用Spring AOP切面在方法执行前后获取和释放Redis锁。
  3. Redis锁实现 :实现一个简单的Redis分布式锁,利用Redis的SETNX命令和过期时间来实现。
  4. SpEL表达式支持:在切面中解析SpEL表达式,获取锁的值。

实现代码

1. 定义注解

首先定义一个自定义注解@DistributedLock,允许通过SpEL表达式指定锁的值。

java 复制代码
import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DistributedLock {
    String value() default ""; // SpEL expression to evaluate lock key
}
2. Redis分布式锁实现

实现一个简单的Redis分布式锁。

java 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

@Component
public class RedisDistributedLock {

    private static final String LOCK_PREFIX = "lock:";
    private final StringRedisTemplate redisTemplate;

    @Autowired
    public RedisDistributedLock(StringRedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    public boolean tryLock(String lockKey, String requestId, long expireTime, TimeUnit unit) {
        String result = redisTemplate.opsForValue().setIfAbsent(LOCK_PREFIX + lockKey, requestId, expireTime, unit);
        return result != null;
    }

    public boolean unlock(String lockKey, String requestId) {
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
                "return redis.call('del', KEYS[1]) " +
                "else " +
                "return 0 " +
                "end";
        Object result = redisTemplate.execute(script, Collections.singletonList(LOCK_PREFIX + lockKey), requestId);
        return result.equals(1L);
    }
}
3. AOP切面实现

使用Spring AOP切面在方法执行前后获取和释放Redis锁。

java 复制代码
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.beans.factory.annotation.Autowired;
import org.springframework.context.expression.MethodBasedEvaluationContext;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Aspect
@Component
public class DistributedLockAspect {

    @Autowired
    private RedisDistributedLock redisDistributedLock;

    private final ExpressionParser parser = new SpelExpressionParser();

    @Around("@annotation(distributedLock)")
    public Object around(ProceedingJoinPoint joinPoint, DistributedLock distributedLock) throws Throwable {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        Object[] args = joinPoint.getArgs();

        String lockKey = resolveLockKey(distributedLock.value(), method, args);
        String requestId = UUID.randomUUID().toString();

        boolean locked = redisDistributedLock.tryLock(lockKey, requestId, 30, TimeUnit.SECONDS);
        if (locked) {
            try {
                return joinPoint.proceed();
            } finally {
                redisDistributedLock.unlock(lockKey, requestId);
            }
        } else {
            throw new RuntimeException("Could not acquire lock for key: " + lockKey);
        }
    }

    private String resolveLockKey(String spelExpression, Method method, Object[] args) {
        MethodBasedEvaluationContext context = new MethodBasedEvaluationContext(
                null, method, args, new DefaultParameterNameDiscoverer());
        return parser.parseExpression(spelExpression).getValue(context, String.class);
    }
}
4. 配置RedisTemplate

在Spring Boot的配置类中配置StringRedisTemplate

java 复制代码
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.StringRedisTemplate;

@Configuration
public class RedisConfig {

    @Bean
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
        return new StringRedisTemplate(redisConnectionFactory);
    }
}
5. 使用示例

在需要加锁的方法上使用@DistributedLock注解,并通过SpEL表达式指定锁的值。

java 复制代码
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class DemoController {

    @DistributedLock("#userId")
    @GetMapping("/user")
    public String getUser(@RequestParam String userId) {
        // Simulate some business logic
        return "User: " + userId;
    }
}

总结

通过以上步骤,我们实现了一个基于Spring Boot和Redis的分布式锁,并通过注解和SpEL表达式灵活地指定锁的值。这个实现方案可以用于保护分布式系统中的临界资源,防止并发访问导致的数据不一致问题。

注意事项

  1. 锁续期:在实际应用中,可能需要实现锁的续期机制,以防止因业务处理时间较长而导致锁过期。
  2. 锁粒度:根据业务场景选择适当的锁粒度,避免锁粒度过大导致的并发性能问题。
  3. 异常处理:确保在方法执行过程中捕获并处理所有可能的异常,以防止因异常导致锁未能正确释放。

相关文献

【Spring相关技术】Spring进阶-SpEL深入解读
【Java知识】java基础-开发一个自定义注解
【分布式技术】Redis命令行详细说明

相关推荐
用户3074596982076 小时前
Redis 延时队列详解
redis
烤代码的吐司君9 小时前
Redis 数据结构 ZSet, BIT, HyperLogLog,Geo 空间数据
redis·后端
Flittly15 小时前
【AgentScope Java新手村系列】(14)人机交互
java·spring boot·spring
Flynt1 天前
从Spring Boot 4.0升到4.1,我在Maven和gRPC上栽了跟头
java·spring boot·后端
掉鱼的猫3 天前
Spring Boot → Solon 注解迁移实战指南:一张对照表说清楚
java·spring boot
leeyi3 天前
Checkpoint 机制:Agent 怎么在断电后接着跑
redis·aigc·agent
人活一口气3 天前
Spring Boot与AIGC的完美结合:从零搭建智能内容生成平台
java·spring boot·aigc
云技纵横4 天前
一个 @Async 让循环依赖暴雷:Spring 代理的暗坑
redis
犯困蛋挞yy4 天前
用Claude快速解决Redis代码报错反复无解的问题
redis
java小白小6 天前
SpringBoot(01): 初识SpringBoot,从Spring的痛点说起
spring boot