Redisson(分布式锁、限流)

注意Redisson是基于Redis的,所以必须先引入Redis配置(参考SpringBoot集成Redis文章)

1. 集成Redisson

  1. 引入依赖
<!-- 二选一,区别是第一个自动配置,第二个还需要手动配置也就是第二步自定义配置,注意版本号!
但是我找不到合适的版本还是进行了自定义配置,也有可能不是我说的原因,记录TODO,后面处理-->
<!--引入redisson-->
<dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId><version>3.16.2</version>
</dependency><!-- 原生 -->
<dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.13.4</version>
</dependency>
  1. 自定义配置
package com.tjx.config;import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.codec.JsonJacksonCodec;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;/*** <p>** </p>** @author tianjiaxin* @createTime 2024/6/4 15:31* @Description:*/
@Configuration
public class RedissonConfig {@Value("${spring.redis.host:127.0.0.1}")private String redisHost;@Value("${spring.redis.password:123456}")private String password;@Value("${spring.redis.port:6379}")private String port;@Beanpublic RedissonClient getRedisson() {System.out.println("初始化redisson : " + redisHost);Config config = new Config();// 单机配置,集群配置用config.useClusterServers(),自行百度网上太多了!config.useSingleServer().setAddress("redis://" + redisHost + ":" + port).setPassword(password);config.setCodec(new JsonJacksonCodec());return Redisson.create();}
}

2. 分布式锁

参考文章:

