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

相关推荐
郑洁文9 分钟前
基于Spring Boot的流浪动物救助网站
java·spring boot·后端·毕设·流浪动物救助
Trouvaille ~11 分钟前
【Redis篇】Redis 哨兵(Sentinel):高可用自动故障转移
数据库·redis·缓存·中间件·sentinel·高可用·哨兵
giaz14n9X1 小时前
Redis 分布式锁进阶第五十七篇
数据库·redis·分布式
指令集梦境2 小时前
Cursor + Spring Boot实战:从零写一个RESTful API
spring boot·后端·restful
WyCAGy8ij2 小时前
Redis 分布式锁进阶第二篇讲解
数据库·redis·分布式
冰西瓜6004 小时前
深度学习的数学原理(四十二)—— 分布式训练
人工智能·分布式·深度学习
普通网友4 小时前
springboot之集成Elasticsearch
spring boot·后端·elasticsearch
学Linux的语莫4 小时前
redis的数据类型和使用
数据库·redis·缓存
invicinble5 小时前
关于flowable流程引擎技术栈相关
spring boot
超梦dasgg6 小时前
Redis ZSet(有序集合)底层数据结构
数据结构·数据库·redis