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

一、Redisson

Redisson是架设在Redis基础上的一个Java驻内存数据网格。在基于NIONetty框架上,充分的利用了Redis键值数据库提供的一系列优势,在Java实用工具包中常用接口的基础上,为使用者提供了一系列具有分布式特性的常用工具类。使得原本作为协调单机多线程并发程序的工具包获得了协调分布式多机多线程并发系统的能力,大大降低了设计和研发大规模分布式系统的难度。同时结合各富特色的分布式服务,更进一步简化了分布式环境中程序相互之间的协作。

Redisson提供了使用Redis的最简单和最便捷的方法。Redisson的宗旨是促进使用者对Redis的关注分离(Separation of Concern),从而让使用者能够将精力更集中地放在处理业务逻辑上。如果您现在正在使用其他的RedisJava客户端,希望Redis命令和Redisson对象匹配列表能够帮助您轻松的将现有代码迁徙到Redisson框架里来。如果Redis的应用场景还仅限于作为缓存使用,您也可以将Redisson轻松的整合到像SpringHibernate这样的常用框架里。除此外您也可以间接的通过Java缓存标准规范JCache API (JSR-107)接口来使用Redisson

Redisson生而具有的高性能,分布式特性和丰富的结构等特点恰巧与Tomcat这类服务程序对会话管理器(Session Manager)的要求相吻合。利用这样的特点,Redisson专门为Tomcat提供了会话管理器(Tomcat Session Manager)。

二、Redisson 和Jedis性能对比

Redisson是吞吐量和延迟敏感系统的完美伴侣。比Jedis更有效的方式利用可用的系统资源。
下面的链接可以清晰的展示在并发量增加的时候,RedissonJedis的性能变化。

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 分布式锁过程如下:

  1. 获取锁:
    多个 jvm使用lua脚本在redissetnx 写入一个相同的key,谁能够写成功谁就获取锁成功。 如果写入key成功,会单独开启一个看门狗的线程(续命定时任务线程) 默认的情况下每隔10s时间不断续命延迟,key默认是30s 有效期。
  2. 释放锁
    调用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连接到主的redissetnx操作的时候,异步将数据同步给从redis;意味着jvm01获取锁成功,正好在这时候主redis宕机了,redis集群自动开启哨兵机制选举,就会选举剩余从节点中某个redis为主redis,就会导致两个jvm获取锁成功,违背分布式锁原子特征。

而红锁机制就需要至少三个以上Redis独立节点,这些节点相互之间可以不需要存在主从之分,每个Redis保证独立即可。

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