一:Redisson——完成分布式锁
1.简介
Redisson 是架设在 Redis 基础上的一个 Java 驻内存数据网格(In-Memory Data Grid)。充分 的利用了 Redis 键值数据库提供的一系列优势,基于 Java 实用工具包中常用接口,为使用者 提供了一系列具有分布式特性的常用工具类。使得原本作为协调单机多线程并发程序的工 具包获得了协调分布式多机多线程并发系统的能力,大大降低了设计和研发大规模分布式 系统的难度。同时结合各富特色的分布式服务,更进一步简化了分布式环境中程序相互之间 的协作。
官方文档:https://github.com/redisson/redisson/wiki/%E7%9B%AE%E5%BD%95
2.导入redisson依赖——整合redisson作为分布式锁的框架
<!--使用redisson作为分布式锁,分布式对象等功能框架-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.12.0</version>
</dependency>
3.配置redisson
1)新建MyRedissonConfig文件,配置redisson
package com.sysg.gulimail.product.config;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.io.IOException;
@Configuration
public class MyRedissonConfig {
/**
* 所有对redisson的使用都是通过RedissonClient对象
* @return
* @throws IOException
*/
@Bean(destroyMethod = "shutdown")
public RedissonClient redisson() throws IOException {
//1.创建配置
Config config = new Config();
config.useSingleServer().setAddress(redis://127.0.0.1:6379");
//2.根据config对象,创建出RedissonClient示例
RedissonClient redissonClient = Redisson.create(config);
return redissonClient;
}
}
- 创建配置
Config config = new Config();
- 使用单集群模式
config.useSingleServer().setAddress(127.0.0.1:6379");
- 根据config对象,创建出RedissonClient示例
RedissonClient redissonClient = Redisson.create(config);
redis://
安全连接
二:可重入锁——避免死锁问题
可重入锁:基于redis的redisson分布式。实现了java对象的lock接口。
1.代码实现
@ResponseBody
@GetMapping("/hello")
public String hello(){
//1.获取一把锁,只要锁名字一样,就是一把锁
RLock lock = redisson.getLock("my-lock");
//2.加锁
lock.lock();//阻塞式等待,一直等,直到拿到锁,才会向下执行
try {
System.out.println("加锁成功,执行业务带代码。。"+Thread.currentThread().getId());
Thread.sleep(30000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//3.解锁
System.out.println("释放锁。。。"+Thread.currentThread().getId());
lock.unlock();
}
return "hello";
}
- 获取一把锁:
RLock lock = redisson.getLock("my-lock");
只要锁名字一样,就是一把锁 - 加锁:
lock.lock();
阻塞式等待,一直等,直到拿到锁,才会向下执行 - 解锁:
lock.unlock();
redisson优势:——看门狗机制
1.解决了锁的自动续期,如果业务超长,运行期间会自动续上新的30s。不用担心锁会过期会自动被删除。
2.加锁的业务只要运行完成,不会自动续期,即使不手动解锁,默认30s以后会自动解锁。
2.使用lock方法加超时时间
lock.lock(10, TimeUnit.SECONDS);
Thread.sleep(30000);//业务执行时间
- 在锁到了以后,不会自动续期
- 自动解锁时间一定要大于业务的的执行时间
结论:
1.如果指定了超时时间,就发送给redis执行脚本,进行占锁,默认超时时间,就是指定的时间。
2.如果未指定超时时间,我们就使用【LockWatchdogTimeout看门狗】的默认时间。
3.只要占锁成功,就会启动定时任务,就会重新设置过期时间,新的时间就是看门狗的默认时间。
4.只要业务没有执行完,就会每隔三分之一看门狗时间,自动续期。
5.建议一般使用指定时间加锁,尽量可以将时间弄长一点。如果太长,就说明业务代码有问题。
三:读写锁(ReadWriteLock)
读写锁:分布式可重入读写锁允许同时有多个读锁和一个写锁处于加锁状态。读锁和写锁是成对存在。写锁控制了读锁,只要写锁存在,读锁就要等待
1.测试读写锁
@GetMapping("/write")
@ResponseBody
public String writeValue(){
RReadWriteLock readWriteLock = redisson.getReadWriteLock("rw-lock");
RLock writeLock = readWriteLock.writeLock();
String s = "";
try {
//1.改数据加写锁,读数据加读锁
writeLock.lock();
s = UUID.randomUUID().toString();
Thread.sleep(30000);
redisTemplate.opsForValue().set("writeValue",s);
} catch (Exception e) {
e.printStackTrace();
} finally {
writeLock.unlock();
}
return s;
}
@GetMapping("/read")
@ResponseBody
public String readValue(){
RReadWriteLock readWriteLock = redisson.getReadWriteLock("rw-lock");
RLock readLock = readWriteLock.readLock();
String readValue = "";
readLock.lock();
try {
readLock.lock();
readValue = redisTemplate.opsForValue().get("writeValue");
} catch (Exception e) {
e.printStackTrace();
} finally {
readLock.unlock();
}
return readValue;
}
2.读写锁特点
读锁:读锁是一个共享锁,大家都可以用
写锁:写锁是排他锁(互斥锁,独享锁),同一时间只能有一个存在
1)写锁+读锁
- 1.改数据加写锁,读数据加读锁
- 2.保证一定可以读取到最新数据,修改期间,写锁是排他锁(互斥锁,独享锁),同一时间只能有一个存在。读锁是一个共享锁,大家都可以用
- 3.写锁没释放,读锁必须等待
2)读锁+读锁
- 相当于无锁,并发读,只会在redis中记录好所有当前的读锁,并且同时加锁成功
3)写锁+写锁
- 阻塞方式
4)读锁+写锁
- 有读锁,写锁也需要等待。只要有写的存在,必须等待。
四:闭锁——等待其他所有线程执行完毕才会释放
举例:放假,锁门。必须等所有学生走了以后才可以锁门。
1.代码实现
/**
* 放假,锁门。必须等所有学生走了以后才可以锁门。
* @return
* @throws InterruptedException
*/
@GetMapping("/lockDoor")
@ResponseBody
public String lockDoor() throws InterruptedException{
RCountDownLatch door = redisson.getCountDownLatch("door");
door.trySetCount(5);
door.await(); //等待闭锁都完成
return "放假了。。。";
}
@GetMapping("/gogogo/{id}")
@ResponseBody
public String gogogo(@PathVariable("id") Long id){
RCountDownLatch door = redisson.getCountDownLatch("door");
door.countDown();//计数减一
return id + "班级的人都走了";
}
五:分布式的信号量(Semaphore)——用来实现分布式限流
举例:车库停车,三个车位,来一辆占用一个车位,走一辆释放一个车位,当车的时候,看车位够不够
1.代码实现
/**
* 车库停车,三个车位,来一辆占用一个车位,走一辆释放一个车位,当车的时候,看车位够不够
* @return
*/
@GetMapping("/park")
@ResponseBody
public String park() throws InterruptedException {
RSemaphore park = redisson.getSemaphore("name");
//相当于占一个车位
//park.acquire();//阻塞方法,获取成功才返回,否则一直调用
boolean b = park.tryAcquire();
if (b) {
//执行复杂业务
return "ok";
} else {
return "流量过大,请等待";
}
}
@GetMapping("/go")
@ResponseBody
public String go() throws InterruptedException {
RSemaphore park = redisson.getSemaphore("name");
//相当于释放一个车位
park.release();
return "ok";
}
- 可以用来实现分布式限流服务,获取到信号量就可以访问,获取不到就等待。
- tryAcquire能停就停,停不了就走,尝试获取
- acquire会一直等待,直到等到为止