spring boot 实战之分布式锁

发布于:2025-07-20 ⋅ 阅读:(15) ⋅ 点赞:(0)

在 Spring Boot 项目中集成分布式锁,可结合 Redis 和 Redisson 框架,实现高性能、高可用的分布式锁服务。下面从环境搭建到实战案例,一步步带你实现 Spring Boot 分布式锁。

一、环境准备:引入依赖

pom.xml中添加 Redisson 和 Redis 客户端依赖:

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.20.0</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
  • redisson-spring-boot-starter:Redisson 的 Spring Boot 自动配置启动器。
  • spring-boot-starter-data-redis:Spring Data Redis 集成包。

二、配置 Redisson 客户端

application.yml中配置 Redis 连接信息:

spring:
  redis:
    host: localhost
    port: 6379
    password: 123456  # 若无密码可省略
    timeout: 3000ms   # 连接超时时间

创建 Redisson 配置类(可选,使用默认配置时可省略):

import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RedissonConfig {
    @Bean
    public RedissonClient redissonClient() {
        Config config = new Config();
        // 单节点模式(生产环境建议用集群模式)
        config.useSingleServer()
              .setAddress("redis://" + 
                         "${spring.redis.host}:${spring.redis.port}")
              .setPassword("${spring.redis.password}")
              .setConnectTimeout((int) 
                         "${spring.redis.timeout}".replace("ms", ""));
        return Redisson.create(config);
    }
}

三、分布式锁实战:秒杀场景

假设我们要实现一个商品秒杀功能,关键是保证库存扣减的原子性,避免超卖。

1. 自定义注解:简化锁使用

创建@DistributedLock注解,用于标记需要加锁的方法:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DistributedLock {
    String value() default "defaultLock"; // 锁的key前缀
    long waitTime() default 10;           // 等待锁的时间(秒)
    long leaseTime() default 30;          // 锁的持有时间(秒)
}
2. AOP 切面:实现锁逻辑

创建切面类,拦截带有@DistributedLock注解的方法,自动加锁和解锁:

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.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;

@Aspect
@Component
public class DistributedLockAspect {
    private final RedissonClient redissonClient;

    public DistributedLockAspect(RedissonClient redissonClient) {
        this.redissonClient = redissonClient;
    }

    @Around("@annotation(com.example.annotation.DistributedLock)")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        DistributedLock lockAnnotation = method.getAnnotation(DistributedLock.class);
        
        // 生成锁的key(类名+方法名+自定义前缀)
        String className = joinPoint.getTarget().getClass().getSimpleName();
        String methodName = method.getName();
        String lockKey = lockAnnotation.value() + ":" + className + ":" + methodName;
        
        RLock lock = redissonClient.getLock(lockKey);
        boolean isLocked = false;
        
        try {
            // 尝试获取锁(等待时间+持有时间)
            isLocked = lock.tryLock(
                lockAnnotation.waitTime(), 
                lockAnnotation.leaseTime(), 
                java.util.concurrent.TimeUnit.SECONDS
            );
            
            if (isLocked) {
                // 获取锁成功,执行方法
                return joinPoint.proceed();
            } else {
                // 获取锁失败,抛出异常或返回失败结果
                throw new RuntimeException("获取锁失败,请稍后重试");
            }
        } finally {
            // 释放锁(仅在当前线程持有锁时释放)
            if (isLocked && lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }
}
3. 业务服务:扣减库存

创建商品服务,使用@DistributedLock注解保护库存扣减逻辑:

import com.example.annotation.DistributedLock;
import org.springframework.stereotype.Service;

@Service
public class ProductService {
    // 使用分布式锁保护库存扣减操作
    @DistributedLock(value = "product:stock", waitTime = 5, leaseTime = 20)
    public boolean deductStock(String productId, int quantity) {
        // 模拟从数据库查询库存
        int stock = getStockFromDb(productId);
        
        if (stock >= quantity) {
            // 扣减库存
            boolean success = updateStockInDb(productId, stock - quantity);
            return success;
        }
        return false;
    }

    private int getStockFromDb(String productId) {
        // 实际应查询数据库或缓存
        return 100; 
    }

