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

相关推荐
Pitayafruit2 小时前
Spring AI 进阶之路05:集成 MCP 协议实现工具调用
spring boot·后端·llm
JIngJaneIL3 小时前
基于java+ vue助农电商系统(源码+数据库+文档)
java·开发语言·前端·数据库·vue.js·spring boot·后端
q_19132846954 小时前
基于Springboot+MySQL+RuoYi的会议室预约管理系统
java·vue.js·spring boot·后端·mysql·若依·计算机毕业设计
云和数据.ChenGuang4 小时前
OpenEuler系统下RabbitMQ安装与基础配置教程
服务器·分布式·rabbitmq·ruby·数据库运维工程师·运维教程
可爱又迷人的反派角色“yang”5 小时前
redis知识点总集
linux·运维·数据库·redis·缓存
BullSmall5 小时前
Redis 性能调优(二)
数据库·redis·缓存
gugugu.5 小时前
Redis ZSet类型深度解析:有序集合的原理与实战应用
网络·windows·redis
学习编程的Kitty5 小时前
Redis(1)——持久化
数据库·redis·mybatis
VX:Fegn08955 小时前
计算机毕业设计|基于springboot + vue乡村振兴服务系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计