基本原理
synchronized是利用JVM内部的锁监视器控制线程,但是只能在一个JVM中生效。如果有多个JVM的时候,就会有多个线程获取到锁,就无法实现多JVM进程之间的互斥了。
因此不能使用JVM内部的锁监视器了,必须使用JVM外部的锁监视器,就能保证只有一个线程获取到锁,就能实现多进程之间的互斥了。
分布式锁是什么
是满足分布式系统或集群模式下多进程可见且互斥的锁。分布式锁可以是悲观锁或乐观锁,具体取决于实现方式。悲观锁是通过独占资源的方式实现的,悲观锁的具体实现如Redis分布式锁、MySQL行锁和表锁。
分布式锁的基本特性
1、多进程可见。
2、互斥(只能被一个线程拿到)。
3、高可用(大多数情况下线程来获取锁都是成功的)。
4、高并发(加锁之后,线程变成串行执行了,性能会下降,所以获取锁的动作要很快)。
5、安全性(避免死锁)。
不同实现方式对比
MySQL支持主从复制;Redis支持主从复制、集群;
基于Redis的分布式锁
基于setnx ex实现
存在许多缺点:
1、不可重入:同一线程无法多次获取同一把锁。
2、不可重试:获取锁没有重试机制。
3、超时释放:锁超时时间长短存在矛盾。
4、主从同步延迟(极端情况):获取锁是SET操作(写操作),主节点写入后未同步给从结点,就可能出现多个线程拿到锁的情况。
Redisson框架实现
Redisson是在Redis的基础上实现的分布式工具的集合。提供了分布式系统下各种各样的工具,包括分布式锁。
推荐自己配置Redisson,不要使用Redisson与SpringBoot的整合方式。
Redisson的使用
1、引入redisson依赖
2、配置Redis(配置文件或配置类)
@Configuration
public class RedisConfig {
@Bean
public RedissonClient redissonClient() {
//配置类
Config config = new Config();
//添加redis地址,也可以使用config.useclusterServers()添加集群地址
config.useSingleServer().setAddress
("redis://192.168.150.101:6379").setPassword("123321");
//创建客户端
return Redisson.create(config);
}
}
3、使用Redisson的分布式锁
@Resource
private RedissonClient redissonClient;
@Test
void testRedisson() throws InterruptedException {
//1、创建锁对象(可重入),指定锁的名称
RLock lock = redissonclient.getLock("lock:order:" + id);
//2、尝试获取锁,参数为:
//重试获取锁的最大等待时间(期间会重试获取锁),1秒结束,返回false
//锁超时释放时间(避免宕机造成死锁)
//时间单位
boolean isLock = lock.tryLock(1, 10, TimeUnit.SECONDS);
//判断尝试获取锁是否成功
if(!isLock) {
//获取锁失败
return Result.fail("不允许重复下单");
}
try {
System.out.println("执行业务");
} finally {
//3、释放锁
lock.unlock();
}
}
并发测试
可以使用Fiddler,Jmeter等软件发送请求。
Redisson原理
Redisson可重入锁原理
解决setnx ex锁的不可重入缺点。
在一个线程里连续多次获取锁,这就是锁的重入。所以锁的数据结构不能使用String(简单的key-value),需要使用能在一个key中存储两个数据的数据结构。Redisson使用Hash存储锁,key记录锁名,field记录线程标识,value记录锁的重入次数(与JDK中ReentrantLock原理相同)。
释放锁的时候将重入次数-1,直至所有的业务都执行完,最外层的方法结束释放锁时才能删除锁。因为尝试获取锁和释放锁是成对出现的,所以当value为0时就能判断出最外层的方法已经释放锁,就可以把锁删除。(代码需要lua脚本实现)
Redisson的锁重试和WatchDog机制
解决setnx ex锁的不可重试和超时释放时间长短矛盾的缺点。
/**
* waitTime 等待时间
* leaseTime 超时释放时间
* unit 时间单位
*/
boolean tryLock(long waitTime, long leaseTime, TimeUnit unit);
重试机制
基于信号量和PubSub发布订阅(等待、唤醒,不占用CPU)
线程获取锁失败之后,进行等待,等待释放锁的消息(PubSub机制),得到消息后,去重新尝试获取锁。
线程获取锁成功,在释放锁的时候会发布消息。
超时释放机制
基于WatchDog机制,延续锁的剩余时间。
线程获取锁成功之后,开启一个定时任务,该任务每隔一段时间重置锁的超时时间(EXPIRE),确保锁在非服务宕机的情况下不会因为自动超时而释放。
Redisson的主从同步multiLock联锁原理
解决setnx ex锁的主从同步延迟的缺点。
主从节点中的所有节点都变成独立的Redis结点,都可以读写,此时获取锁的方式改变为依次向所有独立的Redis节点获取锁,即使一个节点宕机,仍然还有其他节点存活,节点越多可用性越高。
也可以给上述Redis节点建立主从关系,进一步提高可用性。一个主节点宕机,且未完成主从同步的情况下,其他线程也是不能获取到锁的。
该方案称为multiLock(联锁)。
使用
为每个节点(Redisson客户端)都新建一个配置。
@Bean
public RedissonClient redissonClient2() {
//配置
Config config = new Config();
config.useSingleServer().setAddress
("redis://192.168.150.101:6380");
//创建客户端
return Redisson.create(config);
}
@Bean
public RedissonClient redissonClient3() {
//配置
Config config = new Config();
config.useSingleServer().setAddress
("redis://192.168.150.101:6381");
//创建客户端
return Redisson.create(config);
}
//在需要使用的地方注入多个RedissonClient
@Resource
private RedissonClient redissonClient;
@Resource
private RedissonClient redissonClient2;
@Resource
private RedissonClient redissonClient3;
private RLock lock;
@BeforeEach
void setUp() {
RLock lock1 = redissonclient.getLock("order");
RLock lock2 = redissonclient2.getLock("order");
RLock lock3 = redissonclient3.getLock("order");
//创建联锁multiLock
lock = redissonclient.getMultiLock(lockl, lock2, lock3);
}
void func() {
boolean isLock = lock.tryLock();
}
总结
三类Redis分布式锁:
1、不可重入的Redis分布式锁
2、可重入的Redis分布式锁
3、Redisson的multiLock联锁
标签:Redisson,Redis,获取,线程,config,分布式 From: https://www.cnblogs.com/fallorange/p/17749622.html