在 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 "系统繁忙,请稍后重试";
}
}
六、生产环境注意事项
- 集群模式配置:生产环境建议使用 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:简化锁的使用,避免代码重复。
- 选择合适的锁类型:普通锁、读写锁、公平锁等。
- 异常处理与监控:保证系统稳定性。
通过这种方式,你可以在分布式系统中安全、高效地实现资源互斥访问,避免数据竞争问题。