分布式架构-Redisson 框架介绍使用
一、Redisson
Redisson
是架设在Redis
基础上的一个Java
驻内存数据网格。在基于NIO
的Netty
框架上,充分的利用了Redis
键值数据库提供的一系列优势,在Java
实用工具包中常用接口的基础上,为使用者提供了一系列具有分布式特性的常用工具类。使得原本作为协调单机多线程并发程序的工具包获得了协调分布式多机多线程并发系统的能力,大大降低了设计和研发大规模分布式系统的难度。同时结合各富特色的分布式服务,更进一步简化了分布式环境中程序相互之间的协作。
Redisson
提供了使用Redis
的最简单和最便捷的方法。Redisson
的宗旨是促进使用者对Redis
的关注分离(Separation of Concern
),从而让使用者能够将精力更集中地放在处理业务逻辑上。如果您现在正在使用其他的Redis
的Java
客户端,希望Redis
命令和Redisson
对象匹配列表能够帮助您轻松的将现有代码迁徙到Redisson
框架里来。如果Redis
的应用场景还仅限于作为缓存使用,您也可以将Redisson
轻松的整合到像Spring
和Hibernate
这样的常用框架里。除此外您也可以间接的通过Java
缓存标准规范JCache API (JSR-107)
接口来使用Redisson
。
Redisson
生而具有的高性能,分布式特性和丰富的结构等特点恰巧与Tomcat
这类服务程序对会话管理器(Session Manager
)的要求相吻合。利用这样的特点,Redisson
专门为Tomcat
提供了会话管理器(Tomcat Session Manager
)。
二、Redisson 和Jedis性能对比
Redisson
是吞吐量和延迟敏感系统的完美伴侣。比Jedis
更有效的方式利用可用的系统资源。
下面的链接可以清晰的展示在并发量增加的时候,Redisson
和Jedis
的性能变化。
https://dzone.com/articles/redisson-pro-vs-jedis-which-is-faster
三、基本使用
1. 引入
pom依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.11.1</version>
</dependency>
RedissonClient
注入 Spring
@Configuration
public class RedissonConfig {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private String port;
@Value("${spring.redis.password}")
private String password;
@Value("${spring.redis.database}")
private int database;
@Bean
public RedissonClient getRedisson() {
Config config = new Config();
//线程定时间隔时间
// config.setLockWatchdogTimeout(100000000);
//设置单机版本redis
config.useSingleServer().setAddress("redis://" + host + ":" + port).setPassword(password).setDatabase(database);
//设置集群的方式
// config.useClusterServers().addNodeAddress("redis://" + host + ":" + port);
//添加主从配置
// config.useMasterSlaveServers().setMasterAddress("").setPassword("").addSlaveAddress(new String[]{"",""});
return Redisson.create(config);
}
}
或者在现有基础上,增加 RedissonClient
:
spring:
redis:
timeout: 6000
password:
cluster:
max-redirects:
nodes:
- 192.168.0.1:7001
- 192.168.0.2:7002
- 192.168.0.3:7003
@Configuration
public class RedissonConfig {
@Bean
public RedissonClient getRedisson(RedisProperties redisProperties) {
Config config = new Config();
String[] nodes = redisProperties.getCluster().getNodes().stream().filter(StringUtils::isNotBlank).map(node -> "redis://" + node).collect(Collectors.toList()).toArray(new String[]{});
ClusterServersConfig clusterServersConfig = config.useClusterServers().addNodeAddress(nodes);
if (StringUtils.isNotBlank(redisProperties.getPassword())) {
clusterServersConfig.setPassword(redisProperties.getPassword());
}
clusterServersConfig.setConnectTimeout((int) (redisProperties.getTimeout().getSeconds() * 1000));
clusterServersConfig.setScanInterval(2000);
return Redisson.create(config);
}
}
2. Key-Value形式
//添加数据 string
RBucket<String> bucket = redissonClient.getBucket("abc");
bucket.set("asd");
//异步添加数据
bucket.setAsync("asd");
bucket.setAsync("asdf");
//读取数据
RBucket<String> bucket1 = redissonClient.getBucket("abc");
String o = bucket1.get();
System.out.println(o);
//异步读取数据
RFuture<String> async = bucket1.getAsync();
System.out.println(async.get());
3. hash
RMap<String, Object> rMap = redissonClient.getMap("testmap");
//清空
rMap.clear();
//添加,返回之前的值
Object o = rMap.put("abc", "asd");
Object o1 = rMap.putIfAbsent("abc", "ghh");
//移除key
rMap.remove("abc");
//添加并返回之前关联过的值
Object o2 = rMap.putIfAbsent("aa", "tem");
//添加数据,如果已经有key 返回false
boolean third = rMap.fastPut("bb", "tem");
boolean third1 = rMap.fastPut("bb", "tem");
System.out.println(third);
System.out.println(third1);
//异步添加数据,如果已经有key 返回false
RFuture<Boolean> booleanRFuture = rMap.fastPutAsync("cc", "tem");
System.out.println(booleanRFuture.get());
RFuture<Boolean> booleanRFuture1 = rMap.fastPutAsync("cc", "tem");
System.out.println(booleanRFuture1.get());
// 异步移除key
rMap.fastRemoveAsync("cc");
//遍历集合
for(String key :rMap.keySet()){
System.out.println(key+":"+rMap.get(key));
}
4. Set / Zset
//获取不排序的set集合
RSet<Object> set = redissonClient.getSet("");
//获取排序的set集合
RSortedSet<String> rSortedSet = redissonClient.getSortedSet("listtest");
//清空集合
rSortedSet.clear();
//追加数据
boolean abc = rSortedSet.add("abc");
System.out.println(abc);
//异步追加数据
RFuture<Boolean> bb = rSortedSet.addAsync("bb");
System.out.println(bb.get());
//删除数据
boolean abc1 = rSortedSet.remove("abc");
System.out.println(abc1);
System.out.println(Arrays.toString(rSortedSet.toArray()));
5. list
RList<String> listtest = redissonClient.getList("listtest");
listtest.clear();
listtest.add("abc");
listtest.add("asd");
boolean asd = listtest.remove("asd");
System.out.println(asd);
listtest.fastRemove(0);
System.out.println(listtest.toString());
System.out.println(listtest.size());
6. 队列模式 先进先出List
RQueue<String> rQueue = redissonClient.getQueue("testqueue");
rQueue.clear();
rQueue.add("abc1");
rQueue.add("abc2");
rQueue.add("abc3");
rQueue.add("abc4");
//获取队列第一个元素
String a = rQueue.peek();
System.out.println("--"+a);
String b = rQueue.element();
System.out.println("--"+b);
//获取队列第一个元素 并移除队列
String c = rQueue.poll();
System.out.println(c);
String d = rQueue.remove();
System.out.println(d);
System.out.println(rQueue.toString());
7. 双端队列,对头和队尾都可添加或者移除,也是先进先出List
RDeque<String> deque = redissonClient.getDeque("deque");
deque.addFirst("aa");
deque.addLast("bb");
8. 原子类
RAtomicLong abc = redissonClient.getAtomicLong("abclong");
long andAdd = abc.getAndAdd(1L);
System.out.println(andAdd);
RAtomicDouble atomicDouble = redisson.getAtomicDouble("double");
atomicDouble.set(1.0);
atomicDouble.addAndGet(2.0);
atomicDouble.get();
9. 订阅
RTopic qwe = redissonClient.getTopic("qwe");
qwe.addListener(String.class, new MessageListener<String>() {
@Override
public void onMessage(CharSequence charSequence, String s) {
System.out.println("message-->"+s);
}
});
qwe.publish("acs1");
qwe.publish("acs2");
qwe.publish("acs3");
10.分布式锁
Redisson
分布式锁过程如下:
- 获取锁:
多个jvm
使用lua
脚本在redis
中setnx
写入一个相同的key
,谁能够写成功谁就获取锁成功。 如果写入key
成功,会单独开启一个看门狗的线程(续命定时任务线程) 默认的情况下每隔10s
时间不断续命延迟,key
默认是30s
有效期。 - 释放锁
调用lua
脚本,修改重入的次数,如果重入次数小于0
的情况下,直接将该key
删除。
RLock lock = null;
try {
// 获取可重入锁
lock = redissonClient.getLock("redislock");
lock.lock();
Thread.sleep(30000000);
} catch (Exception e) {
} finally {
if (lock != null) {
lock.unlock();
}
}
设置持有锁过期时间
lock.lock(5, TimeUnit.SECONDS);
尝试获取锁等待时间,持有锁最大时间
boolean b = lock.tryLock(5,6, TimeUnit.SECONDS);
示例:
@GetMapping("/GetTest3")
public String GetTest3() throws InterruptedException {
System.out.println(">>>>>>>>>>>GetTest3");
RLock lock = redissonClient.getLock("redislock");
try{
while (true){
boolean b = lock.tryLock(5,6, TimeUnit.SECONDS);
if (b){
break;
}
System.out.println(">>>>>>>>>>>GetTest3 >>>>>>尝试");
}
}catch (Exception e){
e.printStackTrace();
}
System.out.println(">>>>>>>>>>>GetTest3 >>>>>>开始执行");
Thread.sleep(5000);
System.out.println(">>>>>>>>>>>GetTest3 >>>>>>执行结束");
lock.unlock();
return "success";
}
11、RedLock
Jvm01
连接到主的redis
做setnx
操作的时候,异步将数据同步给从redis
;意味着jvm01
获取锁成功,正好在这时候主redis
宕机了,redis
集群自动开启哨兵机制选举,就会选举剩余从节点中某个redis
为主redis
,就会导致两个jvm
获取锁成功,违背分布式锁原子特征。
而红锁机制就需要至少三个以上Redis
独立节点,这些节点相互之间可以不需要存在主从之分,每个Redis
保证独立即可。
- 客户端会在每个
redis
实例创建锁,只需要满足一半的Redis
节点能够获取锁成功,就表示加锁成功。 - 客户端使用相同的
key
,在从所有的Redis
节点获取锁; - 客户端需要设置超时时间,连接
redis
设置不成功的情况下立即切换到下一个Redis
实例,防止一直阻塞; - 客户端需要计算获取锁的总耗时,客户端至少要有
N/2+1
节点获取锁成功
且总耗时时间小于锁的过期时间才能获取锁成功。 - 如果客户端最终获取锁失败,必须所有节点释放锁。
RLock lock1 = redissonClient1.getLock("lock1");
RLock lock2 = redissonClient2.getLock("lock2");
RLock lock3 = redissonClient3.getLock("lock3");
RedissonRedLock lock = new RedissonRedLock(lock1, lock2, lock3);
try {
//获取锁
boolean res = lock.tryLock(10, 10, TimeUnit.SECONDS);
} catch (Exception e) {
} finally {
if (lock != null) {
// 释放锁
lock.unlock();
}
}
12、布隆过滤器
RBloomFilter<String> bloomFilter = redissonClient.getBloomFilter("phoneList");
//初始化布隆过滤器:预计元素为100000000L,误差率为3%
bloomFilter.tryInit(100000000L,0.03);
//将号码10086插入到布隆过滤器中
bloomFilter.add("10086");
//判断下面号码是否在布隆过滤器中
System.out.println(bloomFilter.contains("123456"));//false
System.out.println(bloomFilter.contains("10086"));//true
13、ReadWriteLock
RReadWriteLock rwlock = redisson.getLock("lock");
//尝试获取锁
rwlock.readLock().lock();
rwlock.writeLock().lock();
rwlock.readLock().lock(10, TimeUnit.SECONDS);
rwlock.writeLock().lock(10, TimeUnit.SECONDS);
boolean res = rwlock.readLock().tryLock(100, 10, TimeUnit.SECONDS);
boolean res = rwlock.writeLock().tryLock(100, 10, TimeUnit.SECONDS);
// 释放锁
lock.unlock();
14、Semaphore
RSemaphore semaphore = redisson.getSemaphore("sem");
// 获取信号量
semaphore.acquire();
semaphore.acquire(23);
semaphore.tryAcquire();
semaphore.tryAcquire(23, TimeUnit.SECONDS);
//释放信号量
semaphore.release(10);
semaphore.release();
15、CountDownLatch
RCountDownLatch latch = redisson.getCountDownLatch("cdl");
latch.trySetCount(1);
latch.countDown();
latch.await();