  1. 分布式锁使用:https://blog.csdn.net/liuerpeng1904/article/details/135459765
  2. 加解锁分析:https://writer.blog.csdn.net/article/details/130613740

2.1. 如何使用?

redissonClient.getLock(“key”);获取锁
rLock.tryLock(1, TimeUnit.MINUTES);在1分钟内尝试加锁,并返回是否获取成功结果
rLock.lock();加锁,如果不成功则一直阻塞
rLock.unlock();释放锁
rLock.isHeldByCurrentThread()判断当前线程是否持有锁(防止超时或网络原因造成自动释放锁,从而防止在手动释放锁的时候找不到锁而产生报错)
package com.tjx.service.impl;import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.TimeUnit;/*** <p>** </p>** @author tianjiaxin* @createTime 2024/6/4 14:46* @Description:*/
@Service
public class RedissonServiceImpl {@Autowiredprivate RedissonClient redissonClient;public void test() {
//        new Thread(this::business_01).start();
//        new Thread(this::business_02).start();
//        new Thread(this::business_03).start();new Thread(this::business_04).start();new Thread(this::business_05).start();}public void business_01() {// 1.获取锁RLock rLock = redissonClient.getLock("business");System.out.println(getDate() + ": business_01 锁获取成功");try {// tryLock: 直接返回获取结果,1分钟内不断尝试获取锁,如果获取成功执行业务逻辑,否则释放锁boolean isLocked = rLock.tryLock(1, TimeUnit.MINUTES);System.out.println(getDate() + " : business_01 加锁成功");if (isLocked) {System.out.println(getDate() + ": business_01 开始执行业务逻辑");Thread.sleep(60000);System.out.println(getDate() + ": business_01 结束执行业务逻辑");}} catch (Exception e){e.printStackTrace();} finally {System.out.println(getDate() + ": business_01 释放锁");// 判断当前线程是否持有锁(防止超时或网络原因造成自动释放锁,从而防止在手动释放锁的时候找不到锁而产生报错)if (rLock.isHeldByCurrentThread()) {rLock.unlock();}}}public void business_02() {// 1.获取锁RLock rLock = redissonClient.getLock("business");System.out.println(getDate() + ": business_02 锁获取成功");try {boolean isLocked = rLock.tryLock(1, TimeUnit.MINUTES);System.out.println(getDate() + " : business_02 加锁成功");if (isLocked) {System.out.println(getDate() + ": business_02 开始执行业务逻辑");Thread.sleep(60000);System.out.println(getDate() + ": business_02 结束执行业务逻辑");}} catch (Exception e){e.printStackTrace();} finally {System.out.println(getDate() + ": business_02 释放锁");// 判断当前线程是否持有锁(防止超时或网络原因造成自动释放锁,从而防止在手动释放锁的时候找不到锁而产生报错)if (rLock.isHeldByCurrentThread()) {rLock.unlock();}}}public void business_03() {// 1.获取锁RLock rLock = redissonClient.getLock("business");System.out.println(getDate() + ": business_03 锁获取成功");try {boolean isLocked = rLock.tryLock(1, TimeUnit.MINUTES);System.out.println(getDate() + " : business_03 加锁成功");if (isLocked) {System.out.println(getDate() + ": business_03 开始执行业务逻辑");Thread.sleep(60000);System.out.println(getDate() + ": business_03 结束执行业务逻辑");}} catch (Exception e){e.printStackTrace();} finally {System.out.println(getDate() + ": business_03 释放锁");// 判断当前线程是否持有锁(防止超时或网络原因造成自动释放锁,从而防止在手动释放锁的时候找不到锁而产生报错)if (rLock.isHeldByCurrentThread()) {rLock.unlock();}}}public void business_04() {// 1.获取锁RLock rLock = redissonClient.getLock("business");System.out.println(getDate() + ": business_04 锁获取成功");try {// 如果获取不到锁,则一直阻塞,直到获取到锁rLock.lock();System.out.println(getDate() + " : business_04 加锁成功");System.out.println(getDate() + ": business_04 开始执行业务逻辑");Thread.sleep(10000);System.out.println(getDate() + ": business_04 结束执行业务逻辑");} catch (Exception e){e.printStackTrace();} finally {System.out.println(getDate() + ": business_04 释放锁");// 判断当前线程是否持有锁(防止超时或网络原因造成自动释放锁,从而防止在手动释放锁的时候找不到锁而产生报错)if (rLock.isHeldByCurrentThread()) {rLock.unlock();}}}public void business_05() {// 1.获取锁RLock rLock = redissonClient.getLock("business");System.out.println(getDate() + ": business_05 锁获取成功");try {rLock.lock();System.out.println(getDate() + " : business_05 加锁成功");System.out.println(getDate() + ": business_05 开始执行业务逻辑");Thread.sleep(10000);System.out.println(getDate() + ": business_05 结束执行业务逻辑");} catch (Exception e){e.printStackTrace();} finally {System.out.println(getDate() + ": business_05 释放锁");// 判断当前线程是否持有锁(防止超时或网络原因造成自动释放锁,从而防止在手动释放锁的时候找不到锁而产生报错)if (rLock.isHeldByCurrentThread()) {rLock.unlock();}}}public static String getDate() {SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");String format = dateFormat.format(new Date());return format;}
}

OMS系统使用:

RLock rLock = redissonClient.getMultiLock(actualDeliveryDTO.getItems().stream().map(OutboundOrderActualDeliveryByTruckloadItemDTO::getOrderNo).distinct().map(orderNo -> redissonClient.getLock(getLockKeyForOrderNo(orderNo))).toArray(RLock[]::new));
try {rLock.lock();return doUpdateActualDeliveryByTruckload(actualDeliveryDTO);
} finally {rLock.unlock();
}

2.2. 加解锁分析

加锁:

  1. 尝试获取锁,通过lua加锁脚本获取
  2. 如果没有获取到锁,则订阅解锁消息,并阻塞
  3. 持有的线程释放锁之后,进行广播并唤醒阻塞的线程

两个问题

