1.Redisson简介
Distributed Locks with Redis | Docs
Redisson 是 Redis Java 客户端和实时数据平台。它提供了更方便、最简单的 Redis 工作方式。Redisson 对象提供了关注点分离,允许您专注于数据建模和应用程序逻辑。
1. Overview · redisson/redisson Wiki · GitHub
2.使用Redisson
现在虽然有 springboot 启动场景
但是为了学习,我们还是手动去注入所需要地对象,才知道里面,springboot到底帮我们装了哪些对象
1.导入依赖
<!-- 以后使用redisson作为所有分布式锁,分布式对象等功能框架 -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.12.0</version>
</dependency>
2.配置Redisson
根据官方文档 需要放入这个对象
package com.jmj.gulimall.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 {
Config config = new Config();
//用rediss://来启用ssl连接
config.useSingleServer().setAddress("redis://192.168.232.209:6379");
RedissonClient redissonClient = Redisson.create(config);
return redissonClient;
}
}
3.使用锁做个测试,最普通的分布式锁
@Autowired
private RedissonClient redisson;
private final String jmjLock = "jmjLock";
private int i = 0;
@GetMapping("/hello")
@ResponseBody
public String test() {
//1、获取一把锁,只要锁的名字一样,就是同一把锁
RLock lock = redisson.getLock(jmjLock);
try {
//2、加锁
lock.lock();
int i1 = i + 1;
i=i1;
} catch (Exception e) {
e.printStackTrace();
} finally {
//解锁
lock.unlock();
}
return "good";
}
@GetMapping("/result")
@ResponseBody
public String test1() {
return i+"";
}
原理就是,同一个Key用setnx创建一个值,值设置一个随机的数不重复,当前线程存入,然后业务执行完毕,释放锁的时候,就判断是否是同一把锁,是同一把锁就解锁,创建锁和释放锁都是原子性操作。
1.redission解决了的问题 使用看门狗
阻塞式等待,默认加的锁都是30s,如果时间不够会在业务时间会被续期
1.解决了锁的自动续期,如果业务超长,运行期间自动给所续上新的30s,不用担心业务时间长,锁自动过期被删掉
2.如果在业务期间,程序断电了,没有执行释放锁的操作,看门狗也就不自动续期了,锁超时过后,也会自动释放
while true 阻塞式 无限循环尝试获取锁
4.看门狗lock原理
lock.lock(10, TimeUnit.SECONDS);
// 1、10秒钟自动解锁 自动解锁时间一定要大于业务的执行时间。
// 2、问题 在锁时间到了以后,不会自动续期
// 1、10秒钟自动解锁 自动解锁时间一定要大于业务的执行时间。
// 2、问题 在锁时间到了以后,不会自动续期
// 3、 如果我们传递了锁的超时时间,就发送给redis执行脚本,进行占锁,默认超时就是我们指定的时间
// 4、 如果我们未指定了超时时间 , 就使用 30*1000 private long lockWatchdogTimeout = 30 * 1000;
// 只要占锁成功,就会启动一个定时任务[ 重新给锁设置过期时间,新的过期时间就是看门狗的默认时间],每个一个3分之1看门狗时间自动续期
// internalLockLeaseTime[看门狗时间] / 3 ,10s
最佳实战
// lock.lock(30, TimeUnit.SECONDS);一般用这个,手动解锁,不自动续期
因为不可能让一个业务执行30秒之久的,要不然业务就崩溃了。
5.读写锁测试
@Autowired
RedisTemplate redisTemplate;
@Autowired
private RedissonClient redisson;
@ResponseBody
@GetMapping("/write")
public String writeValue() {
RReadWriteLock readWriteLock = redisson.getReadWriteLock("rw-lock");
RLock writeLock = readWriteLock.writeLock();
String string = "";
try {
writeLock.lock(35,TimeUnit.SECONDS);
System.out.println("正在写数据...");
string = UUID.randomUUID().toString();
TimeUnit.SECONDS.sleep(30);
redisTemplate.opsForValue().set("writeValue", string);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println("释放写锁");
writeLock.unlock();
}
return string;
}
//保证一定能读到最新数据,修改期间,写锁是一个排他锁(互斥锁) 读锁是一个共享锁
//写锁没释放,读就会等待,读没释放,写就会等待
@ResponseBody
@GetMapping("/read")
public String readValue() {
RReadWriteLock readWriteLock = redisson.getReadWriteLock("rw-lock");
RLock readLock = readWriteLock.readLock();
String string = "";
try {
readLock.lock(30,TimeUnit.SECONDS);
System.out.println("正在读取数据...");
string = (String) redisTemplate.opsForValue().get("writeValue");
} catch (Exception e) {
e.printStackTrace();
}finally {
System.out.println("释放读锁");
readLock.unlock();
}
return string;
}
6.信号量
模拟车库停车
private static final String semaphoreLock = "park";
@GetMapping("/park")
@ResponseBody
public String park() throws InterruptedException {
//信号量 就是一个 队列的 锁
RSemaphore park = redisson.getSemaphore(semaphoreLock);
// park.acquire();//获取一个信号,获取一个值 占用一个车位 阻塞式
boolean b = park.tryAcquire(3, TimeUnit.SECONDS);//尝试十秒
boolean c = park.tryAcquire();//可以立马停
if (b){
Thread.sleep(10000);
return "acquire ok";
}else {
return "acquire fail";
}
}
@GetMapping("/go")
@ResponseBody
public String go() throws InterruptedException {
//信号量 就是一个 队列的 锁
RSemaphore park = redisson.getSemaphore(semaphoreLock);
park.release();//释放一个信号, 释放一个车位 *********** 阻塞式
return "release ok";
}
虽然分布式锁满,但是一致性强, 符合 AP 原则
信号量 业务场景 :
车库停车
信号量限流 分布式限流
7.闭锁(CountDownLatch)
基于Redisson的Redisson分布式闭锁(CountDownLatch) Java 对象 RCountDownLatch 采用了与 JUC CountDownLatch相似的接口和用法
闭锁,就是等待,例如十个线程全部做完了,倒计数为0 才算结束
/**
* 放假锁门
* 5个班全部走完,我们可以锁大门
*/
@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+"班的人2都走了...";
}
8.分布式锁的 缓存一致性解决
1.锁的名字 锁的粒度越细 越快
2. 约定: 具体缓存的是某个数据 11-号商品 : product-11-lock
缓存一致性
1.双写模式
2.失效模式
并发问题
都可以加锁解决
经常修改,不用缓存 ,直接数据库
使用Canal解决缓存一致性
我们系统的一致性解决方案:
1.缓存的所有数据都有过期时间,数据过期下一次查询触发主动更新
2.读写数据的时候,加上分布式的读写锁 适用于读多写少