一、Redisson简介和整合
1、简介
Redisson 是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid)。它不仅提供了一系列的分布式的Java常用对象,还提供了许多分布式服务。其中包括(BitSet, Set, Multimap, SortedSet, Map, List, Queue, BlockingQueue, Deque, BlockingDeque, Semaphore, Lock, AtomicLong, CountDownLatch, Publish / Subscribe, Bloom filter, Remote service, Spring cache, Executor service, Live Object service, Scheduler service) Redisson提供了使用Redis的最简单和最便捷的方法。Redisson的宗旨是促进使用者对Redis的关注分离(Separation of Concern),从而让使用者能够将精力更集中地放在处理业务逻辑上。
2、整合
1、引入依赖
gulimall-product/pom.xml
<!-- 以后使用redisson 作为所有分布式锁,分布式对象等框架 -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.12.0</version>
</dependency>
文档:第三方框架整合
为了学习Redisson 原理,我们先手动创建Redisson配置文件,以后项目中可以引用已经封装好的 spring-boot-redisson-starter
来快速安装。
2、增加redisson配置文件
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;
/**
* @author: kaiyi
* @create: 2020-09-01 14:10
*/
@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://192.168.10.10:6379");
//2、根据Config创建出RedissonClient实例
//Redis url should start with redis:// or rediss://
RedissonClient redissonClient = Redisson.create(config);
return redissonClient;
}
}
二、Redisson-lock锁测试
@Controller
public class TestCOntroller {
@Autowired
private RedissonClient redissonClient;
@GetMapping("hello")
@ResponseBody
public String hello() throws Exception {
//1、获取一把锁,只要锁的名字一样,就是同一把锁
RLock mylock = redissonClient.getLock("mylock");
//2、加锁
mylock.lock(); //阻塞式等待。默认加的锁都是30s
//1)、锁的自动续期,如果业务超长,运行期间自动锁上新的30s。不用担心业务时间长,锁自动过期被删掉
//2)、加锁的业务只要运行完成,就不会给当前锁续期,即使不手动解锁,锁默认会在30s内自动过期,不会产生死锁问题
**// mylock.lock(10, TimeUnit.SECONDS); //10秒钟自动解锁,自动解锁时间一定要大于业务执行时间
//过期了并不是死锁,而是下一个线程可以抢占了**
System.out.println("加锁成功"+Thread.currentThread().getId());
Thread.sleep(5000);
System.out.println("释放锁"+Thread.currentThread().getId());
//3、解锁 假设解锁代码没有运行,Redisson会不会出现死锁
mylock.unlock();
return "ok";
}
}
mylock.lock(10, TimeUnit.SECONDS); //10秒钟自动解锁,自动解锁时间一定要大于业务执行时间
//过期了并不是死锁,而是代表下一个线程可以抢占了
一般我们都设置30秒,不让他续期,一般都会给它设置过期时间,不指定会自动续期
//问题:在锁时间到了以后,不会自动续期
//1、如果我们传递了锁的超时时间,就发送给redis执行脚本,进行占锁,默认超时就是 我们制定的时间
//2、如果我们指定锁的超时时间,就使用 lockWatchdogTimeout = 30 * 1000 【看门狗默认时间】
//只要占锁成功,就会启动一个定时任务【重新给锁设置过期时间,新的过期时间就是看门狗的默认时间】,每隔10秒都会自动的再次续期,续成30秒
官方文档:Redisson 分布式锁和同步器
三、Redisson读写锁
加读写锁的目的:保证一定能读到最新数据,修改期间,写锁是一个排它锁(互斥锁、独享锁)(写锁是**阻塞的,读锁不阻塞,是一个共享锁,)
- 写锁没释放读锁必须等待
- 读 + 读 :相当于无锁,并发读,只会在Redis中记录好,所有当前的读锁。他们都会同时加锁成功
- 写 + 读 :必须等待写锁释放
- 写 + 写 :阻塞方式
- 读 + 写 :有读锁。写也需要等待
- 只要有读或者写的存都必须等待**
* 读 + 读 :相当于无锁,并发读,只会在Redis中记录好,所有当前的读锁。他们都会同时加锁成功
@GetMapping("read")
@ResponseBody
public String read() throws Exception {
//读写锁
RReadWriteLock read = redissonClient.getReadWriteLock("read");
//读锁加锁
RLock rLock = read.readLock();
//加锁
rLock.lock();
System.out.println("读锁加锁成功"+Thread.currentThread().getId());
//读
String weritvalue = (String) redisTemplate.opsForValue().get("weritvalue");
Thread.sleep(5000);
System.out.println("读锁释放锁"+Thread.currentThread().getId());
rLock.unlock();
//返回
return weritvalue;
}
* 写 + 读 :必须等待写锁释放
@GetMapping("write")
@ResponseBody
public String write() throws Exception {
//读写锁
RReadWriteLock write = redissonClient.getReadWriteLock("write");
//写锁
RLock rLock = write.writeLock();
//加锁
rLock.lock();
System.out.println("写锁加锁成功"+Thread.currentThread().getId());
//向redi存数据
redisTemplate.opsForValue().set("weritvalue", UUID.randomUUID().toString());
Thread.sleep(5000);
System.out.println("写锁释放锁"+Thread.currentThread().getId());
//3、解锁 假设解锁代码没有运行,Redisson会不会出现死锁
rLock.unlock();
return "ok";
}
@GetMapping("read")
@ResponseBody
public String read() throws Exception {
//读写锁
RReadWriteLock read = redissonClient.getReadWriteLock("read");
//读锁加锁
RLock rLock = read.readLock();
//加锁
rLock.lock();
System.out.println("读锁加锁成功"+Thread.currentThread().getId());
//读
String weritvalue = (String) redisTemplate.opsForValue().get("weritvalue");
Thread.sleep(5000);
System.out.println("读锁释放锁"+Thread.currentThread().getId());
rLock.unlock();
//返回
return weritvalue;
}
剩下的自己试一下吧我就不演示了
** 写 + 写 :阻塞方式
* 读 + 写 :有读锁。写也需要等待*
总结* 只要有读或者写的存都必须等待
项目实战
public class BrandServiceImpl extends ServiceImpl<BrandDao, BrandEntity> implements BrandService {
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private Redisson redisson;
//读写锁的方式实现
@Autowired
private RedissonClient redissonClient;
@Override
public PageUtils queryPage(Map<String, Object> params) {
//获取读写锁
RReadWriteLock threelog = redissonClient.getReadWriteLock("threelog");
//首先把读写锁拿到
//读锁
RLock read = threelog.readLock();
//写锁
RLock write = threelog.writeLock();
//加读锁
read.lock();//用看门狗机制 我这里就演示三级树的代码
//先去缓存查
IPage<BrandEntity> than = (IPage<BrandEntity>) redisTemplate.opsForValue().get("than");
//释放锁
read.unlock();
//判断是否为空
if (than!=null){
System.out.println("缓存命中");
return new PageUtils(than);
}else {
System.out.println("没有去数据库查");
//加写锁 因为写锁是阻塞的
write.lock();
//思考一个问题 假如三个同时三个来抢到数据库 走到这个,其他两个等待
//剩下两个还在等待,如不理解看下图
// 再次判断一下判断是否为空
if (than!=null){
System.out.println("写锁缓存命中");
return new PageUtils(than);
}
IPage<BrandEntity> page = this.page(
new Query<BrandEntity>().getPage(params),
new QueryWrapper<BrandEntity>()
); //解决穿透问题
if (page==null||page.equals("")){
//缓存穿透短暂缓存null值(由于这是演示,这个键我就先放a站位)
redisTemplate.opsForValue().set("a",page,2, TimeUnit.MINUTES);
}//解决雪崩问题
Random random = new Random();
int i = random.nextInt(300);
redisTemplate.opsForValue().set("than",page,1+i,TimeUnit.MINUTES);
write.unlock();
return new PageUtils(page);
}
//思考一个问题 假如三个同时三个来抢到数据库 走到这个,其他两个等待,等待获取写锁
//剩下两个还在等待,如不理解看下图
// 再次判断一下判断是否为空
信号量
/**
* 车库停车
* 3车位
* 信号量也可以做分布式限流
*/
@GetMapping(value = "/park")
@ResponseBody
public String park() throws InterruptedException {
RSemaphore park = redissonClient.getSemaphore("park");
park.acquire(); //获取一个信号、获取一个值,占一个车位
boolean flag = park.tryAcquire();//尝试获取,能获取就获取获取不到就获取不到
if (flag) {
//执行业务
} else {
return "error";
}
return "ok=>" + flag;
}
@GetMapping(value = "/go")
@ResponseBody
public String go() {
RSemaphore park = redissonClient.getSemaphore("park");
park.release(); //释放一个车位
return "ok";
}
可以自己测一下我就不演示效果了
分布式闭锁
/**
* 放假、锁门
* 1班没人了
* 5个班,全部走完,我们才可以锁大门
* 分布式闭锁
*/
@GetMapping(value = "/lockDoor")
@ResponseBody
public String lockDoor() throws InterruptedException {
RCountDownLatch door = redisson.getCountDownLatch("door");
door.trySetCount(5);
door.await(); //等待闭锁完成
return "放假了...";
}
@GetMapping(value = "/gogogo/{id}")
@ResponseBody
public String gogogo(@PathVariable("id") Long id) {
RCountDownLatch door = redisson.getCountDownLatch("door");
door.countDown(); //计数-1
return id + "班的人都走了...";
}
部分借鉴来自链接
面试题
------------等待
有空再补