  1. 如果持有锁的线程崩了怎么办?
    设置锁的时候有过期时间,超时自动释放
  2. 如果超过过期时间但是还没有执行完怎么办?
    看门狗机制,监听持有锁的线程并定义增加过期时间

加锁lua脚本:

解锁:

很简单,直接执行解锁lua脚本

3. 限流

3.1. 基本使用

API详细解释文章:https://blog.csdn.net/qq_43686863/article/details/135634098

import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RRateLimiter;
import org.redisson.api.RateIntervalUnit;
import org.redisson.api.RateType;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.util.concurrent.TimeUnit;/*** <p>** </p>** @author tianjiaxin* @createTime 2024/6/5 11:24* @Description:*/
@Slf4j
@Service
public class RedissonRateLimiterServiceImpl {@Autowiredprivate RedissonClient redissonClient;public void testRateLimiter(String phone) {// Step1.创建限流器RRateLimiter rateLimiter = redissonClient.getRateLimiter("PHONE:" + phone);// Step2.设置限流参数 每10s生成一个令牌// RateType.OVERALL 全局限流, RateType.PER_CLIENT 单机限流rateLimiter.trySetRate(RateType.OVERALL, 1, 10, RateIntervalUnit.SECONDS);// Step3.尝试获取1个令牌// 注意:acquire会阻塞等待, tryAcquire直接返回结果/*if (rateLimiter.tryAcquire()) {log.info("向手机:{}发送短信", phone);} else {log.info("被限流了!!!!!");}*/// 尝试获取1个令牌,获取到返回true,没有获取到就返回false
//        if (rateLimiter.tryAcquire(1)) {
//            log.info("向手机:{}发送短信", phone);
//        } else {
//            log.info("被限流了!!!!!");
//        }// 获取一个令牌,获取到返回true,没有获取到最多等待3秒
//        if (rateLimiter.tryAcquire(3, TimeUnit.SECONDS)) {
//            log.info("向手机:{}发送短信", phone);
//        } else {
//            log.info("被限流了!!!!!");
//        }// 获取1个令牌,获取到返回true,没有获取到最多等待3秒// 注意:permits参数的值必须小于等于trySetRate的rateif (rateLimiter.tryAcquire(1, 3, TimeUnit.SECONDS)) {log.info("向手机:{}发送短信", phone);} else {log.info("被限流了!!!!!");}}
}

3.2. 原理

参考文章:https://github.com/oneone1995/blog/issues/13

主要是通过令牌桶的方式来实现的,令牌桶会设置每间隔多少时间就生产多少令牌,比如10S生产100个令牌,只有拿到令牌才能执行业务逻辑;

具体实现的话有两个重要参数,一个是存储当前剩余的令牌数量,一个是存储每次请求的令牌的时间以及数量;

第一次请求的过来的时候,设置好剩余令牌数,并记录这次请求

第二次请求过来的时候,如果与上一次请求的时间进行比较,如果时间间隔小于令牌桶的间隔,则根据剩余令牌数判断是否可以获取令牌。如果大于令牌桶的间隔,则重置令牌数量;

3.3. 限流常见方案

TODO:理解的比较浅显,后续可以深入了解

参考文章:https://juejin.cn/post/7000152990501847048#heading-4

1. 固定窗口计数器

  • 定义:固定时间窗口计数,比如1秒10次,超过就拒绝否则计数器+1
  • 缺陷:临界问题,比如在0.8s-1s的时候来了10次请求,1s-1.2s来了10次,那其实就是0.8-1.2s有20次请求了!

2. 滑动窗口计数器

  • 定义:在固定时间窗口的基础上,把窗口分隔成n个小窗口,滑动前行,解决了临界问题
  • 缺陷:因为对超出的流量直接放弃,所以削峰填谷;
  • 实现方案:阿里的sentinel

3. 漏桶算法

  • 定义:固定桶大小,固定速度流出,请求数量超出桶大小就拒绝;
  • 缺陷:无法处理突发流量;

4. 令牌桶算法

  • 定义:固定速率生成令牌,每个请求都需要先拿到令牌才能够处理逻辑;可以处理突发流量,比如已经生产了100个令牌,突然来了100个请求,可以直接拿令牌就处理!

