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

相关推荐
顽疲23 分钟前
java springboot vue 养老系统源码详解:请假 care_leave 销假与床位停费日扣实践
java·vue.js·spring boot
Java_AI工程师1 小时前
Spring4 / SpringBoot1.x 老项目想接入 AI?不用升级框架!3 套无痛落地方案
java·人工智能·spring boot
VimorphTech1 小时前
Java 文件缓存实战:如何避免读到未下载完成的视频?
java·spring boot
PingCAP2 小时前
善战者无赫赫之功:消费金融核心系统的数据库选型与升级路径
数据库·分布式·架构
十年Java程序媛3 小时前
JVM 内存区域与对象内存布局|new 对象底层流程,对象头 MarkWord 详解
java·jvm·spring boot
linyb极客之路3 小时前
Spring Boot 2升3太痛苦?用OpenRewrite批量搞定,少走90%弯路!
spring boot
171320330计算机毕设编程3 小时前
计算机毕业设计 基于springboot+vue的在线观鸟视频播放网站 LW PPT
java·大数据·vue.js·spring boot·算法·课程设计
库库的数据库笔记3 小时前
金融核心交易系统的数据库国产信创改造该如何做?
大数据·数据库·分布式·mysql·oracle·金融·数据库架构
xiaoqiMikko6 小时前
Redis 实战(二):宕机会丢多少数据 —— kill -9 与真断电下 RDB / AOF 逐个实测(6.2.4)
redis·后端
星辰云-8 小时前
# 基于Spring Boot的德州特产扒鸡商城系统(毕设|源码|论文|演示视频)
spring boot·后端·课程设计