百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术教程 > 正文

聊一聊Redis官方置顶推荐的Java客户端Redisson

mhr18 2024-11-14 16:26 25 浏览 0 评论

写这篇的时候,相信有很多朋友还在用Jedis作为Redis的客户端,我不禁有很多问号,Jedis还香吗?如果你早些年说它香我信,但是都2020年了,它真的不那么香了。那为什么还继续使用它呢?大部分原因或多或少是因为一遗留代码没人敢大动,就这样吧;二新项目没人主导使用其它实现做替换。祖传代码不轻易大动,这个真理必须相信,且坚持相信;至于没人主导拍板做技术替换,可能是习惯了Jedis的用法,也可能是没人了解其它技术实现,当然还有其它原因,有兴趣分享的朋友可以在评论区聊一聊。咳咳,扯远了,来聊我们今天的话题-Redisson实战用法。

在Redis的官网(https://redis.io/clients#java)上可以看到Java语言的推荐客户端列表,除了我们都熟知的Jedis之外,Redisson也是官方推荐的客户端。从这我们了解到Redisson是一个Redis客户端,那它到底Redisson是什么呢?Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid),它充分利用了Redis键值数据库提供的一系列优势,基于Java实用工具包中常用接口,为使用者提供了一系列具有分布式特性的常用工具类,让使用Redis更加简单、便捷,从而让使用者能够将更多精力集中到业务逻辑处理上。

也就是说Redisson不仅仅是一个Redis客户端,它还实现了很多具有分布式特性的常用工具类,例如分布式锁、布隆过滤器等,更多功能特性请移步https://redisson.org/。接下来,我们一起聊一下Redisson中如何轻松操作Redis中的字符串(strings)、哈希(hashes)、列表(lists)、集合(sets)和有序集合(sorted sets),以及如何使用Redisson实现的布隆过滤器和分布式锁,最后分析一下Redisson中分布式锁的解决方案?。

创建RedissonClient

要使用Redisson,首先要创建RedissonClient对象实例。创建RedissonClient对象实例的方式多种多样,可以直接通过在代码中设置Redis服务的相关参数创建,也可以通过加载JSON格式或YAML格式配置文件创建,还可以通过在Spring XML文件中使用Redisson标签配置创建,具体如何创建RedissonClient对象实例可根据需要选择,这里就不一一介绍了,有想法的可以移步Redission官网。本文为了展示使用样例代码,使用了最简单的方式:在代码中设置单机Redis服务的相关参数创建RedissonClient对象实例,具体代码如下。

Config config = new Config();// 使用单机Redis服务config.useSingleServer().setAddress("redis://127.0.0.1:6379");
// 创建Redisson客户端RedissonClient redisson = Redisson.create(config);

字符串(strings)

Redisson将Redis中的字符串数据结构封装成了RBucket,通过RedissonClient的getBucket(key)方法获取一个RBucket对象实例,通过这个实例可以设置value或设置value和有效期,例如如下代码。

RBucket<String> nameRBucket =  redisson.getBucket("name");// 只设置value,key不过期nameRBucket.set("四哥");
// 设置value和key的有效期nameRBucket.set("四哥", 30, TimeUnit.SECONDS);
// 通过key获取valueredisson.getBucket("name").get();

有朋友可能发现了,上面的nameRBucket使用了String约定了value类型,也就是说这里的value可以是其它类型,例如value是一个对象,可以这么操作。

Student tom = new Student();tom.setId(456L);tom.setChineseName("刘能");tom.setEnglishName("tom");tom.setAge(52);
// 通过key获取RBucket对象实例RBucket<Student> studentRBucket = redisson.getBucket("student");
// 设置value和有效期studentRBucket.set(tom, 300, TimeUnit.SECONDS);
// 通过key获取valueredisson.getBucket("student").get();

哈希(hashes)

Redisson将Redis中的字符串数据结构封装成了RMap,操作示例代码如下。

// 通过key获取RMapRMap<String, String> studentRMap = redisson.getMap("studentMap");
// 设置map中key-valuestudentRMap.put("id", "123");studentRMap.put("name", "赵四");studentRMap.put("age", "50");
// 设置key有效期studentRMap.expire(300, TimeUnit.SECONDS);
// 通过key获取valueredisson.getMap("studentMap").get("name");

列表(lists)

使用示例代码如下。

RList<Student> studentRList = redisson.getList("studentList");studentRList.add(jack);
// 设置有效期studentRList.expire(300, TimeUnit.SECONDS);
// 通过key获取valueredisson.getList("studentList").get(0);

集合(sets)

使用示例代码如下。

RSet<Student> studentRSet = redisson.getSet("studentSet");studentRSet.add(jack);studentRSet.add(tom);
// 设置有效期studentRSet.expire(300, TimeUnit.SECONDS);
// 通过key获取valueredisson.getSet("studentSet");

有序集合(sorted sets)

使用示例代码如下。

RSortedSet<Student> studentSortedSet = redisson.getSortedSet("studentSortedSet");studentSortedSet.add(jack);studentSortedSet.add(tom);
// 通过key获取valueredisson.getSortedSet("studentSortedSet");

布隆过滤器

布隆过滤器(Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都比一般的算法要好的多,缺点是有一定的误识别率和删除困难。更多布隆过滤器的内容,请通过搜索引擎了解更多。

Redission提供了布隆过滤器的实现,可以直接使用,示例代码如下。

RBloomFilter seqIdBloomFilter = redisson.getBloomFilter("seqId");
// 初始化预期插入的数据量为10000000和期望误差率为0.01seqIdBloomFilter.tryInit(10000000, 0.01);
// 插入部分数据seqIdBloomFilter.add("123");seqIdBloomFilter.add("456");seqIdBloomFilter.add("789");
// 判断是否存在System.out.println(seqIdBloomFilter.contains("123"));System.out.println(seqIdBloomFilter.contains("789"));System.out.println(seqIdBloomFilter.contains("100"));

分布式锁

Redission提供了强大的分布式锁实现,使用简单、安全。下面模拟多线程竞争分布式锁,示例代码如下。

for (int i = 0; i < 5; i++) {    new Thread(new Runnable() {        @Override        public void run() {            final String lockKey = "abc";            RLock lock = redisson.getLock(lockKey);            boolean hasLocked = lock.tryLock();            System.out.println(Thread.currentThread().getName() + ":" + hasLocked);        }    }).start();}

Redisson分布式锁解决方案

使用Redis实现分布式锁,一般的实现是使用setnx命令,但是这种实现方式在高并发且并发安全控制非常高的情况是有问题的,下面从三个方面分析这些问题。

?不具备可重入性

在执行setnx命令时,通常采用业务上指定的名称作为key名,用时间或随机值作为value来实现。这样的实现方式不具备追踪请求线程的能力,同时也不具备统计重入次数的能力,甚至有些实现方式都不具备操作的原子性。当遇到业务上需要在多个地方用到同样一个锁的时候,很显然使用不具有可重入的锁会很容易发生死锁的现象。特别是在有递归逻辑的场景里,发生死锁的几率会更高。Java并发工具包里的Lock对象和sychronized语块都具有可重入性,对于经常使用这些工具的人来说,往往会很容易忽略setnx的这个缺陷。

?不支持续约

在分布式环境中,为了保证锁的活性和避免程序宕机造成的死锁现象,分布式锁往往会引入一个失效时间,超过这个时间则认为自动解锁。这样的设计前提是开发人员对这个自动解锁时间的力度有一个很好的把握,太短了可能会出现任务没做完锁就失效了,而太长了在出现程序宕机或业务节点挂掉时,其它节点需要等很长时间才能恢复,而难以保证业务的SLA。setnx的设计缺乏一个延续有效期的续约机制,无法保证业务能够先工作做完再解锁,也不能确保在某个程序宕机或业务节点挂掉的时候,其它节点能够很快的恢复业务处理能力。

?不具备阻塞的能力

平常大家多少都接触过的锁,由于加锁策略(Locking Strategy)的差别,使得每种锁都有各自不同的特性。但是在通常情况下这些锁都具备两个共性:一是互斥性,二是阻塞性。互斥性是指在任何时刻最多只能有一个线程获得通行的资格。阻塞性是指的在有竞争的情况下,未获取到资源的线程会停止继续操作,直到成功获取到资源或取消操作。很显然setnx命令只提供了互斥的特性,却没有提供阻塞的能力。虽然在业务代码里可以引入自旋机制来进行再次获取,但这仅仅是把原本应该在锁里实现的功能搬到了业务代码里,通过增加业务代码的复杂程度来简化锁的实现似乎显得有点南辕北辙。

Redisson的分布式锁在满足以上三个基本要求的同时还增加了线程安全的特点。利用Redis的Hash结构作为储存单元,将业务指定的名称作为key,将随机UUID和线程ID作为field,最后将加锁的次数作为value来储存。同时UUID作为锁的实例变量保存在客户端。将UUID和线程ID作为标签在运行多个线程同时使用同一个锁的实例时,仍然保证了操作的独立性,满足了线程安全的要求。

加锁时通过Lua脚本先检查锁是否存在,如不存在则创建hash相关字段并设定过期时间后返回,这表示加锁成功。如果该hash字段已经存在,再检查随机字段和线程id是否一致。如果一致则递增value的值并重新更新过期时间后返回,此时表示同一节点同一线程再次成功加锁,从而保证了可重入性。如果hash存在且字段不一致,说明其他节点或线程已经拥有了这个锁。因此Lua脚本返回这个hash的当前有效期。当结果返回到该客户端后,如果加锁成功,则通过线程池依照设定好的参数定时执行续约,最后通知请求线程继续后续操作。如果加锁没有成功,则监听一个以这个key为后缀的pubsub频道,直到收到解锁消息后再次重试。

解锁时通过Lua脚本先检查锁是否存在,如果已经不存在则直接发布解锁消息并返回。如果仍然存在则检查标签是否存在,如果不存在则表示这个锁并不为本线程所拥有,这种情况请求线程将收到报错。如果存在则表示该锁正是被该线程所拥有。在这种情况下,递减标签字段后判断,如果返回的加锁数量仍然大于0,说明当前的锁仍然有效,仅仅只是重入次数减少了。相反这表示锁已经完全解开,则立即删除该锁并发布解锁信息。

Redisson的可重入锁解决了setnx锁的许多先天性不足,但是由于它仍然是以单一一个key的方式储存在固定的一个Redis节点里,并且有自动失效期。这样的设计虽然可以很大程度上避免客户端程序宕机或业务节点挂掉造成的影响,但是随之带来的弊端是遇到服务端Redis进程宕机或节点挂掉的情况,还是有可能会造成锁的信息丢失,这样的缺陷显然无法满足某些特定场景提出的高可用性要求。

介于这种情况,Redis作者Salvatore提出了一个基于多个节点的高可用分布式锁的算法,起名叫红锁(RedLock: https://redis.io/topics/distlock)。在这种算法下,客户端需要同时在多个节点里同时尝试获取一个独立的锁,只有当一次性成功获取了大多数锁的情况下才能被视为赢得了高可用分布式锁,否则需要解除已经部分获取到的锁,等待一个随机时间后再次重试。

在算法设计上,Salvatore依然采用的是setnx作为举例讲解分布式锁的互斥特性。在算法实现上,Redisson的RedissonRedLock采用的是前面提到的更加灵活方便的可重入锁。Redisson的扩展算法是Redis官网唯一认可的Java实现。

虽然Redlock的算法提供了高可用的特性,但建立在大多数可见原则的前提下,这样的算法适用性仍然有一定局限。Redisson为此提供了基于增强型的算法的高可用分布式联锁RedissonMultiLock。这种算法要求客户端必须成功获取全部节点的锁才被视为加锁成功,从而更进一步提高了算法的可靠性。


学之多,而后知之少!朋友们【点赞+评论+转发】是我持续更新的最大动力,我们下期见!

往期推荐

我画了25张图展示线程池工作原理和实现原理,建议先收藏再阅读

实战级详解Spring框架中引入阿里开源组件Nacos作配置中心

这样的API网关查询接口优化,我是被迫的

Spring框架你敢写精通,面试官就敢问@Autowired注解的实现原理

面试被问为什么使用Spring Boot?答案好像没那么简单

Spring声明式事务处理的实现原理,来自面试官的穷追拷问

说实话,面试这么问Spring框架的问题,我真扛不住

没使用加号拼接字符串,面试官竟然问我为什么

面试官一步一步的套路你,为什么SimpleDateFormat不是线程安全的

都说ThreadLocal被面试官问烂了,可为什么面试官还是喜欢继续问

超实用高并发编程ExecutorCompletionService案例分析与源码解读

相关推荐

一文读懂Prometheus架构监控(prometheus监控哪些指标)

介绍Prometheus是一个系统监控和警报工具包。它是用Go编写的,由Soundcloud构建,并于2016年作为继Kubernetes之后的第二个托管项目加入云原生计算基金会(C...

Spring Boot 3.x 新特性详解:从基础到高级实战

1.SpringBoot3.x简介与核心特性1.1SpringBoot3.x新特性概览SpringBoot3.x是建立在SpringFramework6.0基础上的重大版...

「技术分享」猪八戒基于Quartz分布式调度平台实践

点击原文:【技术分享】猪八戒基于Quartz分布式调度平台实践点击关注“八戒技术团队”,阅读更多技术干货1.背景介绍1.1业务场景调度任务是我们日常开发中非常经典的一个场景,我们时常会需要用到一些不...

14. 常用框架与工具(使用的框架)

本章深入解析Go生态中的核心开发框架与工具链,结合性能调优与工程化实践,提供高效开发方案。14.1Web框架(Gin,Echo)14.1.1Gin高性能实践//中间件链优化router:=...

SpringBoot整合MyBatis-Plus:从入门到精通

一、MyBatis-Plus基础介绍1.1MyBatis-Plus核心概念MyBatis-Plus(简称MP)是一个MyBatis的增强工具,在MyBatis的基础上只做增强不做改变,为简化开发、提...

Seata源码—5.全局事务的创建与返回处理

大纲1.Seata开启分布式事务的流程总结2.Seata生成全局事务ID的雪花算法源码3.生成xid以及对全局事务会话进行持久化的源码4.全局事务会话数据持久化的实现源码5.SeataServer创...

Java开发200+个学习知识路线-史上最全(框架篇)

1.Spring框架深入SpringIOC容器:BeanFactory与ApplicationContextBean生命周期:实例化、属性填充、初始化、销毁依赖注入方式:构造器注入、Setter注...

OpenResty 入门指南:从基础到动态路由实战

一、引言1.1OpenResty简介OpenResty是一款基于Nginx的高性能Web平台,通过集成Lua脚本和丰富的模块,将Nginx从静态反向代理转变为可动态编程的应用平台...

你还在为 Spring Boot3 分布式锁实现发愁?一文教你轻松搞定!

作为互联网大厂后端开发人员,在项目开发过程中,你有没有遇到过这样的问题:多个服务实例同时访问共享资源,导致数据不一致、业务逻辑混乱?没错,这就是分布式环境下常见的并发问题,而分布式锁就是解决这类问题的...

近2万字详解JAVA NIO2文件操作,过瘾

原创:小姐姐味道(微信公众号ID:xjjdog),欢迎分享,转载请保留出处。从classpath中读取过文件的人,都知道需要写一些读取流的方法,很是繁琐。最近使用IDEA在打出.这个符号的时候,一行代...

学习MVC之租房网站(十二)-缓存和静态页面

在上一篇<学习MVC之租房网站(十一)-定时任务和云存储>学习了Quartz的使用、发邮件,并将通过UEditor上传的图片保存到云存储。在项目的最后,再学习优化网站性能的一些技术:缓存和...

Linux系统下运行c++程序(linux怎么运行c++文件)

引言为什么要在Linux下写程序?需要更多关于Linux下c++开发的资料请后台私信【架构】获取分享资料包括:C/C++,Linux,Nginx,ZeroMQ,MySQL,Redis,fastdf...

2022正确的java学习顺序(文末送java福利)

对于刚学习java的人来说,可能最大的问题是不知道学习方向,每天学了什么第二天就忘了,而课堂的讲解也是很片面的。今天我结合我的学习路线为大家讲解下最基础的学习路线,真心希望能帮到迷茫的小伙伴。(有很多...

一个 3 年 Java 程序员 5 家大厂的面试总结(已拿Offer)

前言15年毕业到现在也近三年了,最近面试了阿里集团(菜鸟网络,蚂蚁金服),网易,滴滴,点我达,最终收到点我达,网易offer,蚂蚁金服二面挂掉,菜鸟网络一个月了还在流程中...最终有幸去了网易。但是要...

多商户商城系统开发全流程解析(多商户商城源码免费下载)

在数字化商业浪潮中,多商户商城系统成为众多企业拓展电商业务的关键选择。这类系统允许众多商家在同一平台销售商品,不仅丰富了商品种类,还为消费者带来更多样的购物体验。不过,开发一个多商户商城系统是个复杂的...

取消回复欢迎 发表评论: