本文主要是介绍基于SpringBoot实现分布式锁的三种方法,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
《基于SpringBoot实现分布式锁的三种方法》这篇文章主要为大家详细介绍了基于SpringBoot实现分布式锁的三种方法,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下...
我来详细讲解Spring Boot中实现分布式锁的几种方式,包括手写Redis锁和使用Redisson框架。
一、基于Redis原生命令实现分布式锁
1. 基础版Redis分布式锁
@Component
public class RedisDistributedLock {
@Autowired
private StringRedisTemplate redisTemplate;
private static final String LOCK_PREFIX = "distributed:lock:";
private static final long DEFAULT_EXPIRE_TIME = 30000; // 30秒
private static final long DEFAULT_WAIT_TIME = 10000; // 10秒
private static final long DEFAULT_SLEEP_TIME = 100; // 100ms
/**
* 尝试获取分布式锁(简单版)
* @param lockKey 锁的key
* @param value 锁的值(通常用UUID)
* @param expireTime 锁的过期时间(ms)
* @return 是否获取成功
*/
public boolean tryLock(String lockKey, String value, long expireTime) {
String key = LOCK_PREFIX + lockKey;
// 使用SET命令,通过NX参数实现"不存在时设置",EX参数设置过期时间
Boolean result = redisTemplate.opsForValue()
.setIfAbsent(key, value, expireTime, TimeUnit.MILLISECONDS);
return Boolean.TRUE.equals(result);
}
/**
* 释放锁
* 需要确保是自己加的锁才能释放(防止释放别人的锁)
*/
public boolean releaseLock(String lockKey, String value) {
String key = LOCK_PREFIX + lockKey;
String currentValue = redisTemplate.opsForValue().get(key);
// 通过Lua脚本确保原子性:判断值是否匹配,匹配则删除
String luaScript =
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
" return redis.call('del', KEYS[1]) " +
"else " +
" return 0 " +
"end";
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
redisScript.setScriptText(luaScript);
redisScript.setResultType(Long.class);
Long result = redisTemplate.execute(
redisScript,
Collections.singletonList(key),
value
);
return result != null && result == 1;
}
/**
* 获取锁(支持重试)
*/
public boolean lockWithRetry(String lockKey, String value,
long expireTime, long waitTime) {
long endTime = System.currentTimeMillis() + waitTime;
while (System.currentTimeMillis() < endTime) {
if (tryLock(lockKey, value, expireTime)) {
return true;
}
try {
Thread.sleep(DEFAULT_SLEEP_TIME);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
return false;
}
}
2. 可重入锁实现
@Component
public class RedisReentrantLock {
@Autowired
private StringRedisTemplate redisTemplate;
private static final String LOCK_PREFIX = "reentrant:lock:";
private static final ThreadLocal<Map<String, Integer>> LOCK_COUNT =
ThreadLocal.withInitial(HashMap::new);
/**
* 可重入锁实现
*/
public boolean tryReentrantLock(String lockKey, String clientId,
long expireTime) {
String key = LOCK_PREFIX + lockKey;
Map<String, Integer> lockCountMap = LOCK_COUNT.get();
int count = lockCountMap.getOrDefault(lockKey, 0);
// 重入:当前线程已持有锁
if (count > 0) {
lockCountMap.put(lockKey, count + 1);
return true;
}
// 尝试获取锁
String luaScript =
"if redis.call('exists', KEYS[1]) == 0 then " +
" redis.call('hset', KEYS[1], 'owner', ARGV[1]) " +
" redis.call('hset', KEYS[1], 'count', 1) " +
" redis.call('pexpire', KEYS[1], ARGV[2]) " +
" return 1 " +
"elseif redis.call('hget', KEYS[1], 'owner') == ARGV[1] then " +
" local count = redis.call('hget', KEYS[1], 'count') " +
" redis.call('hset', KEYS[1], 'count', tonumber(count) + 1) " +
" redis.call('pexpire', KEYS[1], ARGV[2]) " +
" return 1 " +
"else " +
" return 0 " +
"end";
DefaultRedisScript<Long> script = new DefaultRedisScript<>();
script.setScriptText(luaScript);
script.setResultType(Long.class);
Long result = redisTemplate.execute(
script,
Collections.singletonList(key),
clientId,
String.valueOf(expireTime)
);
if (result != null && result == 1) {
lockCountMap.put(lockKey, 1);
return true;
}
return false;
}
/**
* 释放可重入锁
*/
public boolean releaseReentrantLock(String lockKey, String clientId) {
String key = LOCK_PREFIX + lockKey;
Map<String, Integer> lockCountMap = LOCK_COUNT.get();
int count = lockCountMap.getOrDefault(lockKey, 0);
if (count <= 0) {
return false;
}
if (count > 1) {
// 重入次数减1
lockCountMap.put(lockKey, count - 1);
return true;
}
// 最后一次重入,释放锁
String luaScript =
"if redis.call('hget', KEYS[1], 'owner') == ARGV[1] then " +
" local current = redis.call('hget', KEYS[1], 'count') " +
" if tonumber(current) > 1 then " +
" redis.call('hset', KEYS[1], 'count', tonumber(current) - 1) " +
" redis.call('pexpire', KEYS[1], ARGV[2]) " +
" return 0 " +
" else " +
" redis.call('del', KEYS[1]) " +
" return 1 " +
" end " +
"else " +
" return -1 " +
"end";
DefaultRedisScript<Long> script = new DefaultRedisScript<>();
script.setScriptText(luaScript);
script.setResultType(Long.class);
Long result = redisTemplate.execute(
script,
Collections.singletonList(key),
clientId,
"30000"
);
if (result != null && result == 1) {
lockCountMap.remove(lockKey);
if (lockCountMap.isEmpty()) {
LOCK_COUNT.remove();
}
return true;
}
return false;
}
}
二、使用Redisson实现分布式锁(推荐生产环境使用)
1. 添加依赖
<!-- pom.XML --> <dependency> <groupId>org.redisson</groupId> <artifactId>redisson-spring-boot-starter</artifactId> <version>3.23.5</version> </dependency>
2. 配置Redisson
# application.yml
spring:
redis:
host: localhost
port: 6379
database: 0
timeout: 3000
redisson:
config: |
singleServerConfig:
address: "redis://${spring.redis.host}:${spring.redis.port}"
database: ${spring.redis.database}
connectionPoolSize: 64
connectionMinimumIdleSize: 24
idleConnectionTimeout: 10000
connectTimeout: ${spring.redis.timeout}
timeout: ${spring.redis.timeout}
retryAttempts: 3
rChina编程etryInterval: 1500
3. Redisson分布式锁服务
import org.redisson.api.RLock; import org.redisson.api.RedissonClient; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import Java.util.concurrent.TimeUnit; @Component public class RedissonDistributedLock { @Autowired private RedissonClient redissonClient; /** * 可重入锁(最常用) */ public boolean tryReentrantLock(String lockKey, long waitTime, long leaseTime, TimeUnit unit) { RLock lock = redissonClient.getLock(lockKey); try { return lock.tryLock(waitTime, leaseTime, unit); } catch (InterruptedException e) { Thread.currentThread().interrupt(); return false; } } /** * 公平锁 */ public boolean tryFairLock(String lockKey, long waitTime, long leaseTime, TimeUnit unit) { RLock lock = redissonClient.getFairLock(lockKey); try { return lock.tryLock(waitTime, leaseTime, unit); } catch (InterruptedException e) { Thread.currentThread().interrupt(); return false; } } /** * 联锁(多个锁同时获取) *android/ public boolean tryMultiLock(String[] lockKeys, long waitTime, long leaseTime, TimeUnit unit) { RLock[] locks = new RLock[lockKeys.length]; for (int i = 0; i < lockKeys.length; i++) { locks[i] = redissonClient.getLock(lockKeys[i]); } RLock multiLock = redissonClient.getMultiLock(locks); try { return multiLock.tryLock(waitTime, leaseTime, unit); } catch (InterruptedException e) { Thread.currentThread().interrupt(); return false; } } /** * 红锁(RedLock,多个Redis实例) */ public boolean tryRedLock(String lockKey, long waitTime, long leaseTime, TimeUnit unit) { RLock lock = redissonClient.getLock(lockKey); try { return lock.tryLock(waitTime, leaseTime, unit); } catch (InterruptedException e) { Thread.currentThread().interrupt(); return false; } } /** * 释放锁 */ public void unlock(String lockKey) { RLock lock = redissonClient.getLock(lockKey); if (lock.isLocked() && lock.isHeldByCurrentThread()) { lock.unlock(); } } /** * 强制释放锁 */ public void forceUnlock(String lockKey) { RLock lock = redissonClient.getLock(lockKey); lock.forceUnlock(); } /** * 自动续期的锁(看门狗机制) */ public void lockWithWatchdog(String lockKey) { RLock lock = redissonClient.getLock(lockKey); lock.lock(); // 默认30秒,看门狗会自动续期 } }
4. 使用AOP简化分布式锁使用
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DistributedLock {
/** 锁的key,支持SpEL表达式 */
String key();
/** 锁类型,默认可重入锁 */
LockType lockType() default LockType.REENTRANT;
/** 等待时间(秒) */
long waitTime() default 5;
/** 持有时间(秒),-1表示使用看门狗自动续期 */
long leaseTime() default -1;
/** 时间单位 */
TimeUnit timeUnit() default TimeUnit.SECONDS;
enum LockType {
REENTRANT, // 可重入锁
FAIR, // 公平锁
READ, // 读锁
WRITE, // 写锁
MULTI, // 联锁
RED // 红锁
}
}
@ASPect @Component @Slf4j public class DistributedLockAspect { @Autowired private RedissonClient redissonClient; @Autowired private RedissonDistributedLock redissonDistributedLock; @Around("@annotation(distributedLock)") public Object around(ProceedingJoinPoint joinPoint, DistributedLock distributedLock) throws Throwable { String lockKey = parseKey(distributedLock.key(), joinPoint); RLock lock = getLock(lockKey, distributedLock.lockType()); boolean locked = false; try { // 尝试获取锁 if (distributedLock.leaseTime() == -1) { // 使用看门狗自动续期 lock.lock(); } else { locked = lock.tryLock( distributedLock.waitTime(), distributedLock.leaseTime(), distributedLock.timeUnit() ); } if (!locked && distributedLock.leaseTime() != -1) { throw new RuntimeException("获取分布式锁失败: " + lockKey); } log.info("获取分布式锁成功: {}", lockKey); return joinPoint.proceed(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException("获取分布式锁被中断", e); } finally { if (lock.isLocked() && lock.isHeldByCurrentThread()) { lock.unlock(); log.info("释放分布式锁: {}", lockKey); } } } private String parseKey(String keySpEL, ProceedingJoinPoint joinPoint) { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); // 解析SpEL表达式 if (keySpEL.startsWith("#")) { ExpressionParser parser = new SpelExpressionParser(); Expression expression = parser.parseExpression(keySpEL); EvaluationContext context = new StandardEvaluationContext(); context.setVariable("methodName", method.getName()); // 设置参数 Object[] args = joinPoint.getArgs(); Parameter[] parameters = method.getParameters(); for (int i = 0; i < parameters.length; i++) { context.setVariable(parameters[i].getName(), args[i]); } return expression.getValue(context, String.class); } return keySpEL; } private RLock getLock(String lockKey, DistributedLock.LockType lockType) { switch (lockType) { case FAIR: return redissonClient.getFairLock(lockKey); case READ: return redissonClient.getReadwriteLock(lockKey).readLock(); case WRITE: return redissonClient.getReadWriteLock(lockKey).writeLock(); case MULTI: case RED: // 简化处理,实际使用需要多个实例 return redissonClient.getLock(lockKey); case REENTRANT: default: return redissonClient.getLock(lockKey); } } }
三、使用Spring Integration实现分布式锁
1. 添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-integration</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-redis</artifactId>
</dependency>
2. 配置Redis锁注册表
@Configuration
public class RedisLockConfiguration {
@Bean
public RedisLockRegistry redisLockRegistry(RedisConnectionFactory connectionFactory) {
// 过期时间10秒
return new RedisLockRegistry(connectionFactory, "distributed-lock", 10000L);
}
@Bean
public LockRegistry lockRegistry(RedisLockRegistry redisLockRegistry) {
return redisLockRegistry;
}
}
3. 使用Spring Integration锁
@Service
@Slf4j
public class OrderService {
@Autowired
private LockRegistry lockRegistry;
public void createOrder(String orderId) {
Lock lock = lockRegistry.obtain("order:" + orderId);
boolean lockedpython = false;
try {
// 尝试获取锁,最多等待5秒
locked = lock.tryLock(5, TimeUnit.SECONDS);
if (!locked) {
throw new RuntimeException("系统繁忙,请稍后重试");
}
// 执行业务逻辑
processOrder(orderId);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("订单处理被中断", e);
} finally {
if (locked) {
lock.unlock();
}
}
}
private void processOrder(String orderId) {
// 订单处理逻辑
log.info("处理订单: {}", orderId);
}
}
四、实际应用示例
1. 商品秒杀场景
@Service
@Slf4j
public class SeckillService {
@Autowired
private RedissonClient redissonClient;
@Autowired
private StringRedisTemplate redisTemplate;
private static final String SECKILL_PREFIX = "seckill:product:";
private static final String LOCK_PREFIX = "seckill:lock:";
/**
* 秒杀下单(防超卖)
*/
public boolean seckillOrder(Long productId, Integer quantity, Long userId) {
String lockKey = LOCK_PREFIX + productId;
RLock lock = redissonClient.getLock(lockKey);
try {
// 尝试获取锁,等待100ms,持有锁3秒
if (!lock.tryLock(100, 3000, TimeUnit.MILLISECONDS)) {
throw new RuntimeException("抢购太火爆,请重试");
}
// 检查库存
String stockKey = SECKILL_PREFIX + productId + ":stock";
Integer stock = Integer.valueOf(
redisTemplate.opsForValue().get(stockKey)
);
if (stock == null || stock < quantity) {
throw new RuntimeException("库存不足");
}
// 扣减库存
Long newStock = redisTemplate.opsForValue().decrement(stockKey, quantity);
if (newStock < 0) {
// 库存不足,恢复库存
redisTemplate.opsForValue().increment(stockKey, quantity);
throw new RuntimeException("库存不足");
}
// 创建订单
createOrder(productId, quantity, userId);
return true;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("系统异常", e);
} finally {
if (lock.isLocked() && lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
/**
* 使用注解简化版
*/
@DistributedLock(
key = "'seckill:lock:' + #productId",
waitTime = 1,
leaseTime = 3,
timeUnit = TimeUnit.SECONDS
)
public boolean seckillOrderWithAnnotation(Long productId, Integer quantity, Long userId) {
// 业务逻辑,无需关心锁的获取和释放
String stockKey = SECKILL_PREFIX + productId + ":stock";
Integer stock = Integer.valueOf(
redisTemplate.opsForValue().get(stockKey)
);
if (stock == null || stock < quantity) {
throw new RuntimeException("库存不足");
}
Long newStock = redisTemplate.opsForValue().decrement(stockKey, quantity);
if (newStwww.chinasem.cnock < 0) {
redisTemplate.opsForValue().increment(stockKey, quantity);
throw new RuntimeException("库存不足");
}
createOrder(productId, quantity, userId);
return true;
}
private void createOrder(Long productId, Integer quantity, Long userId) {
// 创建订单逻辑
log.info("用户{}抢购商品{},数量{}", userId, productId, quantity);
}
}
2. 定时任务防重复执行
@Component
@Slf4j
public class ScheduledTasks {
@Autowired
private RedissonClient redissonClient;
/**
* 分布式定时任务,确保集群中只有一个实例执行
*/
@Scheduled(cron = "0 */5 * * * ?") // 每5分钟执行一次
public void syncDataTask() {
String lockKey = "task:sync:data";
RLock lock = redissonClient.getLock(lockKey);
// 不等待,获取不到锁直接返回
boolean locked = lock.tryLock();
if (!locked) {
log.info("其他节点正在执行数据同步任务");
return;
}
try {
log.info("开始执行数据同步任务");
// 执行业务逻辑
syncData();
log.info("数据同步任务完成");
} finally {
lock.unlock();
}
}
private void syncData() {
// 数据同步逻辑
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
五、配置和最佳实践
1. Redisson配置类
@Configuration
public class RedissonConfig {
@Value("${spring.redis.host}")
private String redisHost;
@Value("${spring.redis.port}")
private String redisPort;
@Value("${spring.redis.password:}")
private String password;
@Value("${spring.redis.database:0}")
private int database;
@Bean(destroyMethod = "shutdown")
public RedissonClient redissonClient() {
Config config = new Config();
// 单节点模式
config.useSingleServer()
.setAddress(String.format("redis://%s:%s", redisHost, redisPort))
.setDatabase(database)
.setPassword(StringUtils.hasText(password) ? password : null)
.setConnectionPoolSize(64)
.setConnectionMinimumIdleSize(10)
.setIdleConnectionTimeout(10000)
.setConnectTimeout(3000)
.setTimeout(3000)
.setRetryAttempts(3)
.setRetryInterval(1500)
.setPingConnectionInterval(30000)
.setKeepAlive(true);
// 锁配置
config.setLockWatchdogTimeout(30000L); // 看门狗超时时间
return Redisson.create(config);
}
}
2. 分布式锁工具类
@Component
@Slf4j
public class DistributedLockUtil {
@Autowired
private RedissonClient redissonClient;
/**
* 执行带锁的方法
*/
public <T> T executeWithLock(String lockKey, long waitTime, long leaseTime,
TimeUnit unit, Supplier<T> supplier) {
RLock lock = redissonClient.getLock(lockKey);
boolean locked = false;
try {
locked = lock.tryLock(waitTime, leaseTime, unit);
if (!locked) {
throw new DistributedLockException("获取分布式锁失败: " + lockKey);
}
return supplier.get();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new DistributedLockException("获取分布式锁被中断", e);
} finally {
if (locked && lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
/**
* 执行带锁的方法(无返回值)
*/
public void executeWithLock(String lockKey, long waitTime, long leaseTime,
TimeUnit unit, Runnable runnable) {
executeWithLock(lockKey, waitTime, leaseTime, unit, () -> {
runnable.run();
return null;
});
}
/**
* 执行带锁的方法(快速失败)
*/
public <T> Optional<T> tryExecuteWithLock(String lockKey, long waitTime,
long leaseTime, TimeUnit unit,
Supplier<T> supplier) {
try {
return Optional.ofNullable(
executeWithLock(lockKey, waitTime, leaseTime, unit, supplier)
);
} catch (DistributedLockException e) {
log.warn("获取锁失败,跳过执行: {}", lockKey);
return Optional.empty();
}
}
public static class DistributedLockException extends RuntimeException {
public DistributedLockException(String message) {
super(message);
}
public DistributedLockException(String message, Throwable cause) {
super(message, cause);
}
}
}
六、测试分布式锁
@SpringBootTest
@Slf4j
class DistributedLockTest {
@Autowired
private RedissonDistributedLock redissonDistributedLock;
@Autowired
private DistributedLockUtil distributedLockUtil;
private final AtomicInteger counter = new AtomicInteger(0);
@Test
void testConcurrentLock() throws InterruptedException {
int threadCount = 10;
String lockKey = "test:concurrent:lock";
CountDownLatch latch = new CountDownLatch(threadCount);
ExecutorService executor = Executors.newFixedThreadPool(threadCount);
for (int i = 0; i < threadCount; i++) {
executor.submit(() -> {
try {
boolean locked = redissonDistributedLock
.tryReentrantLock(lockKey, 2, 5, TimeUnit.SECONDS);
编程China编程 if (locked) {
try {
// 模拟业务处理
Thread.sleep(100);
int value = counter.incrementAndGet();
log.info("线程 {} 获取锁成功,计数: {}",
Thread.currentThread().getName(), value);
} finally {
redissonDistributedLock.unlock(lockKey);
}
} else {
log.warn("线程 {} 获取锁失败", Thread.currentThread().getName());
}
} catch (Exception e) {
log.error("线程执行异常", e);
} finally {
latch.countDown();
}
});
}
latch.await();
executor.shutdown();
assertEquals(threadCount, counter.get());
}
@Test
void testLockWithUtil() {
String lockKey = "test:util:lock";
String result = distributedLockUtil.executeWithLock(
lockKey,
2,
5,
TimeUnit.SECONDS,
() -> {
// 业务逻辑
return "success";
}
);
assertEquals("success", result);
}
}
总结与建议
1.选择方案
- 简单场景:使用Spring Integration的
RedisLockRegistry - 生产环境:推荐使用Redisson,功能最全,稳定性最好
- 特殊需求:需要精细控制时,可以使用原生Redis命令自定义
2.最佳实践
- 锁的key要有业务含义,如
order:create:{orderId} - 一定要设置合理的过期时间,防止死锁
- 释放锁时要检查是否当前线程持有
- 使用Lua脚本保证原子性
- 考虑锁的可重入性
- 生产环境使用Redis集群或哨兵模式
3.注意事项
- 避免锁粒度过大,影响并发性能
- 避免锁持有时间过长
- 实现锁的自动续期(看门狗机制)
- 考虑锁等待超时和快速失败
- 添加监控和告警机制
4.常见问题解决
- 锁提前过期:使用Redisson的看门狗机制
- 锁误删:每个锁设置唯一value,释放时验证
- 锁不可重入:使用Redisson或实现可重入逻辑
- Redis集群故障:使用RedLock算法(多个Redis实例)
这样实现的分布式锁既安全又可靠,可以满足大多数业务场景的需求。
以上就是基于SpringBoot实现分布式锁的三种方法的详细内容,更多关于SpringBoot分布式锁的资料请关注China编程(www.chinasem.cn)其它相关文章!
这篇关于基于SpringBoot实现分布式锁的三种方法的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!