✨这里是第七人格的博客✨小七,欢迎您的到来~✨
🍅系列专栏:中间件开发🍅
✈️本篇内容: 分布式锁组件✈️
🍱 本篇收录完整代码地址:https://gitee.com/diqirenge/seven-lock-starter 🍱
几年以前小七在面试的时候,和面试官探讨过一个重复造轮子的事情。比如今天的主角——分布式锁,市面上有很多成熟的解决方案,包括框架、组件等,其中Redisson关于锁的实现也非常的完美了。今天我们就基于Redisson封装一个我们自己的分布式锁starter组件。
公司关于分布式锁的实现五花八门,虽然都是基于redis实现的,但是有的项目用的Redisson,有的项目用的RedisTemplate,有的项目两个都在混用,
且使用方式都是直接调用的框架的API,有些小伙伴面对各种各样的分布式锁实现方式,一脸懵逼,不知道如何下手,甚至使用错误的情况也不在少数。
为了解决这个问题,我们计划统一技术栈,使用Redisson实现分布式锁,并提供一个基于Redisson实现的分布式锁的框架封装。
为了方便各个系统更好的接入新的分布式锁组件,我们计划如下:
1、基于Spring boot starter封装,接入方只需要引入依赖,替换调用方法即可
2、尽量少的配置改动,如果项目以前有使用Redisson,甚至不修改配置,也可以使用
兼容方案如图所示:
名称 | 版本 |
---|---|
JDK | 1.8 |
Redis | 7.2.0 |
redisson-spring-boot-starter | 3.15.4 |
spring-boot-configuration-processor | 2.3.5.RELEASE |
spring-boot-autoconfigure-processor | 2.3.5.RELEASE |
master
https://gitee.com/diqirenge/seven-lock-starter
1、添加Maven依赖
<dependencies>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.15.4</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<version>2.3.5.RELEASE</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure-processor</artifactId>
<version>2.3.5.RELEASE</version>
<optional>true</optional>
</dependency>
</dependencies>
2、新建一个类DistributedLockProperties,用于存储和获取分布式锁的超时时间配置。
/**
* <pre>
* 这个类是一个简单的Java Bean,主要用于存储和获取分布式锁的超时时间配置,也是给调用方一个修改默认加锁时间的机会。
* 通过@ConfigurationProperties注解,Spring Boot可以自动将配置文件(如application.properties或application.yml)中前缀为"com.run2code.starter"的属性绑定到这个类的字段上。
* 例如,如果在配置文件中有一个属性com.run2code.starter.lock-timeout=60,那么这个值就会自动设置到lockTimeout字段上。
* <pre/>
*
* 关注公众号【奔跑的码畜】,一起进步不迷路
*
* @author 第七人格
* @date 2024/06/17
*/
// 使用@ConfigurationProperties注解,这允许我们将配置文件中的属性绑定到这个类的字段上。
// prefix属性定义了配置文件中属性的前缀,即"com.run2code.starter"。
@ConfigurationProperties(
prefix = "com.run2code.starter"
)
public class DistributedLockProperties {
// 定义一个长整型字段lockTimeout,用来存储锁的超时时间,单位可能是秒或者其他时间单位,
// 默认值被设置为30L。
private long lockTimeout = 30L;
// 类的默认构造函数,没有参数,也没有执行任何操作。
public DistributedLockProperties() {
}
// 这是一个getter方法,用于获取lockTimeout字段的值。
public long getLockTimeout() {
return this.lockTimeout;
}
// 这是一个setter方法,用于设置lockTimeout字段的值。
// 方法接受一个长整型参数lockTimeout,并将其值赋给类的lockTimeout字段。
public void setLockTimeout(final long lockTimeout) {
this.lockTimeout = lockTimeout;
}
}
3、编写DistributedLockRedissonAutoConfiguration,该类继承了Redisson的相关配置,并且引入了上面我们自己的配置
/**
* <pre>
* 这个配置类的主要目的是自定义RedissonClient的配置,特别是锁的看门狗超时时间。
* 同时,它也确保了只有在Redisson.class和RedisOperations.class存在于类路径中,且RedissonAutoConfiguration已经完成配置后,这个配置类才会被加载和配置。
* <pre/>
*
* 关注公众号【奔跑的码畜】,一起进步不迷路
*
* @author 第七人格
* @date 2024/06/17
*/
// 使用@Configuration注解,表示这是一个Spring Boot的配置类,用于定义Bean和配置信息
@Configuration
// @ConditionalOnClass注解表示仅当类路径中存在Redisson.class和RedisOperations.class时,此配置才会生效
@ConditionalOnClass({Redisson.class, RedisOperations.class})
// @AutoConfigureAfter注解表示此配置类应在RedissonAutoConfiguration配置类加载并配置完成后才进行加载配置
@AutoConfigureAfter({RedissonAutoConfiguration.class})
// @EnableConfigurationProperties注解启用DistributedLockProperties类的配置属性绑定
@EnableConfigurationProperties({DistributedLockProperties.class})
// @ComponentScan注解用于扫描并加载指定包下的组件,这里指定扫描包含DistributedLock.class的包
@ComponentScan(
basePackageClasses = {DistributedLock.class}
)
public class DistributedLockRedissonAutoConfiguration {
// 定义一个私有变量distributedLockProperties,其类型为DistributedLockProperties
private final DistributedLockProperties distributedLockProperties;
// 使用@Primary注解表示当存在多个同类型的Bean时,优先使用该Bean
// 使用@Bean注解定义一个名为redissonClient的Bean,其类型为RedissonClient
// destroyMethod属性指定在Bean销毁时调用的方法,这里指定为"shutdown"
// @ConditionalOnBean注解表示仅当存在RedissonClient类型的Bean时,此Bean定义才会生效
@Primary
@Bean(
name = {"redissonClient"},
destroyMethod = "shutdown"
)
@ConditionalOnBean({RedissonClient.class})
public RedissonClient redissonClient(RedissonClient redissonClient) {
// 从传入的redissonClient中获取其配置
Config config = redissonClient.getConfig();
// 设置锁的看门狗超时时间,单位为毫秒,这里将distributedLockProperties中获取的锁超时时间(秒)转为毫秒
config.setLockWatchdogTimeout(this.distributedLockProperties.getLockTimeout() * 1000L);
// 关闭传入的redissonClient
redissonClient.shutdown();
// 使用更新后的配置创建一个新的RedissonClient实例并返回
return Redisson.create(config);
}
// 构造函数,接收一个DistributedLockProperties类型的参数,用于初始化distributedLockProperties变量
public DistributedLockRedissonAutoConfiguration(final DistributedLockProperties distributedLockProperties) {
this.distributedLockProperties = distributedLockProperties;
}
}
4、定义分布式锁接口
/**
* 分布式锁接口
* 关注公众号【奔跑的码畜】,一起进步不迷路
*
* @author 第七人格
* @date 2024/06/17
*/
public interface DistributedLock {
/**
* 根据key加锁,默认锁的过期时间是30秒
*
* @param key 加锁key
* @return boolean
*/
boolean lock(String key);
/**
* 根据key加锁,并且设置等待时间和锁的过期时间
*
* @param key 加锁key
* @param waitTime 等待时间
* @param leaseTime 过期时间
* @return boolean
*/
boolean lock(String key, int waitTime, int leaseTime);
/**
* 根据key释放锁
*
* @param key 加锁key
*/
void unlock(String key);
}
5、使用Redisson实现分布式锁接口
/**
* Redisson分布式锁实现
* 关注公众号【奔跑的码畜】,一起进步不迷路
*
* @author 第七人格
* @date 2024/06/17
*/
// 使用@Component注解,表明这是一个Spring组件,Spring会自动检测并实例化这个类
@Component
// 使用@ConditionalOnClass注解,表明这个组件的创建依赖于Redisson.class和RedisOperations.class是否存在于类路径中
@ConditionalOnClass({Redisson.class, RedisOperations.class})
public class RedissonDistributedLock implements DistributedLock {
// 创建一个Logger对象,用于记录日志,这个对象是针对RedissonDistributedLock类的
private static final Logger log = LoggerFactory.getLogger(RedissonDistributedLock.class);
// 定义一个RedissonClient类型的成员变量,这个变量将用于与Redis进行交互
private final RedissonClient redissonClient;
// 定义一个lock方法,接收一个String类型的key作为参数,尝试获取对应的锁,并立即返回获取结果
public boolean lock(String key) {
// 使用redissonClient获取对应的锁,并尝试加锁,如果加锁成功则返回true,否则返回false
return this.redissonClient.getLock(key).tryLock();
}
/**
* 接收key、等待时间和租约时间作为参数,尝试在指定的等待时间内获取锁,并在获取锁后设置指定的租约时间
*
* @param key 加锁key
* @param waitTime 等待时间
* @param leaseTime 过期时间
* @return boolean
*/
public boolean lock(String key, int waitTime, int leaseTime) {
try {
// 使用redissonClient获取对应的锁,尝试在指定的等待时间内加锁,并设置租约时间
// 如果在等待时间内成功获取锁,则返回true,否则返回false
return this.redissonClient.getLock(key).tryLock(waitTime, leaseTime, TimeUnit.SECONDS);
} catch (InterruptedException e) {
// 如果在等待过程中线程被中断,则记录错误信息,并返回false表示获取锁失败
log.error("获取锁失败 - {} - {}", new Object[]{key, waitTime, e});
return false;
}
}
/**
* 接收一个String类型的key作为参数,用于释放对应的锁
*
* @param key 加锁key
*/
public void unlock(String key) {
try {
// 使用redissonClient获取对应的锁,并释放它
this.redissonClient.getLock(key).unlock();
} catch (Exception e) {
// 如果在释放锁的过程中出现异常,则抛出一个DistributedLockException异常,将原始的异常作为其原因
throw new DistributedLockException(key, e);
}
}
// 构造函数,接收一个RedissonClient对象作为参数,并将其赋值给成员变量redissonClient
public RedissonDistributedLock(final RedissonClient redissonClient) {
this.redissonClient = redissonClient;
}
}
6、自定义分布式锁异常类
/**
* <pre>
* 这段代码定义了一个自定义的运行时异常类DistributedLockException,它扩展了Java标准库中的RuntimeException类。
* 这个自定义异常类主要用于处理与分布式锁相关的异常情况。
* 通过提供一个带有错误信息和原始异常原因的构造方法,它使得在抛出异常时能够提供更详细的上下文信息,便于问题的定位和解决。
* <pre/>
* 关注公众号【奔跑的码畜】,一起进步不迷路
*
* @author 第七人格
* @date 2024/06/17
*/
// 声明一个名为DistributedLockException的公开类,它继承自RuntimeException
// RuntimeException是Java中表示运行时异常的一个类,通常用于程序逻辑错误或其他异常情况
public class DistributedLockException extends RuntimeException {
// 定义一个构造方法,接收两个参数:一个字符串类型的message和一个Throwable类型的cause
// message通常用于提供关于异常的描述信息
// cause表示引发当前异常的原始异常,允许将异常链传递下去,便于后续的错误追踪和分析
public DistributedLockException(String message, Throwable cause) {
// 调用父类RuntimeException的构造方法,将message和cause传递给父类的构造器
// 这样做是为了初始化RuntimeException对象,同时保留了原始异常的堆栈跟踪信息
super(message, cause);
}
}
7、在spring.factories中指明组件自动配置类
# 在Spring Boot项目中,spring.factories文件是一个特殊的配置文件,用于配置Spring Boot的自动配置和其他Spring Boot特性。
# 这行代码的意义在于,它告诉Spring Boot在启动过程中自动加载并应用DistributedLockRedissonAutoConfiguration这个配置类。
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.run2code.starter.lock.config.DistributedLockRedissonAutoConfiguration
8、执行Maven打包/上传命令
1、引入依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.5.RELEASE</version>
<relativePath/>
</parent>
<dependencies>
<dependency>
<groupId>com.run2code.starter</groupId>
<artifactId>seven-lock-starter</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
2、加入配置
spring.redis.host=localhost
#spring.redis.password=
spring.redis.port=16379
spring.redis.database=5
# com.run2code.starter.lock-timeout=10
3、编写测试用的Controller
@RestController
public class UserController {
private Logger logger = LoggerFactory.getLogger(UserController.class);
@Resource
private DistributedLock distributedLock;
@GetMapping(path = "/query")
public String queryUserInfo(@RequestParam String userId) {
logger.info("查询用户信息,userId:{}", userId);
boolean hasLocked = distributedLock.lock("lockKey");
if (!hasLocked) {
// 没有获得锁的处理
System.out.println("请求过于频繁,请稍候重试");
return "请求过于频繁,请稍候重试";
}
try {
// 获得锁后的操作
Thread.sleep(5000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
distributedLock.unlock("lockKey");
}
System.out.println("查询用户信息成功");
return userId;
}
}
4、编写启动类
@SpringBootApplication
public class ApplicationLock {
public static void main(String[] args) {
SpringApplication.run(ApplicationLock.class, args);
}
}
5、测试
启动项目后先请求http://localhost:8080/query?userId=123,5秒内再次请求http://localhost:8080/query?userId=123,返回
请求过于频繁,请稍候重试
本文通过详细的步骤和代码示例,展示了如何从零开始实现一个基于Redisson的分布式锁组件,并提供了封装和测试方案。读者可以直接用于生产。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。