SpringBoot使用Redisson操作Redis及使用场景实战

前言

在SpringBoot使用RedisTemplate、StringRedisTemplate操作Redis中,我们介绍了RedisTemplate以及如何SpringBoot如何通过RedisTemplate、StringRedisTemplate操作Redis。
RedisTemplate的好处就是基于SpringBoot自动装配的原理,使得整合redis时比较简单。

那既然SrpingBoot可以通过RedisTemplate操作Redis,为何又出现了Redisson呢?Rddisson 中文文档
Reddissin也是一个redis客户端,其在提供了redis基本操作的同时,还具备其他客户端一些不具备的高精功能,例如:分布式锁+看门狗、分布式限流、远程调用等等。Reddissin的缺点是api抽象,学习成本高。

一、概述

从 spring-boot 2.x 版本开始,spring-boot-data-redis 默认使用 Lettuce 客户端操作数据。

1.1 Lettuce

SpringBoot2之后,默认就采用了lettuce。
是高级Redis客户端,基于Netty框架的事件驱动的通信层,用于线程安全同步,异步和响应使用,支持集群,Sentinel,管道和编码器。
Lettuce的API是线程安全的,可以操作单个Lettuce连接来完成各种操作,连接实例(StatefulRedisConnection)可在多个线程间并发访问。

1.2 Reddisson

基于Netty框架的事件驱动的通信层,方法是异步的,API线程安全,可操作单个Redisson连接来完成各种操作。
实现了分布式和可扩展的Java数据结构,不支持字符串操作,不支持排序、事务、管道、分区等Redis特性。
提供很多分布式相关操作服务,如,分布式锁,分布式集合,可通过 Redis支持延迟队列。

总结:优先使用Lettuce,需要分布式锁,分布式集合等分布式的高级特性,添加Redisson结合使用。

二、Spring-Boot整合Redisson

2.1 引入依赖

<dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId><version>3.13.6</version>
</dependency>

注意:引入此依赖后,无需再引入spring-boot-starter-data-redis,其redisson-spring-boot-starter内部已经进行了引入,且排除了 Redis 的 Luttuce 以及 Jedis 客户端。因此,在 application.yaml 中 Luttuce 和 Jedis 的配置是不会生效的。
在这里插入图片描述

在项目使用 Redisson 时,我们一般会使用 RedissonClient 进行数据操作,但有朋友或许觉得 RedissonClient 操作不方便,或者更喜欢使用 RedisTemplate 进行操作,其实这两者是可以共存的,我们只需要再定义RedisTemplate的配置类即可。参考SpringBoot使用RedisTemplate、StringRedisTemplate操作Redis。

发现项目引入 Redisson 后,RedisTemplate底层所用的连接工厂也是 Redisson。
在这里插入图片描述

2.2 配置文件

在application.yaml中添加redis的配置信息。

spring:data:redis:mode: master# 地址host: 30.46.34.190# 端口,默认为6379port: 6379# 密码,没有不填password: ''# 几号库database: 1sentinel:master: masternodes: 30.46.34.190cluster:nodes: 30.46.34.190lettuce:pool:# 连接池的最大数据库连接数max-active: 200# 连接池最大阻塞等待时间(使用负值表示没有限制)max-wait: -1ms# 连接池中的最大空闲连接max-idle: 50# 连接池中的最小空闲连接min-idle: 8

2.3 配置类

