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;

    @Bean
    public 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 {
    @Autowired
    private 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 {
    @Autowired
    private 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的rate
        if (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.mfbz.cn/a/757368.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Java对应C++ STL的用法

sort&#xff1a; 1&#xff1a;java.util.Arrays中的静态方法Arrays.sort()方法&#xff0c;针对基本数据类型和引用对象类型的数组元素排序 2&#xff1a;java.util.Collections中的静态方法的Collections.sort()方法&#xff0c;针对集合框架中的动态数组&#xff0c;链表&…

【mysql的行记录格式】

记录头信息 除了变长字段长度列表、NULL值列表之外&#xff0c;还有一个用于描述记录的记录头信息&#xff0c;它是由固定的5个字节组成。5个字节也就是40个二进制位&#xff0c;不同的位代表不同的意思&#xff0c;如图&#xff1a; 记录的真实数据 对于record_format_demo表来…

linux中的各种指令

按文件的大小进行查找 find / usr -size 100M 在home路径下创建txt文件 touch test.txt 查看test.txt文件中的内容&#xff1a; cat test.txt通过指令pwd可以查看当前所处路径。 切换超级用户的指令&#xff1a; su - root 离开时可以使用指令&#xff1a;exit grep指…

.net 项目中配置 Swagger

一、前言 二、Swagger 三、.net 项目中添加Swagger 1、准备工作 &#xff08;1&#xff09;.net项目 &#xff08;2&#xff09;SwaggerController &#xff08;3&#xff09;XML文档注释 2、安装Swagger包 3、 添加配置swagger中间件 &#xff08;1&#xff09;添加S…

深入理解Unix/Linux中sync、fsync、fdatasync和sync_file_range系统调用以及他们的区别

前言 在linux内核中都有缓冲区或者页面高速缓存&#xff0c;大多数磁盘IO都是通过缓冲写的。当你想将数据write进文件时&#xff0c;内核通常会将该数据复制到其中一个缓冲区中&#xff0c;如果该缓冲没被写满的话&#xff0c;内核就不会把它放入到输出队列中。当这个缓冲区被…

5000字深入讲解:企业数字化转型优先从哪个板块开始?

很多企业都知道数字化转型重要&#xff0c;但不知道应该怎样入手&#xff0c;分哪些阶段。以下引用国内领先数字化服务商 织信Informat 的数字化转型方法论材料&#xff0c;且看看他们是如何看待数字化转型的&#xff1f;数字化转型应该从哪先开始&#xff1f;如何做&#xff1…

编译工具-Gradle

文章目录 Idea中配置Gradle项目project目录settings.gradlebuild.gradlegradlewgradlew.bat Gradle Build生命周期编写Settings.gradle编写Build.gradleTasksPlugins Idea中配置 配置项&#xff1a;gradle位置 及仓库位置 Gradle项目 Task&#xff0c;settings.gradle,build.…

【ai】tx2 nx:ubuntu18.04 yolov4-triton-tensorrt 成功部署server 运行

isarsoft / yolov4-triton-tensorrt运行发现插件未注册? 【ai】tx2 nx: jetson Triton Inference Server 部署YOLOv4 【ai】tx2 nx: jetson Triton Inference Server 运行YOLOv4 对main 进行了重新构建 【ai】tx2 nx :ubuntu查找NvInfer.h 路径及哪个包、查找符号【ai】tx2…

调用京灵平台接口,很详细

调用京灵平台接口&#xff0c;很详细 一、准备1、开发资源2、申请环境 二、测试接口调用1、查看接口文档2、查看示例代码3、引入对应依赖4、改造后需要的依赖5、测试调用 三、工具类1、配置dto2、公共参数dto3、请求参数dto4、响应参数dto4、调用工具类&#xff08;重要&#x…

免费翻译API及使用指南——百度、腾讯

目录 一、百度翻译API 二、腾讯翻译API 一、百度翻译API 百度翻译API接口免费翻译额度&#xff1a;标准版&#xff08;5万字符免费/每月&#xff09;、高级版&#xff08;100万字符免费/每月-需个人认证&#xff0c;基本都能通过&#xff09;、尊享版&#xff08;200万字符免…

matlab中simulink仿真软件的基础操作

&#xff08;本内容源自《详解MATLAB&#xff0f;SIMULINK 通信系统建模与仿真》 刘学勇编著的第二章内容&#xff0c;有兴趣的可以阅读该书&#xff09; 例&#xff1a;简单系统输入为两个不同频率的正弦、余弦信号&#xff0c;输出为两信号之和&#xff0c;建立模型。 在…

webpack源码深入--- webpack的编译主流程

webpack5的编译主流程 根据watch选项调用compiler.watch或者是compiler.run()方法 try {const { compiler, watch, watchOptions } create();if (watch) {compiler.watch(watchOptions, callback);} else {compiler.run((err, stats) > {compiler.close(err2 > {callb…

使用鸿蒙HarmonyOs NEXT 开发 快速开发 简单的购物车页面

目录 资源准备&#xff1a;需要准备三张照片&#xff1a;商品图、向下图标、金钱图标 1.显示效果&#xff1a; 2.源码&#xff1a; 资源准备&#xff1a;需要准备三张照片&#xff1a;商品图、向下图标、金钱图标 1.显示效果&#xff1a; 定义了一个购物车页面的布局&#x…

[方法] Unity 3D模型与骨骼动画

1. 在软件中导出3D模型 1.1 3dsmax 2014 1.1.1 TGA转PNG 3dsmax的贴图格式为tga&#xff0c;我们需要在在线格式转换中将其转换为Unity可识别的png格式。 1.1.2 模型导出 导出文件格式为fbx。在导出设置中&#xff0c;要勾选三角算法&#xff0c;取消勾选摄像机和灯光&#…

mysql解压版本安装5.7

1. 官网下载好解压版本 我这边5.7版本 https://dev.mysql.com/downloads/file/?id523570 mysql官网 创建 my.ini文件 内容如下 [client] #客户端设置&#xff0c;即客户端默认的连接参数# socket /data/mysqldata/3306/mysql.sock #用于本地连接的socket套接字 # 默…

运维锅总详解HAProxy

本文尝试从HAProxy简介、HAProxy工作流程及其与Nginx的对比对其进行详细分析&#xff1b;在本文最后&#xff0c;给出了为什么Nginx比HAProxy更受欢迎的原因。希望对您有所帮助&#xff01; HAProxy简介 HAProxy&#xff08;High Availability Proxy&#xff09;是一款广泛使…

【知识学习】阐述Unity3D中Profile和性能的概念及使用方法示例

在Unity3D中&#xff0c;"Profile"和"性能"是两个相关但不同的概念&#xff0c;它们在游戏开发中扮演着重要的角色。 Profile&#xff08;配置文件&#xff09; "Profile"在Unity中通常指的是一种配置文件&#xff0c;它包含了一系列的设置和参…

JAVA医院绩效考核管理系统源码:系统优势、系统目的、系统原则 (自主研发 功能完善 可直接上项目)

JAVA医院绩效考核管理系统源码&#xff1a;系统优势、系统目的、系统原则 &#xff08;自主研发 功能完善 可直接上项目&#xff09; 医院绩效考核系统优势 1.实现科室负责人单独考核 对科室负责人可以进行单独考核、奖金发放。 2. 科室奖金支持发放到个人 支持奖金二次分配&…

Numpy array和Pytorch tensor的区别

1.Numpy array和Pytorch tensor的区别 笔记来源&#xff1a; 1.Comparison between Pytorch Tensor and Numpy Array 2.numpy.array 4.Tensors for Neural Networks, Clearly Explained!!! 5.What is a Tensor in Machine Learning? 1.1 Numpy Array Numpy array can only h…

已解决问题 | 该扩展程序未列在 Chrome 网上应用店中,并可能是在您不知情的情况下添加的

在Chrome浏览器中&#xff0c;如果你看到“该扩展程序未列在 Chrome 网上应用店中&#xff0c;并可能是在您不知情的情况下添加的”这样的提示&#xff0c;通常是因为该扩展程序没有通过Chrome网上应用店进行安装。以下是解决这个问题的步骤&#xff1a; 解决办法&#xff1a;…