spring boot 实战之分布式锁
在 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: localhostport: 6379password: 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 {@Beanpublic 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 "系统繁忙,请稍后重试";}
}
六、生产环境注意事项
- 集群模式配置:生产环境建议使用 Redis 集群,提高可用性:
spring:redis:cluster:nodes:- 192.168.1.1:6379- 192.168.1.2:6379- 192.168.1.3:6379
锁超时设置:根据业务耗时合理设置waitTime
和leaseTime
,避免锁持有过久或提前释放。
监控与告警:监控 Redis 性能和锁竞争情况,对频繁获取锁失败的场景告警。
降级策略:高并发场景下,若锁获取失败可降级为排队或返回 “稍后重试”,避免服务雪崩。
总结
在 Spring Boot 中集成 Redis 分布式锁,通过 Redisson 框架可以轻松实现:
- 依赖引入:添加 Redisson 和 Redis Starter 依赖。
- 配置客户端:连接 Redis 服务器(单节点或集群)。
- 自定义注解与 AOP:简化锁的使用,避免代码重复。
- 选择合适的锁类型:普通锁、读写锁、公平锁等。
- 异常处理与监控:保证系统稳定性。
通过这种方式,你可以在分布式系统中安全、高效地实现资源互斥访问,避免数据竞争问题。