一. 初识Redission
前面我们讲了Redis的三大常见客户端中的Jedis和Lettuce,今天就跟大家聊聊这个Redisson。那Redisson是什么呢?Redisson是一个在Redis的基础上实现的Java内存数据网格(In-Memory Data Grid),它充分利用了Redis键值数据库提供的一系列优势,基于Java实用工具包中常用接口,为使用者提供了一系列具有分布式特性的常用工具类,让使用Redis更加简单、便捷,从而让使用者能够将更多精力集中到业务逻辑处理上。
也就是说Redisson不仅仅是一个Redis客户端,它是一个以内存 Redis 服务器作为后端的处理 Java 对象,它还实现了很多具有分布式特性的常用工具类。比如分布式锁、布隆过滤器等。下图是官网提供的Redisson可以做的事情。
1.依赖
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.redisson</groupId> <artifactId>redisson-spring-boot-starter</artifactId> <version>3.13.0</version> </dependency> </dependencies>2.yml文件
spring: redis: host: 127.0.0.1 port: 6379 password: 123456二.使用方法
1.对象存储
Redisson将Redis中的字符串数据结构封装成了RBucket,通过RedissonClient的getBucket(key)方法获取一个RBucket对象实例,通过这个实例可以设置value或设置value和有效期。并且可以操作所有类型的对象。示例如下代码。
/** * 通用对象桶,可以用来存放任类型的对象 */ @Test public void RedissonBucket(){ //====================创建Redisson客户端==================== Config config = new Config(); //config.useSingleServer().setAddress("redis://127.0.0.1:6379").setPassword("123456"); config.useSingleServer().setAddress("redis://127.0.0.1:6379"); RedissonClient redissonClient = Redisson.create(config); //====================操作对象桶来存储对象(同步)==================== RBucket<Object> bucket = redissonClient.getBucket("name"); //设置值为victory,过期时间为3小时 bucket.set("victory",30, TimeUnit.HOURS); Object value = bucket.get(); System.out.println(value); //通过key取value值 Object name = redissonClient.getBucket("name").get(); System.out.println(name); //====================关闭客户端==================== redissonClient.shutdown(); }2.二进制流存储
/** * 二进制流 * 提供了InputStream接口和OutputStream接口的实现 */ @Test public void RedissonStream() throws IOException { //====================创建Redisson客户端==================== Config config = new Config(); //config.useSingleServer().setAddress("redis://127.0.0.1:6379").setPassword("123456"); config.useSingleServer().setAddress("redis://127.0.0.1:6379"); RedissonClient redissonClient = Redisson.create(config); //====================操作流来存储对象==================== RBinaryStream stream = redissonClient.getBinaryStream("stream"); stream.set("name is ".getBytes()); OutputStream outputStream = stream.getOutputStream(); outputStream.write("victory".getBytes()); InputStream inputStream = stream.getInputStream(); ByteArrayOutputStream result = new ByteArrayOutputStream(); byte[] bytes = new byte[1024]; int length; while ((length = inputStream.read(bytes)) != -1) { result.write(bytes, 0, length); } System.out.println(result.toString()); //====================关闭客户端==================== redissonClient.shutdown(); }3.List
/** * list * Redisson操作list */ @Test public void list() { //====================创建Redisson客户端==================== Config config = new Config(); //config.useSingleServer().setAddress("redis://127.0.0.1:6379").setPassword("123456"); config.useSingleServer().setAddress("redis://127.0.0.1:6379"); RedissonClient redissonClient = Redisson.create(config); //====================操作list==================== RList<String> list = redissonClient.getList("list"); list.add("victory1"); list.add("victory2"); System.out.println(list); //取值 List<Object> list1 = redissonClient.getList("list").get(); System.out.println(list1); //移除索引0位置元素 list.remove(0); System.out.println(list); //通过key取value值 List<Object> list2 = redissonClient.getList("list").get(); System.out.println(list2); //====================关闭客户端==================== redissonClient.shutdown(); }4.Set
/** * set * Redisson操作set */ @Test public void set() { //====================创建Redisson客户端==================== Config config = new Config(); //config.useSingleServer().setAddress("redis://127.0.0.1:6379").setPassword("123456"); config.useSingleServer().setAddress("redis://127.0.0.1:6379"); RedissonClient redissonClient = Redisson.create(config); //====================操作list==================== RSet<Object> set = redissonClient.getSet("set"); set.add("victory1"); set.add("victory2"); System.out.println(set); //通过key取value值 RSet<Object> set1 = redissonClient.getSet("set"); System.out.println(set1); //====================关闭客户端==================== redissonClient.shutdown(); }5.Map
Redisson将Redis中的字符串数据结构封装成了RMap,就是原本redis中的string类型
/** * map * Redisson操作map * Redisson将Redis中的字符串数据结构封装成了RMap,就是原本redis中的string类型 */ @Test public void map() { //====================创建Redisson客户端==================== Config config = new Config(); //config.useSingleServer().setAddress("redis://127.0.0.1:6379").setPassword("123456"); config.useSingleServer().setAddress("redis://127.0.0.1:6379"); RedissonClient redissonClient = Redisson.create(config); //====================操作list==================== RMap<Object, Object> map = redissonClient.getMap("map"); map.put("name1","victory1"); map.put("name2","victory2"); map.forEach((key,value)->{ System.out.println("key = "+key+" ,value = "+ value); }); //通过key取value值 Object o = redissonClient.getMap("map").get("name1"); System.out.println(o); //====================关闭客户端==================== redissonClient.shutdown(); }6.队列
这里的队列和下面要说的发布订阅的功能跟MQ的不能说是毫无关系,只能说是一毛一样,计划这个专栏结束下个专栏专门来聊MQ,欢迎个位大佬交流指正
/** * 队列 * Redisson操作queue */ @Test public void queue() { //====================创建Redisson客户端==================== Config config = new Config(); //config.useSingleServer().setAddress("redis://127.0.0.1:6379").setPassword("123456"); config.useSingleServer().setAddress("redis://127.0.0.1:6379"); RedissonClient redissonClient = Redisson.create(config); //====================操作queue==================== RQueue<String> queue = redissonClient.getQueue("queue"); //存值 queue.add("victory1"); queue.add("victory2"); //取值 String item = queue.poll(); System.out.println(item); // RQueue<Object> queue1 = redissonClient.getQueue("queue"); System.out.println(queue1); //====================关闭客户端==================== redissonClient.shutdown(); }7.限流器
基于Redis的分布式限流器可以用来在分布式环境下现在请求方的调用频率。既适用于不同Redisson实例下的多线程限流,也适用于相同Redisson实例下的多线程限流。该算法不保证公平性。
/** * 限流器 * Redisson操作rateLimiter */ @Test public void rateLimiter() throws InterruptedException { //====================创建Redisson客户端==================== Config config = new Config(); //config.useSingleServer().setAddress("redis://127.0.0.1:6379").setPassword("123456"); config.useSingleServer().setAddress("redis://127.0.0.1:6379"); RedissonClient redissonClient = Redisson.create(config); //====================操作rateLimiter==================== RRateLimiter rateLimiter = redissonClient.getRateLimiter("rateLimiter"); //创建限流器,最大流速:每1秒钟产生20个令牌 rateLimiter.trySetRate(RateType.OVERALL, 20, 1, RateIntervalUnit.SECONDS); for (int i = 0; i < 10; i++) { new Thread(new Runnable() { int i = 0; @Override public void run() { while(true) { rateLimiter.acquire(1); System.out.println(Thread.currentThread() + "-" + System.currentTimeMillis() + "-" + i++); } } }).start(); } //等待执行完成,不设置等待可能出现还未执行完成客户端就关闭的情况 Thread.sleep(5000); //====================关闭客户端==================== redissonClient.shutdown(); }8.可重入锁
/** * 可重入锁 * Redisson操作RLock */ @Test public void lock() throws InterruptedException { //====================创建Redisson客户端==================== Config config = new Config(); //config.useSingleServer().setAddress("redis://127.0.0.1:6379").setPassword("123456"); config.useSingleServer().setAddress("redis://127.0.0.1:6379"); RedissonClient redissonClient = Redisson.create(config); //====================操作lock==================== RLock lock = redissonClient.getLock("lock"); for (int i = 0; i < 5; i++) { new Thread(() -> { lock.lock(); try { System.out.println(Thread.currentThread() + "-" + System.currentTimeMillis() + "-" + "获取了锁"); Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } }).start(); } //等待执行完成,不设置等待可能出现还未执行完成客户端就关闭的情况 Thread.sleep(5000); //====================关闭客户端==================== redissonClient.shutdown(); }9.发布订阅
Redisson的分布式话题 RTopic 对象实现了发布、订阅的机制。
/** * 发布订阅操作 * Redisson操作RTopic执行发布订阅操作 **/ @Test public void topicPublisherAndSubscriber() throws InterruptedException { //====================创建Redisson客户端==================== Config config = new Config(); //config.useSingleServer().setAddress("redis://127.0.0.1:6379").setPassword("123456"); config.useSingleServer().setAddress("redis://127.0.0.1:6379"); RedissonClient redissonClient = Redisson.create(config); RedissonClient redissonClient1 = Redisson.create(config); //====================操作topic执行发布操作==================== RTopic topic1 = redissonClient.getTopic("topic",new SerializationCodec()); topic1.publish(new Message(1L,"victory",18)); //====================操作topic执行订阅操作==================== Thread.sleep(5000); RTopic topic = redissonClient1.getTopic("topic", new SerializationCodec()); topic.addListener(Message.class, new MessageListener<Message>() { @Override public void onMessage(CharSequence channel, Message msg) { System.out.println("onMessage:=========" + channel + "; Thread:========= " + Thread.currentThread().toString()); System.out.println(" name : " + msg.getName() + " age : " + msg.getAge()); LoggerFactory.getLogger(RedissonDemo.class).info("Redisson接收到消息",msg); } }); //====================关闭客户端==================== redissonClient.shutdown(); redissonClient1.shutdown(); }10.布隆过滤器
布隆过滤器是一个非常长的二进制向量和一系列随机哈希函数的组合,可用于检索一个元素是否存在。
主要作用
应对缓存穿透等场景,如果高并发一波冷数据的情况下,大量的请求可能会击穿我们的缓存导致了雪崩,此时我们可以设置布隆过滤器,如果索引在我们的过滤器中,请求来我们先去查内存,如果内存没有再去查DB,如果索引不存在于过滤器中我们快速返回失败来提高程序的性能。
原理如下:
建立一个二进制向量,所有位设置0;
选择K个散列函数,用于对元素进行K次散列,计算向量的位下标;
添加元素:将K个散列函数作用于该元素,生成K个值作为位下标,将向量的对应位设置为1;
检索元素:将K个散列函数作用于该元素,生成K个值作为位下标,若向量的对应位都是1,则说明该元素可能存在;否则,该元素肯定不存在;
当然他也有他的局限性,比如误差率。随着存入的元素数量增加,误差率随之增加。但是如果元素数量太少,则使用散列表足矣。
/** * Redisson利用Redis实现了Java分布式布隆过滤器(Bloom Filter) * 作用:在缓存层前添加布隆过滤器,常用于高并发场景下应对缓存穿透问题 * 布隆过滤器是一个非常长的二进制向量和一系列随机哈希函数的组合,可用于检索一个元素是否存在; * */ @Test public void bloomFilter() { //====================创建Redisson客户端==================== Config config = new Config(); //config.useSingleServer().setAddress("redis://127.0.0.1:6379").setPassword("123456"); config.useSingleServer().setAddress("redis://127.0.0.1:6379"); RedissonClient redissonClient = Redisson.create(config); //====================操作布隆过滤器==================== RBloomFilter<String> bloomFilter = redissonClient.getBloomFilter("bloom-filter"); // 初始化布隆过滤器,初始化预期插入的数据量为200,期望误差率为0.01 bloomFilter.tryInit(200, 0.01); //插入数据 bloomFilter.add("丁胜利"); bloomFilter.add("丁向前"); bloomFilter.add("胜利丁"); //判断是否包含 boolean victory = bloomFilter.contains("丁胜利"); boolean forward = bloomFilter.contains("向前丁"); System.out.println(victory); //true System.out.println(forward); //false //====================关闭客户端==================== redissonClient.shutdown(); }三.总结
Redisson相比于之前聊过的Jedis和Lettuce来说显得过于笨重,如果只是需要Redis实现简单的热点数据缓存和登录定时过期之类的小功能的话,建议在项目中慎重使用。
到这里吧,Redisson可以做的事情还有很多,我只列举了同步操作的其中一部分就已经一万多字了,还有异步和响应式丁点都没有涉及。但是基本都大同小异,如果有理解不到的欢迎交流。
因为之前虽然用过但是从来没有这么系统的去了解每一个功能,所以导致这一块在创作的时候需要查询大量的资料,Demo的进度也是格外的慢。学然后知不足,教然后知困。各位共勉。
老规矩,需要Demo的话可评论或者私聊我,所有代码的Demo都有敲而且都会有保留,各位看官如果满意的话还请点个赞再走。下面我想想还要再补充一些集群相关的本专栏也就该结束了应该,暂时想不到还有其他什么需要补充的地方了。
参考文献
Redisson: Redis Java client with features of In-Memory Data Grid
概览 - 《Redisson 官方文档中文翻译》 - 书栈网 · BookStack
阿里云登录 - 欢迎登录阿里云,安全稳定的云计算服务平台
阿里云登录 - 欢迎登录阿里云,安全稳定的云计算服务平台