@Configuration
@EnableConfigurationProperties({RedisProperties.class})
public class RedissonConfig {private static final String REDIS_PROTOCOL_PREFIX = "redis://";@Value("${spring.data.redis.mode}")private String redisMode;private final RedisProperties redisProperties;public RedissonConfig(RedisProperties redisProperties) {this.redisProperties = redisProperties;}/*** 逻辑参考 RedissonAutoConfiguration#redisson()*/@Bean(destroyMethod = "shutdown")public RedissonClient redisson(@Autowired(required = false) List<RedissonAutoConfigurationCustomizer> redissonAutoConfigurationCustomizers) throws IOException {Config config = new Config();config.setCheckLockSyncedSlaves(false);int max = redisProperties.getLettuce().getPool().getMaxActive();int min = redisProperties.getLettuce().getPool().getMinIdle();switch (redisMode) {case "master": {SingleServerConfig singleConfig = config.useSingleServer().setAddress(REDIS_PROTOCOL_PREFIX + redisProperties.getHost() + ":" + redisProperties.getPort()).setDatabase(redisProperties.getDatabase()).setPassword(redisProperties.getPassword());if (redisProperties.getConnectTimeout() != null) {singleConfig.setConnectTimeout((int) redisProperties.getConnectTimeout().toMillis());}singleConfig.setConnectionPoolSize(max);singleConfig.setConnectionMinimumIdleSize(min);}break;case "sentinel": {String[] nodes = convert(redisProperties.getSentinel().getNodes());SentinelServersConfig sentinelConfig = config.useSentinelServers().setMasterName(redisProperties.getSentinel().getMaster()).addSentinelAddress(nodes).setDatabase(redisProperties.getDatabase()).setPassword(redisProperties.getPassword());if (redisProperties.getConnectTimeout() != null) {sentinelConfig.setConnectTimeout((int) redisProperties.getConnectTimeout().toMillis());}sentinelConfig.setMasterConnectionPoolSize(max);sentinelConfig.setMasterConnectionMinimumIdleSize(min);sentinelConfig.setSlaveConnectionPoolSize(max);sentinelConfig.setSlaveConnectionMinimumIdleSize(min);}break;case "cluster": {String[] nodes = convert(redisProperties.getCluster().getNodes());ClusterServersConfig clusterConfig = config.useClusterServers().addNodeAddress(nodes).setPassword(redisProperties.getPassword());if (redisProperties.getConnectTimeout() != null) {clusterConfig.setConnectTimeout((int) redisProperties.getConnectTimeout().toMillis());}clusterConfig.setMasterConnectionMinimumIdleSize(min);clusterConfig.setMasterConnectionPoolSize(max);clusterConfig.setSlaveConnectionMinimumIdleSize(min);clusterConfig.setSlaveConnectionPoolSize(max);}break;default:throw new IllegalArgumentException("无效的redis mode配置");}if (redissonAutoConfigurationCustomizers != null) {for (RedissonAutoConfigurationCustomizer customizer : redissonAutoConfigurationCustomizers) {customizer.customize(config);}}return Redisson.create(config);}private String[] convert(List<String> nodesObject) {List<String> nodes = new ArrayList<String>(nodesObject.size());for (String node : nodesObject) {if (!node.startsWith(REDIS_PROTOCOL_PREFIX)) {nodes.add(REDIS_PROTOCOL_PREFIX + node);} else {nodes.add(node);}}return nodes.toArray(new String[0]);}
}

2.4 使用方式

