在互联网大厂的后端开发领域,Spring Boot3 框架广泛应用于构建高效的应用程序。而随着业务规模的不断扩大,分布式系统的复杂性日益增加,如何确保多个服务实例对共享资源的安全、有序访问,成为了亟待解决的关键问题。分布式锁,作为应对这一挑战的有力手段,发挥着至关重要的作用。在众多实现分布式锁的技术方案中,借助 Redis 来实现具有显著优势,因其出色的性能和丰富的数据结构,成为了广大后端开发者的热门选择。今天,我们就深入探讨一下在 Spring Boot3 项目里,如何巧妙运用 Redis 实现分布式锁。
背景介绍
在单机应用的时代,我们使用线程锁就能轻松控制并发访问。可如今,分布式系统大行其道,多个应用实例运行在不同服务器上,传统线程锁就 “有心无力” 了。分布式锁应运而生,它的使命就是管控分布式系统里多个节点对共享资源的访问,保证同一时刻只有一个节点能拿到锁并执行相关操作,从而维护数据一致性和操作原子性。而 Redis,凭借其出色的性能和丰富的数据结构,成为实现分布式锁的绝佳选择。在众多互联网大厂的后端开发中,有相当高比例的项目会用到 Spring Boot3 框架,同时,超过半数的项目会借助 Redis 来实现分布式锁。这足以看出 Spring Boot3 和 Redis 在分布式锁实现上的 “黄金搭档” 地位。
解决方案
基于 Redisson 框架实现
引入依赖
在项目的 pom.xml 文件里,添加 Redisson 的依赖。就像这样
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.18.0</version>
</dependency>
配置 Redis 连接
在 application.yml 文件中,配置 Redis 的连接信息。假设你的 Redis 运行在本地,端口是 6379,配置如下:
spring:
data:
redis:
database: 0
host: 127.0.0.1
port: 6379
password: 123456 # 如果没有密码,可去掉这一行
创建 RedisLockService 类
创建一个 RedisLockService 类,专门用来管理分布式锁的获取和释放。代码如下:
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class RedisLockService {
@Autowired
private RedissonClient redissonClient;
/**
* 获取锁的方法
* @param lockKey 锁键
* @param waitTime 等待时间
* @param leaseTime 租约时间
* @return 是否获取到锁
*/
public boolean acquireLock(String lockKey, long waitTime, long leaseTime) {
// 获取指定锁键对应的Redisson锁
RLock lock = redissonClient.getLock(lockKey);
try {
// 尝试以指定等待时间和租约时间获取锁
return lock.tryLock(waitTime, leaseTime, TimeUnit.SECONDS);
} catch (InterruptedException e) {
// 当前线程中断
Thread.currentThread().interrupt();
// 返回获取锁失败
return false;
}
}
/**
* 释放锁的方法
* @param lockKey 锁键
*/
public void releaseLock(String lockKey) {
// 获取指定锁键对应的Redisson锁
RLock lock = redissonClient.getLock(lockKey);
// 如果当前线程持有该锁
if (lock.isHeldByCurrentThread()) {
// 释放锁
lock.unlock();
}
}
}
创建控制器测试
创建一个控制器,用来测试分布式锁的实际效果。代码如下:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class LockController {
@Autowired
private RedisLockService redisLockService;
@GetMapping("/doSomething")
public String doSomething() {
String lockKey = "myLock";
boolean isLockAcquired = redisLockService.acquireLock(lockKey, 5, 10);
if (isLockAcquired) {
try {
System.out.println("获取到锁!");
// 在这里执行你的业务逻辑
Thread.sleep(5000); // 模拟业务处理时间
return "业务处理完成";
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return "业务处理失败";
} finally {
redisLockService.releaseLock(lockKey);
System.out.println("锁已释放!");
}
} else {
System.out.println("未能获取到锁!");
return "业务处理失败";
}
}
}
基于 Redis Lua 脚本实现
创建工具类
创建一个 RedisLuaUtils 工具类,把加锁和解锁的逻辑封装起来。代码如下:
package com.modules.redis.utils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import java.time.Duration;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
@Slf4j
public class RedisLuaUtils {
// 超时时间(毫秒)
private static final long TIMEOUT_MILLIS = 15000;
// 重试次数
private static final int RETRY_TIMES = 10;
// 睡眠时间(重试间隔)
private static final long SLEEP_MILLIS = 100;
// 用来加锁的lua脚本
private static final String LOCK_LUA = "if redis.call(\"setnx\",KEYS(1),ARGV(1)) == 1 " +
"then " +
" return redis.call('expire',KEYS(1),ARGV(2)) " +
"else " +
" return 0 " +
"end";
// 用来释放分布式锁的lua脚本
private static final String UNLOCK_LUA = "if redis.call(\"get\",KEYS(1)) == ARGV(1) " +
"then " +
" local number = redis.call(\"del\", KEYS(1)) " +
" return tostring(number) " +
"else " +
" return tostring(0) " +
"end ";
// 检查redisKey是否上锁(没加锁返回加锁)
public static Boolean isLock(String redisKey, String value, RedisTemplate<Object, Object> template) {
return lock(redisKey, value, template, RETRY_TIMES);
}
private static Boolean lock(String redisKey, String value, RedisTemplate<Object, Object> template, int retryTimes) {
boolean result = lockKey(redisKey, value, template);
// 循环等待上一个用户锁释放,或者锁超时释放
while (!(result) && retryTimes-- > 0) {
try {
log.debug("lock failed, retrying...{}", retryTimes);
Thread.sleep(RedisLuaUtils.SLEEP_MILLIS);
} catch (InterruptedException e) {
return false;
}
result = lockKey(redisKey, value, template);
}
return result;
}
// 加锁
private static Boolean lockKey(String redisKey, String value, RedisTemplate<Object, Object> template) {
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(LOCK_LUA, Long.class);
List<String> keys = new CopyOnWriteArrayList<>();
keys.add(redisKey);
List<String> args = new CopyOnWriteArrayList<>();
args.add(value);
args.add(String.valueOf(TIMEOUT_MILLIS / 1000));
Long result = template.execute(redisScript, keys, args);
return result!= null && result == 1;
}
// 解锁
public static void unlock(String redisKey, String value, RedisTemplate<Object, Object> template) {
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(UNLOCK_LUA, Long.class);
List<String> keys = new CopyOnWriteArrayList<>();
keys.add(redisKey);
List<String> args = new CopyOnWriteArrayList<>();
args.add(value);
template.execute(redisScript, keys, args);
}
}
使用工具类加锁解锁
在需要使用分布式锁的地方,注入 RedisTemplate,调用 RedisLuaUtils 工具类的方法来加锁和解锁。代码如下:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
@Component
public class SomeService {
@Autowired
private RedisTemplate<Object, Object> redisTemplate;
public void doSomething() {
String lockKey = "my_lock_key";
String value = "unique_value";
boolean locked = RedisLuaUtils.isLock(lockKey, value, redisTemplate);
if (locked) {
try {
// 获取锁成功后执行需要加锁的代码块
System.out.println("成功获取到锁,开始执行任务...");
// 模拟执行任务
Thread.sleep(10000);
System.out.println("任务执行完成!");
} catch (InterruptedException e) {
// 处理线程中断异常
Thread.currentThread().interrupt();
} finally {
// 释放锁
RedisLuaUtils.unlock(lockKey, value, redisTemplate);
System.out.println("释放锁!");
}
} else {
// 获取锁失败,处理失败逻辑
System.out.println("获取锁失败!");
}
}
}
总结
通过上述两种方式,我们能够在 Spring Boot3 项目中顺利地利用 Redis 实现分布式锁。Redisson 框架提供了强大且易用的 API,极大地简化了分布式锁的实现过程;而基于 Redis Lua 脚本实现则更灵活,能满足一些对性能和原子性有极致要求的场景。各位后端开发的小伙伴们,赶紧在你们的项目中实践起来吧!要是在实现过程中有任何疑问或者新的见解,欢迎在评论区留言分享,咱们一起交流探讨,共同攻克技术难题,让我们的项目在分布式锁的保障下更加稳定、高效地运行。