Redisson(分布式锁、限流)

news2024/9/20 5:48:13

注意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.coloradmin.cn/o/1877870.html

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

相关文章

【吊打面试官系列-MyBatis面试题】MyBatis 框架适用场合?

大家好&#xff0c;我是锋哥。今天分享关于 【MyBatis 框架适用场合 &#xff1f;】面试题&#xff0c;希望对大家有帮助&#xff1b; MyBatis 框架适用场合&#xff1f; 1、MyBatis 专注于 SQL 本身&#xff0c;是一个足够灵活的 DAO 层解决方案。 2、对性能的要求很高&#…

苹果电脑移动硬盘不能写入怎么办 读取移动硬盘的磁盘管理软件 Paragon NTFS for Mac永久激活

对于使用苹果电脑的用户们&#xff0c;我们经常会使用到移动硬盘来拷贝大量的文件。一般的移动硬盘的容量都比较大&#xff0c;再加上国内大多数人使用的都是 Windows 系统&#xff0c;为了通用与方便&#xff0c;所以硬盘的分区一般都是 NTFS 格式的。对于 Windows 系统的 NTF…

LLDP 基本原理

LLDP 简介 定义 LLDP&#xff08;Link Layer Discovery Protocol&#xff0c;链路层发现协议&#xff09;是 IEEE 802.1ab 中定义的第二层发现&#xff08;Layer 2 Discovery&#xff09;协议。 LLDP 提供了一种标准的链路层发现方式&#xff0c;可以将本端设备的主要能力、…

西安高校大学智能制造实验室数字孪生可视化系统平台建设项目验收

随着工业4.0时代的到来&#xff0c;智能制造成为推动制造业转型升级的关键。为了培养学生的创新能力和实践能力&#xff0c;西安高校大学决定建设智能制造实验室&#xff0c;并引入数字孪生技术&#xff0c;构建可视化系统平台。项目旨在通过数字孪生技术&#xff0c;实现对制造…

【吴恩达深度学习笔记系列】Logistic Regression 【理论】