@Component
public class RedissonService {@Resourceprotected RedissonClient redissonClient;public void redissonExists(String key){RBucket<String> rBucketValue = redissonClient.getBucket(key, StringCodec.INSTANCE);if (rBucketValue.isExists()){String value = rBucketValue.get();// doSomething} else {// doElseSomething}}}

2.5 实用场景

2.5.1 分布式锁

有点经验的同学一提到使用分布式锁便联想到了redis,那redis如何实现分布式锁呢?

分布式锁本质上要实现的目标就是在Redis中占一个坑(简单的说,就是萝卜占坑的道理),当别的进程也要来占坑时,发现那个坑里已经有一个颗大萝卜时,就只好放弃或者稍后重试。

分布式锁常用手段

1.使用setNx命令
这个命令的详细描述是(set if not exists),如果指定key不存在则设置(成功占坑),在业务执行完成后,调用del命令删该key(释放坑)。比如:

# set 锁名 值
setnx distribution-lock  locked// dosomingdel  distribution-lock

但这个命令存在一个问题,如果执行逻辑中出现问题,可能导致del指令无法执行,那么该锁就会成为死锁了。
可能有小伙伴贴心的想到了,我们可以给这个key再设置一个过期时间呀。比如:

setnx distribution-lock  lockedexpire distribution-lock  10// dosomingdel  distribution-batch

即使这样操作后,该逻辑仍有问题,由于 setnx 与 expire 是两条命令,如果在 setnx 与 expire 之间,redis 服务器挂了,就会导致 expire 不会执行,从而过期时间设置失败,该锁仍会成为死锁。

根源是 setnx 与 expire 两条命令并不是原子命令

且redis的事物也无法解决 setnx 与 expire 的问题,因为 expire 是依赖于 setnx 的执行结果的,如果 setnx 没有成功,expire则不应该执行。事物又无法进行if else判断,故 setnx+expire 方式实现分布式锁,并不是优解。

2.使用setNx Ex 命令
上方已经说了 setNx+expire 的问题,Redis官方为了解决这个问题,在2.8版本时引入了 set指令的扩展参数,使得 setnx 与 expire命令可以一起执行。比如:

# set 锁名 值 ex 过期时间(单位:秒) nx
set distribution-lock locked ex 5 nx// doSomthingdel distribution-lock

从逻辑上来讲,setNx Ex 已是优解了,不会使该分布式锁成为死锁。

但在我们开发中,或许仍会出现问题,为什么呢?
由于我们一开始为此锁设置了一个过期时间,那假如我们的业务逻辑执行耗时超过了设置的过期时间呢?就会出现一个线程未执行完毕,第二个线程可能持有了这个分布式锁的情况。
所以呢,如果使用 setNx Ex 组合,必须要确保自己的锁的超时时间大于占锁后的业务执行时间

3.使用lua脚本+watch dog自动延期机制
这个方案在网上一找一大堆,在此就不做详细赘述。

Redisson实现分布式锁

上方介绍的 setNx 与 setNx Ex 命令,都是Redis 服务器为我们提供的原生命令,也或多或少的存在着一部分问题,为解决setNx Ex命令存在着业务逻辑大于锁超时时间的问题,Redisson内部提供了一个监控锁的看门狗,它的作用是在Redisson实例被关闭前,不断的延长锁的有效期。默认情况下,看门狗的检查锁的超时时间是30秒钟(就是续期30s),也可以通过修改Config.lockWatchdogTimeout来另行指定,锁的初始过期时间默认也是30s。

// 加锁以后10秒钟自动解锁
// 无需调用unlock方法手动解锁
lock.lock(10, TimeUnit.SECONDS);// 尝试加锁,最多等待100秒,上锁以后10秒自动解锁
boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);
if (res) {try {...} finally {lock.unlock();}
}
@Resource
RedissonClient redissonClient;@GetMapping("/testDistributionLock")
public BaseResponse<String> testRedission(){RLock lock = redissonClient.getLock("redis:distributionLock");try {boolean locked = lock.tryLock(10, 3, TimeUnit.SECONDS);if(locked){log.info("获取锁成功");Thread.sleep(100);return ResultUtils.success("ok" );}else{log.error("获取锁失败");return ResultUtils.error(ErrorCode.SYSTEM_ERROR);}} catch (InterruptedException e) {throw new BusinessException(ErrorCode.SYSTEM_ERROR,"出异常了");} finally {lock.unlock();}
}

2.5.2 限流

我们是有面临高并发下需要对接口或者业务逻辑限流的问题,我们可以采用Guaua依赖下的RateLimiter 实现,实际上,Redisssion也有类似的限流功能。

RateLimiter 被称为令牌桶限流,此类限流是首先定义好一个令牌桶,指明在一定时间内生成多少个令牌,每次访问时从令牌桶获取指定数量令牌,如果获取成功,则设为有效访问。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/870734.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【力扣C语言】每日一题—第387题,字符串中第一个唯一字符

题目&#xff1a; 给出一串字符串&#xff0c;找出其中第一个不重复的字符&#xff0c;返回它的索引 思路&#xff1a; 暴力求解&#xff1a; 1、算字符串长度 2、while循环作为外循环&#xff0c;for循环作为内循环&#xff0c;依次遍历&#xff0c;如果有相同的跳过&…

防火墙安全策略练习

目录 实验拓扑 实验要求 实验思路 实验步骤 1.配置交换机&#xff0c;划分接口的vlan&#xff0c;配置ISP 2.配置防火墙 3.接下来在WEB界面进行所有安全策略操作 配置接口 创建安全区域&#xff1a; 创建地址&#xff1a; 时间段&#xff1a; 安全策略部分&#xff…

地理信息科学在灾害管理中的应用:GIS构建防灾减灾的智慧防线

在全球气候变化与人类活动加剧的背景下&#xff0c;自然灾害频发&#xff0c;给社会经济发展带来了严峻挑战。本文将深入分析GIS在灾害预测、评估和响应中的核心作用&#xff0c;展示其如何为构建更加安全、韧性的社会提供智慧解决方案。 灾害预测&#xff1a;GIS的“先知”之…

JavaScript青少年简明教程:为何学习JavaScript及JavaScript简介

JavaScript青少年简明教程&#xff1a;为何学习JavaScript及JavaScript简介 JavaScript最初是为web浏览器&#xff08;前端开发&#xff09;设计的。它可以在所有现代浏览器中运行&#xff0c;包括Chrome, Firefox, Safari, Edge等。 这意味着JavaScript代码可以在任何能运行…

docker中mysql设置lower_case_table_names配置的坑

前沿 今天在使用flowable流程框架的时候&#xff0c;遇到一个问题。需要配置MySQL数据库以实现表名大小写不敏感。本以为这是一个简单的任务&#xff0c;却耗费了我两个多小时的时间。 docker容器中修改配置&#xff0c;重启不成功 我们前提是容器中的mysql中已经有很多数据…

FastAPI -- 第一弹(查询参数/路径参数/请求体参数)

Hello World 经典的 Hello World 安装 pip install fastapi pip install "uvicorn[standard]"main.py from typing import Unionfrom fastapi import FastAPIapp FastAPI()app.get("/") def read_root():return {"Hello": "World"}…

判断链表中是否有环(力扣141.环形链表)

这道题要用到快慢指针。 先解释一下什么是快慢指针。 快慢指针有两个指针&#xff0c;走得慢的是慢指针&#xff0c;走得快的是快指针。 在这道题&#xff0c;我们规定慢指针一次走一步&#xff0c;快指针一次走2步。 如果该链表有环&#xff0c;快慢指针最终会在环中相遇&a…

微调及代码

一、微调&#xff1a;迁移学习&#xff08;transfer learning&#xff09;将从源数据集学到的知识迁移到目标数据集。 二、步骤 1、在源数据集&#xff08;例如ImageNet数据集&#xff09;上预训练神经网络模型&#xff0c;即源模型。 2、创建一个新的神经网络模型&#xff…

大数据基础:Hadoop之Yarn重点架构原理

文章目录 Hadoop之Yarn重点架构原理 一、Yarn介绍 二、Yarn架构 三、Yarn任务运行流程 四、Yarn三种资源调度器特点及使用场景 Hadoop之Yarn重点架构原理 一、Yarn介绍 Apache Hadoop Yarn(Yet Another Reasource Negotiator&#xff0c;另一种资源协调者)是Hadoop2.x版…

LLM-向量数据库中的索引算法总结

文章目录 前言向量数据库介绍索引方法倒排索引KNN 搜索近似 KNN 搜索Product Quantization(PQ)NSW 算法搜索HNSW 前言 向量数据库是当今大模型知识库检索落地实践的核心组件&#xff0c;下图是构建知识库检索的架构图&#xff1a; 首先会将相关文档数据向量化嵌入到向量化数据…

Python Linux下编译

注意 本教程针对较新Linux系统&#xff0c;没有升级依赖、处理旧版本Linux的openssl等步骤&#xff0c;如有需要可以查看往期文章&#xff0c;例如&#xff1a;在Centos7.6镜像中安装Python3.9 教程中没有使用默认位置、默认可执行文件名&#xff0c;请注意甄别 安装路径&#…

vue3中echarts的使用

1.下载 echartsnpm i -s echarts 2.在main.js中引入import { createApp } from vue import App from ./App.vue// 引入 echarts import * as echarts from echarts const app createApp(App) // 全局挂载 echarts app.config.globalProperties.$echarts echartsapp.mount(#ap…

I18N/L10N 历史 / I18N Guidelines I18N 指南 / libi18n 模块说明

注&#xff1a;机翻&#xff0c;未校对。 文章虽然从 Netscape 客户端展开 I18N/L10N 历史&#xff0c;但 I18N/L10N 的演化早已不仅限适用于 Netscape 客户端。 Netscape Client I18N/L10N History Netscape 客户端 I18N/L10N 历史 Contact: Bob Jung <bobjnetscape.com&…

阿里生态体系

阿里巴巴的“16N”战略框架是一种业务布局战略。具体来说&#xff0c;“1”代表核心电商平台&#xff0c;“6”代表阿里的六大板块&#xff0c;“N”代表众多的新业务和创新业务。以下是对“16N”具体内容的详细说明&#xff1a; 1. 核心电商平台 阿里巴巴电子商务业务&#…

Go语言入门之数组切片

Go语言入门之数组切片 1.数组的定义 数组是一组连续内存空间存储的具有相同类型的数据&#xff0c;是一种线性结构。 在Go语言中&#xff0c;数组的长度是固定的。 数组是值传递&#xff0c;有较大的内存开销&#xff0c;可以使用指针解决 数组声明 var name [size]typename&…

达梦数据库dm8安装步骤及迁移

目录 前言: 一、安装部署 1、下载 2、创建用户及安装目录 3、挂载下载的镜像 4、环境配置 5、安装 二、基本使用 1、DM工具使用 2、兼容性配置 2.1 兼容GBK字符集编码 2.2 兼容UTF-8字符集编码 3、创建用户和密码,表空间 4、整理数据库配置 5、启动脚本设置 …

华为OD机考题(HJ74 参数解析)

前言 经过前期的数据结构和算法学习&#xff0c;开始以OD机考题作为练习题&#xff0c;继续加强下熟练程度。 描述 在命令行输入如下命令&#xff1a; xcopy /s c:\\ d:\\e&#xff0c; 各个参数如下&#xff1a; 参数1&#xff1a;命令字xcopy 参数2&#xff1a;字符串…

JavaSE学习笔记之内部类、枚举类和基本类型包装类

今天我们继续复习Java相关的知识&#xff0c;和大家分享有关内部类等方面的知识&#xff0c;希望大家喜欢。 目录​​​​​​​ 内部类 成员内部类 ​编辑 静态内部类 局部内部类 匿名内部类 枚举类 定义方法 基本类型包装类 自动装箱和拆箱 内部类 成员内部类 成…

使用 Google 的 Generative AI 服务时,请求没有包含足够的认证范围(scopes)

题意&#xff1a; Google generativeai 403 Request had insufficient authentication scopes. [reason: "ACCESS_TOKEN_SCOPE_INSUFFICIENT" 问题背景&#xff1a; I have tried the simple POC for generativeai on its own to do generate_content and it works…

WPS点击Zotero插入没有任何反应

wps个人版没有内置vba&#xff0c;因此即便一下插件安装上了&#xff08;如Axmath&#xff0c;zotero&#xff09;&#xff0c;当点击插件的时候会出现“点不动”、“点击插件没反应的现象。至于islide一类的插件&#xff0c;干脆连装都装不上。 这就需要手动安装一下vba。 针…