【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命令行详细说明

相关推荐
寻找沙漠的人1 小时前
Redis 缓存
数据库·redis·缓存
小白的白是白痴的白1 小时前
Spark基础介绍
大数据·分布式·spark
火山引擎开发者社区1 小时前
推理加速新范式:火山引擎高性能分布式 KVCache (EIC)核心技术解读
分布式·火山引擎·eic
CONTONUE1 小时前
【Spark】使用Spark集群搭建Yarn模式
大数据·分布式·spark
cainiao0806052 小时前
《Spring Boot 4.0新特性深度解析》
java·spring boot·后端
Clockwiseee2 小时前
SSTI记录
运维·服务器·redis·学习
呆萌很2 小时前
基于 Spring Boot 瑞吉外卖系统开发(十二)
spring boot
code在飞3 小时前
windows 部署 Kafka3.x KRaft 模式 不依赖 ZooKeeper
windows·分布式·zookeeper·kafka
CONTONUE3 小时前
Spark处理过程-转换算子和行动算子(一)
大数据·分布式·spark
计算机学姐3 小时前
基于SpringBoot的小区停车位管理系统
java·vue.js·spring boot·后端·mysql·spring·maven