分布式架构-Redisson 框架介绍使用

举报
程序员-上善若水 发表于 2022/06/23 23:02:28 2022/06/23
【摘要】 分布式架构-Redisson 框架介绍使用 一、Redisson Redisson是架设在Redis基础上的一个Java驻内存数据网格。 Redisson在基于NIO的Netty框架上,充分的利用了R...

分布式架构-Redisson 框架介绍使用

一、Redisson

Redisson是架设在Redis基础上的一个Java驻内存数据网格。
Redisson在基于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是吞吐量和延迟敏感系统的完美伴侣。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>

  
 
  • 1
  • 2
  • 3
  • 4
  • 5

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);
    }
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

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());

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

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));
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

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()));

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

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());

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

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());

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

7. 双端队列,对头和队尾都可添加或者移除,也是先进先出List

RDeque<String> deque = redissonClient.getDeque("deque");
deque.addFirst("aa");
deque.addLast("bb");

  
 
  • 1
  • 2
  • 3

8. 原子类

RAtomicLong abc = redissonClient.getAtomicLong("abclong");
long andAdd = abc.getAndAdd(1L);
System.out.println(andAdd);

  
 
  • 1
  • 2
  • 3

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");

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

10.分布式锁

RLock lock = null;
try {
   // 获取可重入锁
   lock = redissonClient.getLock("redislock");
   lock.lock();
   Thread.sleep(30000000);
} catch (Exception e) {

} finally {
   if (lock != null) {
       lock.unlock();
   }
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

设置持有锁过期时间

lock.lock(5, TimeUnit.SECONDS);

  
 
  • 1

尝试获取锁等待时间,持有锁最大时间

boolean b = lock.tryLock(5,6, TimeUnit.SECONDS);

  
 
  • 1

示例:

@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";
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

四、Redisson分布式锁过程

  1. 获取锁:
    多个jvm同时的在redis中写入一个相同的key,谁能够写成功谁就获取锁成功。 向Redis中写入key的时候使用lua脚本不是setnx。如果写入key成功,会单独开启一个看门狗的线程(续命定时任务线程) 默认的情况下每隔10s时间不断续命延迟。使用lua脚本在redis上创建hash key;创建成功,则开启一个看门狗续命线程一直不断续命;该key默认是在30s 有效期,续命是每隔10s续命一次。

  2. 释放锁
    调用lua脚本,修改重入的次数,如果重入次数小于0的情况下,直接将该key删除。

  3. 使用Lua脚本的好处:

    Redis在2.6推出了脚本功能,允许开发者使用Lua语言编写脚本传到Redis中执行。使用脚本的好处如下:
    减少网络开销:本来5次网络请求的操作,可以用一个请求完成,原先5次请求的逻辑放在redis服务器上完成。使用脚本,减少了网络往返时延。这点跟管道类似。
    原子操作:Redis会将整个脚本作为一个整体执行,中间不会被其他命令插入。管道不是原子的。
    替代redis的事务功能:redis自带的事务功能很鸡肋,报错不支持回滚,而redis的lua脚本几乎实现了 常规的事务功能,支持报错回滚操作,官方推荐如果要使用redis的事务功能可以用redis lua替代。

五、Redisson RedLock

Jvm01连接到主的redis 做setnx操作的时候,异步将数据同步给从redis;意味着jvm01获取锁成功,正好在这时候主redis宕机了,redis集群自动开启哨兵机制选举,就会选举剩余从节点中某个redis为主redis,就会导致两个jvm获取锁成功,违背分布式锁原子特征。

Redis的分布式锁算法采用红锁机制,红锁需要至少三个以上Redis独立节点,这些节点相互之间可以不需要存在主从之分,每个redis保证独立即可。

  1. 客户端会在每个redis实例创建锁,只需要满足一半的Redis节点能够获取锁成功,就表示加锁成功。
  2. 客户端使用相同的key,在从所有的Redis节点获取锁;
  3. 客户端需要设置超时时间,连接redis设置不成功的情况下立即切换到下一个Redis实例,防止一直阻塞;
  4. 客户端需要计算获取锁的总耗时,客户端至少要有N/2+1节点获取锁成功
    且总耗时时间小于锁的过期时间才能获取锁成功。
  5. 如果客户端最终获取锁失败,必须所有节点释放锁。
RLock lock1 = redissonClient1.getLock(commodityId + "");
RLock lock2 = redissonClient2.getLock(commodityId + "");
RLock lock3 = redissonClient3.getLock(commodityId + "");
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(); 
   }
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

六、Redisson 使用布隆过滤器

@GetMapping("/getTest")
public String getTest() {
    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
    return "success";
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

文章来源: blog.csdn.net,作者:小毕超,版权归原作者所有,如需转载,请联系作者。

原文链接:blog.csdn.net/qq_43692950/article/details/112726748

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。