简介:
redisson 是架设在 redis 基础上的一个 java 驻内存数据网格(in-memory data grid)。充分 的利用了 redis 键值数据库提供的一系列优势,基于 java 实用工具包中常用接口,为使用者 提供了一系列具有分布式特性的常用工具类。
使得原本作为协调单机多线程并发程序的工 具包获得了协调分布式多机多线程并发系统的能力,大大降低了设计和研发大规模分布式 系统的难度。同时结合各富特色的分布式服务,更进一步简化了分布式环境中程序相互之间的协作。
一.简单使用
1、导入依赖
<!--使用redisson作为分布式锁-->
<dependency>
<groupid>org.redisson</groupid>
<artifactid>redisson</artifactid>
<version>3.16.8</version>
</dependency>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;
@configuration
public class myredissonconfig {
/**
* 所有对redisson的使用都是通过redissonclient对象
* @return
*/
@bean(destroymethod = "shutdown")
public redissonclient redissonclient(){
// 创建配置 指定redis地址及节点信息
config config = new config();
config.usesingleserver().setaddress("xxx.xx.xx.x(redis地址):端口").setpassword("xxxxxxxxx");
// 根据config创建出redissonclient实例
redissonclient redissonclient = redisson.create(config);
return redissonclient;
}
}3、测试
@autowired
redissonclient redissonclient;
@test
void redisson(){
system.out.println(redissonclient);
}
运行测试代码发现控制台有异常,报错信息为 :
java.lang.illegalargumentexception: redis url should start with redis:// or rediss:// (for ssl connection)
提示我们要在地址前加上redis:// ,ssl连接则需要加上rediss://
所以需要改为setaddress("redis://xxx.xx.xx.x:6379")即可
打印结果:
org.redisson.redisson@6159fb3c
二.分布式锁
1、可重入锁
基于redis的redisson分布式可重入锁rlock java对象实现了java.util.concurrent.locks.lock接口。同时还提供了异步(async)、反射式(reactive)和rxjava2标准的接口。
常用代码:
rlock lock = redisson.getlock("anylock");
// 最常见的使用方法
lock.lock();测试代码:
@responsebody
@getmapping("/hello")
public string hello() {
// 1.获取一把锁,只要锁的名字一样,就是同一把锁
rlock lock = redisson.getlock("my-lock");
lock.lock(); // 阻塞式等待。默认加的锁是30s时间
try {
// 1、锁的自动续期,运行期间自动给锁续上新的30s,无需担心业务时间长,锁过期会自动被释放
// 2、加锁的业务只要运行完成,就不会给当前锁续期,即使不手动释放锁,锁默认在30s后自动释放,避免死锁
system.out.println("加锁成功,执行业务代码..."+thread.currentthread().getid());
thread.sleep(30000);
} catch (exception e) {
e.printstacktrace();
}finally {
system.out.println("释放锁..."+thread.currentthread().getid());
lock.unlock();
}
return "hello!";
}
结果:
加锁成功,执行业务代码...99
释放锁...99
可以发现,当我们的业务超长时,运行期间,redisson会为我们自动续期锁,业务执行完将不会续期,即使不手动释放锁,锁也会默认在30s后释放。
2、看门狗机制
redisson 中提供的续期机制
开一个监听线程,如果方法还没执行完,就帮你重置 redis 锁的过期时间。
原理:
- 启动定时任务重新给锁设置过期时间,默认过期时间是 30 秒,每 10 秒(看门狗默认事件的1/3)续期一次(补到 30 秒)
- 如果线程挂掉(注意 debug 模式也会被它当成服务器宕机),则不会续期
- 只有lock.lock(); 会有看门狗机制;
- lock.lock(10,,timeunit.seconds);手动设置过期时间的话,则不会有看门狗机制(推荐)
3、读写锁
一次只有一个线程可以占有写模式的读写锁, 但是可以有多个线程同时占有读模式的读写锁.(该数据加写锁、读数据加读锁)
- 当读写锁是写加锁状态时, 在这个锁被解锁之前, 所有试图对这个锁加锁的线程都会被阻塞.
- 当读写锁在读加锁状态时, 所有试图以读模式对它进行加锁的线程都可以得到访问权, 但是如果线程希望以写模式对此锁进行加锁, 它必须直到所有的线程释放锁.
测试读写锁代码:
@autowired
redissonclient redisson;
@autowired
redistemplate redistemplate;
@responsebody
@getmapping("/write")
public string writevalue(){
rreadwritelock lock = redisson.getreadwritelock("rw-lock");
rlock rlock = lock.writelock();
string s = "";
try {
s = uuid.randomuuid().tostring();
// 模拟业务时间
thread.sleep(30000);
} catch (exception e){
}finally {
rlock.unlock();
}
redistemplate.opsforvalue().set("writevalue",s);
return s;
}
@getmapping(value = "/read")
@responsebody
public string readvalue() {
string s = "";
rreadwritelock readwritelock = redisson.getreadwritelock("rw-lock");
//加读锁
rlock rlock = readwritelock.readlock();
try {
rlock.lock();
s = (string) redistemplate.opsforvalue().get("writevalue");
timeunit.seconds.sleep(10);
} catch (exception e) {
e.printstacktrace();
} finally {
rlock.unlock();
}
return s;
}
当我们访问 localhost:10000/write写入数据时,因为线程睡眠了30s(模拟业务),此时我们访问 localhost:10000/read 将会一直阻塞,等待写锁释放,读锁才能占锁从而获取执行业务。
4、信号量
信号量为存储在redis中的一个数字,当这个数字大于0时,即可以调用acquire()方法增加数量,也可以调用release()方法减少数量,但是当调用release()之后小于0的话方法就会阻塞,直到数字大于0。
可以应用于秒杀、限流等操作。
简单应用:
@getmapping(value = "/park")
@responsebody
public string park() {
rsemaphore park = redisson.getsemaphore("park");
try {
park.acquire();// 获取一个信号量(redis中信号量值-1),如果redis中信号量为0了,则在这里阻塞住,直到信号量大于0,可以拿到信号量,才会继续执行。
} catch (interruptedexception e) {
e.printstacktrace();
}
return "ok";
}
@getmapping(value = "/go")
@responsebody
public string go() {
rsemaphore park = redisson.getsemaphore("park");
park.release(); //释放一个信号量(redis中信号量值+1)
return "ok";
}其中:redisson.getsemaphore("park").acquire() 当信号量为0时将会一直阻塞,直到信号量大于0,才会继续执行。但redisson.getsemaphore("park").tryacquire() 将不会阻塞,能拿到信号量就返回true,否则返回false,lock.trylock() 同理。
5、闭锁
在要完成某些运算时,只有其它线程的运算全部运行完毕,当前运算才继续下去。
模拟场景:
学校放假,学校门卫锁门必须等待所有班级全部离开,才将学校大门锁住。
@getmapping(value = "/lockdoor")
@responsebody
public string lockdoor() throws interruptedexception {
rcountdownlatch lockdoor = redisson.getcountdownlatch("lockdoor");
lockdoor.trysetcount(5); // 设置计数为5
lockdoor.await(); //等待闭锁完成
return "放假啦...";
}
@getmapping(value = "/go/{id}")
public string go(@pathvariable("id") integer id) {
rcountdownlatch lockdoor = redisson.getcountdownlatch("lockdoor");
lockdoor.countdown(); // 计数减1
return id+"班都走光了";
}结果:我们先访问 /lockdoor 线程将会阻塞,连续访问五次 /go/1 ,输出 放假了。
三.缓存数据一致性解决方案




尽量给锁加上过期时间;对于读写状态时,应该加上分布式读写锁。
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。
发表评论