前言
随着系统的更新迭代,我们日常使用的synchronized与Reentrantlock已经不满足分布式系统的使用。Java 的 synchronized或者Reentrantlock 锁只能保证在同一个 JVM 进程内的多线程并发安全性。但是在多线程并发的情况下,并不能保证线程安全。因为现在的业务大多都是集群部署,也就是多个 JVM 实例,所以 Java 内置的锁无法保证集群环境的多线程安全性。所以如果业务是集群部署,那就需要分布式锁来保证整合集群的线程安全。
介绍
Redission是一个基于Redis实现的Java分布式对象存储和缓存框架。它提供了丰富的分布式数据结构和服务。**例如:分布式锁、分布式队列、分布式Rate Limiter等。
分布式锁场景
- 互联网秒杀
- 抢优惠卷
- 接口幂等性校验
特点
- 互斥性是分布式锁的重要特点,在任意时刻,只有一个线程能够持有锁锁的超时时间;
- 一个线程在持锁期间挂掉了而没主动释放锁,此时通过超时时间来保证该线程在超时后可以释放锁,这样其他线程才可以继续获取锁;
- 加锁和解锁必须是由同一个线程来设置;
- Redis 是缓存型数据库,拥有很高的性能,因此加锁和释放锁开销较小,并且能够很轻易地实现分布式锁。
特性
- 高性能
Redission是基于Redis的,因此它继承了Redis的高性能和低延迟的特性。同时,它采用了Netty的NIO框架,能够并发地处理大量的请求,使得应用程序的响应速度得到了极大的提升。
- 易用性
Redission提供了丰富的API和方法,同时还提供了文档和示例,让开发者易于上手和使用。此外,它支持自动配置和灵活的配置方式,使得开发者可以根据自己的需求进行配置和调整。
- 可扩展性
Redission的分布式架构使得它支持水平扩展,可以将数据和请求分散到更多的节点上进行处理。这也使得它具备了更好的容错能力和可靠性。
Redisson实现分布式锁
1、导入依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.6.5</version>
</dependency>
2、初始化客户端
package com.ruoyi.merit.config;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author : DongJiYong
* @date : 2023-11-17 16:21
*/
@Configuration
public class RedissonConfig {
// @Value("${redisson.address}")
// private String address;
@Bean
public RedissonClient redisson(){
// 单机模式
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379").setDatabase(0);
return Redisson.create(config);
}
}
3、 代码实现
目前导入的本人逻辑代码的实现
@Resource
private RedissonClient redissonClient;
private ResultBody buildParamsAndSaveBatchByDb(List<MeritSzlsPdwFaultRuleConfig> meritSzlsPdwFaultRuleConfigList, String version, BigDecimal versionInteger, boolean flag) {
// 1.获取锁对象
RLock redissonLock = redissonClient.getLock("myLock");
try {
//5分钟之后锁失效
//尝试获取锁,如果锁已经被其他线程获取,则等待直到获取到锁或者超时(第一个参数 等待时间 第二个参数 锁过期时间 第三个 时间单位)
//注意:如果指定锁自动释放时间,不管业务有没有执行完,锁都不会自动延期,即没有 watch dog 机制。
boolean locked = redissonLock.tryLock(-1,5, TimeUnit.MINUTES);
// 从redis 中拿当前库存的值
if (locked) {
//业务处理
for (MeritSzlsPdwFaultRuleConfig szlsPdwFaultRuleConfig : meritSzlsPdwFaultRuleConfigList) {
szlsPdwFaultRuleConfig.setGzVersion(version.substring(0, 1) + versionInteger);
szlsPdwFaultRuleConfig.setAutoId(null);
}
//新增版本+0.1
flag = this.saveBatch(meritSzlsPdwFaultRuleConfigList);
//根据版本号 + 检测项 设置数据库的MODEL_UPDATE状态为0
// meritSzlsPdwFaultRuleConfigMapper.updateModelUpdate(meritSzlsPdwFaultRuleConfigList.get(0));
/**
* 调用python脚本并传入版本号
*/
// usePythonScript(meritSzlsPdwFaultRuleConfigList, redissonLock);
} else {
log.info("程序正在执行中,请稍后重试");
return ResultUtil.success("程序正在执行中,请稍后重试", "false");
}
} catch (Exception e) {
log.error("操作异常", e);
//报异常了需要关锁
redissonLock.unlock();
} finally {
// 校验数据库ModelUpdate字段是否是 1 or 2
// checkoutModelUpdateIsFinish(meritSzlsPdwFaultRuleConfigList, redissonLock);
//注意! 目前是将调用python的代码注释了,如果放开注释 则需要把下面的Thread.sleep(2000)注释掉
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//查询当前线程是否持有此锁定,如果还持有,则释放,如果未持有,则说明已被释放;
if (redissonLock.isLocked() && redissonLock.isHeldByCurrentThread()) {
redissonLock.unlock();
}
}
// 关闭 Redisson 客户端
return ResultUtil.success("操作完成", flag);
}
实现原理
使用步骤
分布式锁的使用分成以下 3 步:
-
获取锁:根据唯一的 key 去 redis 获取锁。
-
加锁:拿到锁后在指定的等待时间内不断尝试对其加锁,超过等待时间则加锁失败。
-
解锁:分成两种情形:
- 第一如果在加锁的时候指定了自动释放时间,那么在此时间范围内业务提前完成的话就在 finally 手动释放锁,而如果业务没有完成也会自动释放锁,所以指定自动释放时间需要做非常仔细的考量;
- 第二就是没有指定自动释放时间,由于 redisson 有 watch dog (看门狗)机制,watch dog 默认的 releaseTime 是 30s,给锁加上 30s 的自动释放时间,并且每隔 releaseTime / 3 即 10 s 去检查业务是否完成,如果没有完成重置 releaseTime 为 30 s, 即锁的续约,所以一个业务严重阻塞的话会造成系统资源的极大浪费。到这里你应该能够明白分布式锁是没有完美的解决方案的。