    private boolean updateStockInDb(String productId, int newStock) {
        // 实际应更新数据库
        return true;
    }
}
4. 控制器:暴露秒杀接口
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class SeckillController {
    private final ProductService productService;

    public SeckillController(ProductService productService) {
        this.productService = productService;
    }

    @PostMapping("/seckill/{productId}/{quantity}")
    public String seckill(@PathVariable String productId, 
                          @PathVariable int quantity) {
        boolean success = productService.deductStock(productId, quantity);
        return success ? "秒杀成功" : "库存不足,秒杀失败";
    }
}

四、高级用法:读写锁与公平锁

Redisson 支持多种锁类型,满足不同场景需求。

1. 读写锁(ReadWriteLock)

适合读多写少的场景,允许多个读操作并行,但写操作会互斥:

import org.redisson.api.RReadWriteLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;

@Service
public class CacheService {
    private final RedissonClient redissonClient;

    public CacheService(RedissonClient redissonClient) {
        this.redissonClient = redissonClient;
    }

    // 读锁:允许多个线程同时读
    public String getCache(String key) {
        RReadWriteLock rwLock = redissonClient.getReadWriteLock("cache:" + key);
        rwLock.readLock().lock();
        
        try {
            // 从缓存或数据库读取数据
            return "cacheValue";
        } finally {
            rwLock.readLock().unlock();
        }
    }

    // 写锁:互斥,同一时间只能有一个线程写
    public void updateCache(String key, String value) {
        RReadWriteLock rwLock = redissonClient.getReadWriteLock("cache:" + key);
        rwLock.writeLock().lock();
        
        try {
            // 更新缓存或数据库
        } finally {
            rwLock.writeLock().unlock();
        }
    }
}
2. 公平锁(Fair Lock)

按请求顺序获取锁,避免某些线程长期饥饿:

import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;

@Service
public class OrderService {
    private final RedissonClient redissonClient;

    public OrderService(RedissonClient redissonClient) {
        this.redissonClient = redissonClient;
    }

    public String createOrder() {
        // 获取公平锁(加参数true)
        RLock fairLock = redissonClient.getFairLock("order:create");
        fairLock.lock();
        
        try {
            // 生成唯一订单号
            return generateOrderId();
        } finally {
            fairLock.unlock();
        }
    }

    private String generateOrderId() {
        // 生成订单号的逻辑
        return "ORD" + System.currentTimeMillis();
    }
}

五、监控与异常处理

1. 自定义异常
public class LockAcquisitionException extends RuntimeException {
    public LockAcquisitionException(String message) {
        super(message);
    }
}
2. 全局异常处理器
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(LockAcquisitionException.class)
    public String handleLockException(LockAcquisitionException e) {
        return "系统繁忙,请稍后重试";
    }
}

六、生产环境注意事项

  1. 集群模式配置:生产环境建议使用 Redis 集群,提高可用性:
spring:
  redis:
    cluster:
      nodes:
        - 192.168.1.1:6379
        - 192.168.1.2:6379
        - 192.168.1.3:6379

锁超时设置:根据业务耗时合理设置waitTimeleaseTime,避免锁持有过久或提前释放。

  1. 监控与告警:监控 Redis 性能和锁竞争情况,对频繁获取锁失败的场景告警。

  2. 降级策略:高并发场景下,若锁获取失败可降级为排队或返回 “稍后重试”,避免服务雪崩。

总结

在 Spring Boot 中集成 Redis 分布式锁,通过 Redisson 框架可以轻松实现:

  1. 依赖引入:添加 Redisson 和 Redis Starter 依赖。
  2. 配置客户端:连接 Redis 服务器(单节点或集群)。
  3. 自定义注解与 AOP:简化锁的使用,避免代码重复。
  4. 选择合适的锁类型:普通锁、读写锁、公平锁等。
  5. 异常处理与监控:保证系统稳定性。

通过这种方式,你可以在分布式系统中安全、高效地实现资源互斥访问,避免数据竞争问题。


网站公告

今日签到

点亮在社区的每一天
去签到