  • 实现方案:redisson

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

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

相关文章

Windows怎么实现虚拟IP

在做高可用架构时&#xff0c;往往需要用到虚拟IP&#xff0c;在linux上面有keepalived来实现虚拟ip的设置。在windows上面该怎么弄&#xff0c;keepalived好像也没有windows版本&#xff0c;我推荐一款浮动IP软件PanguVip&#xff0c;它可以实现windows上面虚拟ip的漂移。设置…

UE5材质之HLSL:深度

UE4/5的Custom节点&#xff1a;在VScode使用HLSL&#xff08;新手入门用&#xff09;_vscode写hlsl-CSDN博客 效果&#xff1a; 材质节点&#xff1a; 自定义节点代码&#xff1a; float3 rayStepViewDir*-1; float4 inputTexTexture2DSample(TexObject,TexObjectSampler,uv)…

JavaSE主要内容(全套超完整)

一、为什么选择Java&#xff08;Java的优势&#xff09; 1、应用面广&#xff1a; 相较于其他语言&#xff0c;Java的应用面可谓是非常广&#xff0c;这得益于他的跨平台性和其性能的稳定性。他在服务器后端&#xff0c;Android应用开发&#xff0c;大数据开发&#xf…

Jmeter性能场景设计

为什么会有性能场景设计呢&#xff1f; 相信有部分同学对场景设计优点模糊&#xff0c;前面博文提到的是场景提取 场景设计&#xff1a;在压测的过程中怎么设置线程数、Ramp-Up时间(秒)、循环次数等等 一、 性能场景分类 场景的概念&#xff1a; a. 单场景 b. 混合场景 c. 容…

开源项目-商城管理系统

哈喽,大家好,今天主要给大家带来一个开源项目-商城管理系统 商城管理系统分前后端两部分。前端主要有商品展示,我的订单,个人中心等内容;后端的主要功能包括产品管理,门店管理,会员管理,订单管理等模块 移动端页面

[OtterCTF 2018]Name Game

Name Game 题目描述&#xff1a;我们知道这个帐号登录到了一个名为Lunar-3的频道。账户名是什么&#xff1f;猜想&#xff1a;既然登陆了游戏&#xff0c;我们尝试直接搜索镜像中的字符串 Lunar-3 。 直接搜索 Lunar-3 先把字符串 重定向到 txt文件里面去然后里面查找 Lunar-3…

什么是机器学习,机器学习与人工智能的区别是什么(一)?

人工智能和计算机游戏领域的先驱阿瑟塞缪尔&#xff08;Arthur Samuel&#xff09;创造了 "机器学习"一词。他将机器学习定义为 “一个让计算机无需明确编程即可学习的研究领域” 。通俗地说&#xff0c;机器学习&#xff08;ML&#xff09;可以解释为根据计算机的经…

【RT摩拳擦掌】RT云端测试之百度天工物接入构建(设备型)

【RT摩拳擦掌】RT云端测试之百度天工物接入构建&#xff08;设备型&#xff09; 一&#xff0c; 文档介绍二&#xff0c; 物接入IOT Hub物影子构建2.1 创建设备型项目2.2 创建物模型2.3 创建物影子 三&#xff0c; MQTT fx客户端连接云端3.1 MQTT fx配置3.2 MQTT fx订阅3.3 MQT…

Linux 生产消费者模型

&#x1f493;博主CSDN主页:麻辣韭菜&#x1f493;   ⏩专栏分类&#xff1a;Linux初窥门径⏪   &#x1f69a;代码仓库:Linux代码练习&#x1f69a;   &#x1f339;关注我&#x1faf5;带你学习更多Linux知识   &#x1f51d; 前言 1. 生产消费者模型 1.1 什么是生产消…

AI复活亲人市场分析:技术、成本与伦理挑战

“起死回生”这种事&#xff0c;过去只存在于科幻电影里&#xff0c;但今年&#xff0c;被“复活”的案例却越来越多。 2月底&#xff0c;知名音乐人包晓柏利用AI“复活”了她的女儿&#xff0c;让她在妈妈生日时唱了一首生日歌&#xff1b;3月初&#xff0c;商汤科技的年会上…

grpc编译

1、cmake下载 Download CMakehttps://cmake.org/download/cmake老版本下载 Index of /fileshttps://cmake.org/files/2、gprc源码下载&#xff0c;发现CMAKE报错 3、使用git下载 1&#xff09;通过git打开一个目录&#xff1a;如下grpc将放在D盘src目录下 cd d: cd src2&am…

深入解析内容趋势:使用YouTube API获取视频数据信息

一、引言 YouTube&#xff0c;作为全球最大的视频分享平台之一&#xff0c;汇聚了无数优质的内容创作者和观众。从个人分享到专业制作&#xff0c;从教育科普到娱乐休闲&#xff0c;YouTube上的视频内容丰富多彩&#xff0c;满足了不同用户的需求。对于内容创作者、品牌以及希…

欧姆龙NJ/NX使用科伺伺服的PDO一般配置

选择单击左侧“配置和设置”&#xff0c;双击“EtherCAT”&#xff0c;选择从设备&#xff0c;单击“编辑PDO映射设置” 配置伺服所需PDO 选择单击左侧“配置和设置”下的“运动控制设置”&#xff0c;然后右键“轴设置”&#xff0c;添加“运动控制轴/单轴位置控制轴”&#x…

基于SpringBoot漫画网站系统设计和实现(源码+LW+调试文档+讲解等)

&#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN作者、博客专家、全栈领域优质创作者&#xff0c;博客之星、平台优质作者、专注于Java、小程序技术领域和毕业项目实战✌&#x1f497; &#x1f31f;文末获取源码数据库&#x1f31f; 感兴趣的可以先收藏起来&#xff0c;…

Nettyの粘包、半包问题框架解决方案自定义协议

1、Netty框架是如何解决粘包、半包问题 关于粘包&#xff0c;半包问题&#xff0c;在前面几篇中都有提及&#xff0c;我们简单的复习一下。 粘包指的是客户端发出的多条消息&#xff0c;被服务端当做一条进行接收。半包指的是客户端发出一条完整的消息&#xff0c;在传输的过程…

Redisson框架

1. Redisson锁与Redis订阅与发布模式的联系&#xff1a; Redisson锁中&#xff0c;使用订阅发布模式去通知等待锁的客户端&#xff1a;锁已经释放&#xff0c;可以进行抢锁。 publish channel_name message&#xff1a;将消息发送到指定频道 解锁时&#xff0c;在Lua解锁脚本…

如何把项目文文件/文件夹)上传到Gitee(全网最细)

目录 1、首先必须要有一个Gitee官网的账号 2、点击右上角的号&#xff0c;点击新建仓库 3、按照下图步骤&#xff0c;自己起仓库名字&#xff0c;开发语言 4、点击初始化readme文件 5、在自己的电脑上选择姚上传的文件夹&#xff0c;或者文件&#xff0c;这里都是一样的&a…

内网渗透:端口转发(SSH隧道)

SSH&#xff1a;两台设备之间进行远程登录的协议&#xff08;SSH本身就是一个隧道协议&#xff09; 远程文件传输scp命令&#xff08;scp是基于SSH的&#xff09; 拓扑&#xff1a; SSH隧道搭建的条件 1.获取到跳板机权限 2.跳板机中SSH服务启动 SSH端口转发分类&#xff1…

正点原子rk3588烧录linux和安卓镜像

1、烧录 Linux buildroot 系统镜像 1.1 进入 Loader 模式&#xff1a; 按住开发板上的 V&#xff08;音量&#xff09;按键不松&#xff0c;给开发板 上电或复位&#xff0c;此时烧录工具会提示&#xff1a;发现一个 LOADER 设备&#xff0c;表示开发板此时已经处于 Loader 模…

【爆肝34万字】从零开始学Python第2天: 判断语句【入门到放弃】

目录 前言判断语句True、False简单使用作用 比较运算符引入比较运算符的分类比较运算符的结果示例代码总结 逻辑运算符引入逻辑运算符的简单使用逻辑运算符与比较运算符一起使用特殊情况下的逻辑运算符 if 判断语句引入基本使用案例演示案例补充随堂练习 else 判断子句引入else…