Binary Classification: Logistic Regression: y ^ σ ( w T x b ) \hat{y}\sigma{(w^T xb)} y^​σ(wTxb) using sigmoid function σ 1 1 e − z \sigma \frac{1}{1e^{-z}} σ1e−z1​. 【torch.sigmoid(x)】 Sigmoid ( x ) 1 1 e − x \text{Sigmoid}(x)\frac{1}{…

运维锅总详解Nginx

本文尝试从Nginx特性及优缺点、为什么具有文中所述的优缺点、Nginx工作流程、Nginx最佳实践及历史演进等角度对其进行详细分析。希望对您有所帮助。 Nginx特性及优缺点 Nginx简介 Nginx&#xff08;发音为 “engine-x”&#xff09;是一款高性能的开源Web服务器及反向代理服…

[OtterCTF 2018]Play Time

还是这个程序 。。要找到游戏名字查看 进程 psscan pstree pslist 0x000000007d686b30 Rick And Morty 3820 2728 0x000000000b59a000 2018-08-04 19:32:55 UTC0000 0x000000007d7cb740 LunarMS.exe 708 2728 0x00000000731cb000 2018-08-04 19:27:39 UTC0000…

安全架构概述_1.信息安全面临的威胁

在当今以计算机、网络和软件为载体的数字化服务几乎成为人类赖以生存的手段。与之而来的计算机犯罪呈现指数上升趋势&#xff0c;因此&#xff0c;信息的可用性、完整性、机密性、可控性和不可抵赖性等安全保障显得尤为重要&#xff0c;而满足这些诉求&#xff0c;离不开好的安…

【mysql的行记录格式】

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

操作系统期末复习考题二

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 一、前言&#x1f680;&#x1f680;&#x1f680;二、正文☀️☀️☀️三、总结&#x1f353;&#x1f353;&#x1f353; 一、前言&#x1f680;&#x1f680;&am…

算法-位运算基础

文章目录 前置知识1. 交换两个数2. 比较两个数的大小3. leetcode268 寻找缺失的数字4. leetcode136 只出现一次的数字5. leetcode260 只出现一次的数字|||6. leetcode137 只出现一次的数字||7. 2/3的幂8. 大于等于该数字的最小2的幂9. leetcode201 数字范围按位与10. 位运算中分…

JAVA笔试题目

1.标识符的使用 2.类名和java文件名的关系 3.java数据类型关系 4.循环体的考验 答案选择C&#xff0c;D的话需要在do前面加上loop:表示跳出当前循环体。 5.三元运算符的类型运算 6.局部变量的使用 这里需要注意的是c表示当前行代码还是使用原来的数值&#xff0c;下一行代码才…

fiddler抓包工具

概念 概念&#xff1a; Fiddler是一个http协议调试代理工具&#xff0c;它能够记录并检查所有你的电脑和互联网之间的http通讯。 http&#xff1a;不加密&#xff0c;端口为80 https&#xff1a;加密&#xff0c;端口为443 原理&#xff1a; 其实就在访问服务器时&#xff0…

005-GeoGebra基础篇-GeoGebra的矩形

上一篇关于点的介绍已经触及到了诸多GeoGebra的基础操作&#xff0c;这一篇我们根据画矩形&#xff0c;继续探索GeoGebra。 目录 一、最粗暴的方式绘制矩形1. 使用“Polygon”工具直接绘制2. 注意看代数列表3. 关于矩形和线段 二、用点和线段绘制矩形&#xff08;1&#xff09;…

CJSON库

目录 一、介绍 1、JSON是什么 2、为什么使用CJSON 3、JSON格式 二、使用CJSON构造JSON 1、创建对象 2、添加字段 3、转换格式 4、释放对象 三、使用CJSON解析JSON 1、解析数据 2、 获取字段 3、释放对象 一、介绍 1、JSON是什么 JSON是什么呢&#xff1f;JSON全称…

【Python3的内置函数和使用方法】

目录 Python 特点 Python 中文编码 Python 变量类型 Python列表 Python 元组 元组是另一个数据类型&#xff0c;类似于 List&#xff08;列表&#xff09; Python 字典 Python数据类型转换 Python 运算符 Python算术运算符 Python比较运算符 Python赋值运算符 Pyt…

clion ctrl+左键只能跳转到虚函数的声明处

右击函数 -> GOTO -> Definition 这样不够便捷&#xff0c;但是我没有找到更好的办法 可能是因为该函数是虚函数的重写&#xff0c;clion 无法识别出该函数是虚函数的哪个重写版&#xff0c;只能跳转到唯一的虚函数位置

FlowUs息流打造AI赋能下的知识库,信息深度挖掘与智能创作!FlowUs让你的数据资产更有价值

在AI时代的大潮中&#xff0c;FlowUs息流笔记类数据库凭借其强大的数据资产管理能力&#xff0c;正以前所未有的方式重塑着知识工作者的学习、研究与协作模式。当深厚的数据资产遇上AI的智能助力&#xff0c;无论是学术论文的撰写&#xff0c;还是高效提炼多人会议的核心观点&a…

Summaries

摘要是网格项&#xff0c;它利用聚合函数来显示有关所显示数据的摘要信息&#xff1a;总记录计数、最小值等。 GridControl-Grid View Summary Types 汇总 汇总总数&#xff08;GridSummaryItem&#xff09;是根据所有数据网格记录计算的&#xff0c;并显示在视图页脚中。启…

一文讲透大模型 Qwen2 的训练与推理

通义千问最近问鼎开源模型Top 1 &#xff0c;今天我来分享一下Qwen2系列模型&#xff0c;Qwen2系列模型是Qwen1.5系列模型的重大升级。包括了&#xff1a; 5个尺⼨的预训练和指令微调模型, 包括Qwen2-0.5B、Qwen2-1.5B、Qwen2-7B、Qwen2-57B-A14B以及Qwen2-72B&#xff1b; 在…