当前位置: 首页 > news >正文

Guava限流器原理浅析

文章目录

  • 基本知识
    • 限流器的类图
    • 使用示例
  • 原理解析
    • 限流整体流程
    • 问题驱动
      • 1、限流器创建的时候会初始化令牌吗?
      • 2、令牌是如何放到桶里的?
      • 3、如果要获取的令牌数大于桶里的令牌数会怎么样
      • 4、令牌数量的更新会有并发问题吗
  • 总结

实际工作中难免有限流的场景。我们熟知的限流算法有计数器限流(固定窗口、滑动窗口)算法、漏桶算法、令牌桶算法等。其具体实现也多种多样,本文就来简单窥探一下Guava的实现。

基本知识

限流器的类图

在这里插入图片描述
RateLimiter:限流器基类,定义限流器的创建、令牌的获取等操作。
SmoothRateLimiter:定义一种平滑的限流器,也是抽象类,继承RateLimiter。
SmoothBursty:普通的平滑限流器实现类,实现SmoothRateLimiter。以稳定的速率生成令牌,则会同时全部被获取到。比如令牌桶现有令牌数为5,这时连续进行10个请求,则前5个请求会全部直接通过,没有等待时间,之后5个请求则每隔200毫秒通过一次。
SmoothWarmingUp:预热的平滑限流器实现类,实现SmoothRateLimiter。随着请求量的增加,令牌生成速率会缓慢提升直到一个稳定的速率。比如令牌桶现有令牌数为5,这时连续进行10个请求,只会让第一个请求直接通过,之后的请求都会有等待时间,等待时间不断缩短,直到稳定在每隔200毫秒通过一次。这样,就会有一个预热的过程。

下文以SmoothBursty为例来分析限流原理。

使用示例

public class RateLimitTest {public static void main(String[] args) throws InterruptedException {// 1、创建限流器,一秒内最多允许2个请求通过RateLimiter rateLimiter = RateLimiter.create(2);serial(rateLimiter);}private static void serial(RateLimiter rateLimiter) throws InterruptedException {for (int i = 0; i < 10; i++) {String time = LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_TIME);// 2、尝试获取令牌,不论是否能获取到都直接返回boolean res = rateLimiter.tryAcquire();// 获取令牌,如果获取不到就一直等待// rateLimiter.acquire();if (res) {System.out.println(time + ":请求被允许");} else {System.out.println(time + ":请求被限流");}Thread.sleep(250);}}}

执行结果:

15:52:08.583:请求被允许
15:52:08.852:请求被限流
15:52:09.108:请求被允许
15:52:09.361:请求被限流
15:52:09.617:请求被允许
15:52:09.872:请求被限流
15:52:10.127:请求被允许
15:52:10.378:请求被限流
15:52:10.629:请求被允许
15:52:10.882:请求被限流

可以看到同一秒内最多只有2个请求被允许。

原理解析

限流整体流程

在这里插入图片描述

  1. 创建限流器。此时桶里的令牌数为0。设置QPS=5(每秒最多允许5个请求),这个数字“5”带表了两层含义:
    1)桶里最大只能容纳5个令牌。
    2)一秒可以生成5个令牌,生成一个令牌需要1/5=0.2秒=200毫秒。
  2. 发起请求。此时距离限流器创建已经经过了一秒,桶里应该存在5个令牌,而本次请求需要获取并消耗1个令牌。
  3. 更新令牌数量。

上面只是描述了一个大致思路,还有很多细节问题需要考虑,下文就以问题来驱动原理探究。

问题驱动

限流器关键属性解释
SmoothRateLimiter.java

/*** 当前桶中已存在的令牌数,如果请求需要的令牌数小于已存在的令牌数,就允许通过*/
double storedPermits;/*** 令牌桶可以保存的最大令牌数*/
double maxPermits;/*** 多长时间可以生成一个令牌,单位是微秒。比如RateLimiter.create(5),就意味着1秒生成5个令牌,那么生成一个令牌就需要200ms*/
double stableIntervalMicros;/*** 重要!!!下一个请求可以被允许获取令牌的时间点,单位是微秒。*/
private long nextFreeTicketMicros = 0L;

1、限流器创建的时候会初始化令牌吗?

我们从限流器的创建源码着手分析。
RateLimiter.java

public static RateLimiter create(double permitsPerSecond) {return create(permitsPerSecond, SleepingStopwatch.createFromSystemTimer());}static RateLimiter create(double permitsPerSecond, SleepingStopwatch stopwatch) {// 创建一个普通平滑限流器RateLimiter rateLimiter = new SmoothBursty(stopwatch, 1.0 /* maxBurstSeconds */);// 关键:设置限流器速率相关信息rateLimiter.setRate(permitsPerSecond);return rateLimiter;}public final void setRate(double permitsPerSecond) {checkArgument(permitsPerSecond > 0.0 && !Double.isNaN(permitsPerSecond), "rate must be positive");synchronized (mutex()) {// 关键doSetRate(permitsPerSecond, stopwatch.readMicros());}}// 由子类即SmoothRateLimiter来实现abstract void doSetRate(double permitsPerSecond, long nowMicros);

SmoothRateLimiter.java

@Overridefinal void doSetRate(double permitsPerSecond, long nowMicros) {// 重点1:生成令牌,并同步下次可以获取令牌的时间resync(nowMicros);double stableIntervalMicros = SECONDS.toMicros(1L) / permitsPerSecond;// 将stableIntervalMicros从默认的0.0设置为 生成一个令牌所需的时间this.stableIntervalMicros = stableIntervalMicros;// 重点2doSetRate(permitsPerSecond, stableIntervalMicros);}// 重点1/** 限流器创建(doSetRate(double permitsPerSecond, long nowMicros))* 以及 获取令牌(reserveEarliestAvailable(int requiredPermits, long nowMicros))的时候都会调用这个方法* 如果是创建时调用 由于coolDownIntervalMicros返回值即stableIntervalMicros=0,所以当前storedPermits的计算结果仍为0**/void resync(long nowMicros) {if (nowMicros > nextFreeTicketMicros) {// 下一次可以获取令牌的时间到现在这段时间内,需要生成多少令牌,由于当前coolDownIntervalMicros()会返回0.0,所以计算结果为Infinity(无穷)double newPermits = (nowMicros - nextFreeTicketMicros) / coolDownIntervalMicros();// 保证桶里的令牌数不能超过最大允许的令牌数,因为newPermits=无穷,所以这里计算出桶里的令牌数应该是0storedPermits = min(maxPermits, storedPermits + newPermits);// 将nextFreeTicketMicros值设为限流器创建的时间nextFreeTicketMicros = nowMicros;}}// 由子类即SmoothBursty来实现abstract void doSetRate(double permitsPerSecond, double stableIntervalMicros);static final class SmoothBursty extends SmoothRateLimiter {// 重点2@Overridevoid doSetRate(double permitsPerSecond, double stableIntervalMicros) {// 当前允许的最大令牌数,限流器创建时该值为0.0double oldMaxPermits = this.maxPermits;// 计算最新的允许的最大令牌数maxPermits = maxBurstSeconds * permitsPerSecond;if (oldMaxPermits == Double.POSITIVE_INFINITY) {// if we don't special-case this, we would get storedPermits == NaN, belowstoredPermits = maxPermits;} else {// 如果最大允许的令牌数时0,则将桶里的令牌数也置为0storedPermits =(oldMaxPermits == 0.0)? 0.0 // initial state: storedPermits * maxPermits / oldMaxPermits;}}@Overridedouble coolDownIntervalMicros() {// 返回的就是生成一个令牌需要多长时间,该值在限流器创建的时候初始值为0.0return stableIntervalMicros;}}

通过上面源码中 重点1和重点2的分析可以发现,在创建限流器的时候,当前桶中的令牌数一直是0。

结论:限流器创建的时候不会初始化令牌

2、令牌是如何放到桶里的?

我们经常看到对于令牌桶限流算法的描述是:将令牌每隔一段时间定时放入桶中。
乍一看也许需要一个定时器才能达到这个效果。但Guava的实现告诉我们其实不用这么复杂,只需要一个计数器(storedPermits)变量就能搞定。

想要知道令牌如何放到桶里,就需要从获取令牌的时候开始探索。

这有点奇怪对吗,正常是先把令牌放到桶里,然后才获取令牌,即有因才有果;但是我们却需要先知道如何获取令牌,才能知道令牌是如何放到桶里的。
在我看来,这正是Guava实现的巧妙之处。

RateLimiter.java

/**
* 尝试获取令牌
* @param permits 要获取的令牌数
* @param timeout 能获取到令牌的最大等待时间,等待时间超过这个时间就直接返回false。如果该值是0,不做任何等待,直接返回是否获取到令牌
*/
public boolean tryAcquire(int permits, long timeout, TimeUnit unit) {long timeoutMicros = max(unit.toMicros(timeout), 0);checkPermits(permits);long microsToWait;synchronized (mutex()) {long nowMicros = stopwatch.readMicros();// 判断在超时时间内能否获取到令牌if (!canAcquire(nowMicros, timeoutMicros)) {// 获取不了就返回falsereturn false;} else {// 关键:如果在超时时间内能获取到令牌,计算需要等待的时间microsToWait = reserveAndGetWaitLength(permits, nowMicros);}}// 睡眠等待足够的时间stopwatch.sleepMicrosUninterruptibly(microsToWait);return true;}private boolean canAcquire(long nowMicros, long timeoutMicros) {// 获取最早可以获得令牌的时间return queryEarliestAvailable(nowMicros) - timeoutMicros <= nowMicros;}final long reserveAndGetWaitLength(int permits, long nowMicros) {// 关键:获取令牌并返回最早能获得令牌的时间long momentAvailable = reserveEarliestAvailable(permits, nowMicros);return max(momentAvailable - nowMicros, 0);}// 由子类即SmoothBursty实现abstract long queryEarliestAvailable(long nowMicros);// 由子类即SmoothBursty实现abstract long reserveEarliestAvailable(int permits, long nowMicros);

SmoothBursty.java

final long queryEarliestAvailable(long nowMicros) {// 又是它!!!待会分析它到底是个什么东西return nextFreeTicketMicros;}/*** 获取令牌的核心方法** @param requiredPermits 需要获取的令牌数* @param nowMicros* @return*/@Overridefinal long reserveEarliestAvailable(int requiredPermits, long nowMicros) {// 关键:生成令牌,并将下一次可以获取令牌的时间设置为当前时间resync(nowMicros);// 这里拿到的是最早可以获取到令牌的时间long returnValue = nextFreeTicketMicros;// 实际能获取的令牌数,有可能需要的令牌数大于当前桶里的令牌数,两者取最小double storedPermitsToSpend = min(requiredPermits, this.storedPermits);// 实际拿到的令牌数相比需要的令牌数还差多少double freshPermits = requiredPermits - storedPermitsToSpend;// 要拿到还差的令牌数,还需要等多久long waitMicros =storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend)+ (long) (freshPermits * stableIntervalMicros);// 重点3:更新下一次可以获取令牌的时间 = 当前时间 + 要拿到还差的令牌数要等的时间this.nextFreeTicketMicros = LongMath.saturatedAdd(nextFreeTicketMicros, waitMicros);// 重点4:更新桶里还剩的令牌数this.storedPermits -= storedPermitsToSpend;return returnValue;}void resync(long nowMicros) {if (nowMicros > nextFreeTicketMicros) {// 下一次可以获取令牌的时间到现在这段时间内,需要生成多少令牌double newPermits = (nowMicros - nextFreeTicketMicros) / coolDownIntervalMicros();// 重点1:生成令牌并放入桶中storedPermits = min(maxPermits, storedPermits + newPermits);// 重点2:将nextFreeTicketMicros值设为当前时间nextFreeTicketMicros = nowMicros;}}

通过上面源码中的重点1、重点2、重点3、重点4可以发现:

  • 重点1是向桶里放令牌,既增加令牌计数器storedPermits
  • 重点4是从桶里获取令牌,既减少令牌计数器storedPermits
  • 重点2和重点3都是更新nextFreeTicketMicros

所以令牌的生成、获取都围绕着两个变量:storedPermits(当前桶里的令牌数)和nextFreeTicketMicros(下次可以获得令牌的时间)。

而这两个变量也正是Guava限流设计的巧妙之处:不必提前向桶里放入令牌,或通过一个单独的定时器向桶里放令牌,而是在获取令牌的时候增加令牌数量再减少令牌数量。

用图来更加直观的体现这里的逻辑。

nextFreeTicketMicros在源码中其实是用微秒级时间戳表示,为了方便理解,下面就用正常时间来表示。

在这里插入图片描述

  1. 创建限流器。RateLimiter rateLimiter = RateLimiter.create(5);即QPS=5,每秒生成5个令牌,生成1个令牌需要200毫秒,桶内最大令牌数=5。storedPermits(此时桶里的令牌数)=0,nextFreeTicketMicros(下次可以获取令牌的时间)=0。
  2. 请求A要获取1个令牌。rateLimiter.acquire();当前时间是2023-9-26 10:00:00。
  3. 发现当前时间 > nextFreeTicketMicros,两者相差的这段时间远远大于1秒,而1秒可以生成5个令牌(最多也只能存5个)。同时要把nextFreeTicketMicros设置为当前时间,意味着现在桶里已经有令牌了,现在马上就可以获取到令牌。此时storedPermits=5,nextFreeTicketMicros=2023-9-26 10:00:00。
  4. 获取到1个令牌,此时storedPermits=4,nextFreeTicketMicros=2023-9-26 10:00:00。
  5. 请求B要获取10个令牌。rateLimiter.acquire(10);当前时间是2023-9-26 10:00:01.001。
  6. 发现当前时间 > nextFreeTicketMicros,两者相差的这段时间大于1秒,1秒可以生成5个令牌,当前桶里还有4个,5+4=9,但桶最多只能存5个。同时要把nextFreeTicketMicros设置为当前时间,意味着现在桶里已经有令牌了,现在马上就可以获取到令牌。此时storedPermits=5,nextFreeTicketMicros=2023-9-26 10:00:01.001。
  7. 需要获取10个令牌,但是现在桶里只有5个,即使全部获取还欠5个,那就提前透支5个咯。意味着接下来这1秒生成的5个令牌是预留给当前请求的,其它请求1秒后才能再获取令牌。此时storedPermits=0,nextFreeTicketMicros=2023-9-26 10:00:02.001。
  8. 请求C要获取1个令牌。rateLimiter.acquire();当前时间是2023-9-26 10:00:01.999。
  9. 由于nextFreeTicketMicros=2023-9-26 10:00:02.001。还没到下次可以获取令牌的时间,就只能等待。
  10. 等待ing …
  11. 当前时间是2023-9-26 10:00:02.200。当前时间 > nextFreeTicketMicros,相差的这段时间是200毫秒,刚好能生成1个令牌。同时要把nextFreeTicketMicros设置为当前时间,意味着现在桶里已经有令牌了,现在马上就可以获取到令牌。此时storedPermits=1,nextFreeTicketMicros=2023-9-26 10:00:02.200。
  12. 获取到1个令牌,此时storedPermits=0,nextFreeTicketMicros=2023-9-26 10:02:200。

结论:令牌的生成其实是在令牌的获取逻辑中。

3、如果要获取的令牌数大于桶里的令牌数会怎么样

经过上面的分析可以得出结论:会透支/预支不足的令牌数。

4、令牌数量的更新会有并发问题吗

可以看一下获取令牌时的源码:

public double acquire(int permits) {long microsToWait = reserve(permits);stopwatch.sleepMicrosUninterruptibly(microsToWait);return 1.0 * microsToWait / SECONDS.toMicros(1L);}final long reserve(int permits) {checkPermits(permits);// 这里已经加了同步处理synchronized (mutex()) {return reserveAndGetWaitLength(permits, stopwatch.readMicros());}}

结论:同一个限流器不会有并发问题。

总结

本文并不过多深度剖析源码和原理。旨在以初学者的角度窥探Guava限流器的限流实现思路,并解答一些理解中存在的疑惑。

尤其是令牌生成和获取的设计思路也能对自己的日常工作有启发作用~

相关文章:

Guava限流器原理浅析

文章目录 基本知识限流器的类图使用示例 原理解析限流整体流程问题驱动1、限流器创建的时候会初始化令牌吗&#xff1f;2、令牌是如何放到桶里的&#xff1f;3、如果要获取的令牌数大于桶里的令牌数会怎么样4、令牌数量的更新会有并发问题吗 总结 实际工作中难免有限流的场景。…...

第四十二章 持久对象和SQL - 用于创建持久类和表的选项

文章目录 第四十二章 持久对象和SQL - 用于创建持久类和表的选项用于创建持久类和表的选项访问数据 第四十二章 持久对象和SQL - 用于创建持久类和表的选项 用于创建持久类和表的选项 要创建持久类及其对应的 SQL 表&#xff0c;可以执行以下任一操作&#xff1a; 使用 IDE …...

集合-ArrayList源码分析(面试)

系列文章目录 1.集合-Collection-CSDN博客​​​​​​ 2.集合-List集合-CSDN博客 3.集合-ArrayList源码分析(面试)_喜欢吃animal milk的博客-CSDN博客 目录 系列文章目录 前言 一 . 什么是ArrayList? 二 . ArrayList集合底层原理 总结 前言 大家好,今天给大家讲一下Arra…...

跨类型文本文件,反序列化与类型转换的思考

文章目录 应用场景序列化 - 对象替换原内容&#xff0c;方便使用编写程序取得结果数组 序列化 - JSON 应用场景 在编写热更新的时候&#xff0c;我发现了一个古早的 ini 文件&#xff0c;记录了许多有用的数据 由于使用的语言年份较新&#xff0c;没有办法较好地对 ini 文件的…...

ubuntu20安装nvidia驱动

1. 查看显卡型号 lspci | grep -i nvidia 我的输出&#xff1a; 01:00.0 VGA compatible controller: NVIDIA Corporation GP104 [GeForce GTX 1080] (rev a1) 01:00.1 Audio device: NVIDIA Corporation GP104 High Definition Audio Controller (rev a1) 07:00.0 VGA comp…...

gma 2 成书计划

随着 gma 2 整体构建完成。下一步计划针对库内所有功能完成一个用户指南&#xff08;非网站&#xff09;。 封皮 主要章节 章节完成度相关链接第 1 章 GMA 概述已完成第 2 章 地理空间数据操作已完成第 3 章 坐标参考系统已完成第 4 章 地理空间制图已完成第 5 章 数学运算模…...

从零手搓一个【消息队列】项目设计、需求分析、模块划分、目录结构

文章目录 一、需求分析1, 项目简介2, BrokerServer 核心概念3, BrokerServer 提供的核心 API4, 交换机类型5, 持久化存储6, 网络通信7, TCP 连接的复用8, 需求分析小结 二、模块划分三、目录结构 提示&#xff1a;是正在努力进步的小菜鸟一只&#xff0c;如有大佬发现文章欠佳之…...

【Spring Cloud】深入探索 Nacos 注册中心的原理,服务的注册与发现,服务分层模型,负载均衡策略,微服务的权重设置,环境隔离

文章目录 前言一、初识 Nacos 注册中心1.1 什么是 Nacos1.2 Nacos 的安装&#xff0c;配置&#xff0c;启动 二、服务的注册与发现三、Nacos 服务分层模型3.1 Nacos 的服务分级存储模型3.2 服务跨集群调用问题3.3 服务集群属性设置3.4 修改负载均衡策略为集群策略 四、根据服务…...

No156.精选前端面试题,享受每天的挑战和学习

🤍 前端开发工程师(主业)、技术博主(副业)、已过CET6 🍨 阿珊和她的猫_CSDN个人主页 🕠 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 🍚 蓝桥云课签约作者、已在蓝桥云课上架的前后端实战课程《Vue.js 和 Egg.js 开发企业级健康管理项目》、《带你从入…...

如何在PIL图像和PyTorch Tensor之间进行相互转换,使用pytorch进行PIL和tensor之间的数据转换

目录 引言PIL简介PyTorch和Torchvision简介PIL转换为TensorTensor转换为PIL实例代码和解释结论参考文献 &#x1f4dd; 引言 在计算机视觉领域&#xff0c;使用图像处理库对图像进行预处理是非常常见的。其中&#xff0c;Python Imaging Library&#xff08;PIL&#xff09;以…...

STM32F4X UCOSIII任务消息队列

STM32F4X UCOSIII任务消息队列 任务消息队列和内核消息队列对比内核消息队列内核消息队列 UCOSIII任务消息队列API任务消息队列发送函数任务消息队列接收函数 UCOSIII任务消息队列例程 之前的章节中讲解过消息队列这个机制&#xff0c;UCOSIII除了有内核消息队列之外&#xff0…...

8个居家兼职,帮助自己在家搞副业

越来越多的人开始追求居家工作的机会&#xff0c;无论是为了获得更多收入以改善生活质量&#xff0c;还是为了更好地平衡工作和家庭的关系&#xff0c;居家兼职已成为一种趋势。而在家中从事副业不仅能够为我们带来额外的收入&#xff0c;更重要的是&#xff0c;它可以让我们在…...

管理与系统思维

技术管理者不仅仅需要做事情&#xff0c;还需要以系统思维的方式推动组织变革&#xff0c;从而帮助团队和个人做到更好。原文: Management and Systems Thinking 图片来源: Dall-E "除非管理者考虑到组织的系统性&#xff0c;否则大多数提高绩效的努力都将注定失败。"…...

电死人的是电流还是电压?

先说答案&#xff0c;是电流。 这个有两个派别&#xff0c;一个是电流派&#xff0c;一个是电压派。 举个例子&#xff0c;拿我们的头发或者指甲之类的高电阻物质去接触高压&#xff0c;你会发现基本没有什么作用&#xff1b;还有就是冬天我们脱毛衣的时候&#xff0c;噼里啪啦…...

mac 编译问题记录

1、mac 编译提示 Unsupported option ‘--no-pie‘ Linux 上用 --no-pie mac 上用 -no-pie 2、mac 找不到 malloc.h 使用 #include <sys/malloc.h> Mac上使用malloc函数报错_mac malloc.h-CSDN博客...

centos 7.9同时安装JDK1.8和openjdk11两个版本

1.使用的原因 在服务器上&#xff0c;有些情况因为有一些系统比较老&#xff0c;所以需要使用JDK8版本&#xff0c;但随着时间的发展&#xff0c;新的软件出来&#xff0c;一般都会使用比较新的JDK版本。所以就出现了我们标题的需求&#xff0c;一个系统内同时安装两个不同的版…...

【JavaEE】HTML

JavaWeb HTML 超文本标记语言 超文本&#xff1a;文本、声音、图片、视频、表格、连接标记&#xff1a;有许许多多的标签组成 vscode开发工具搭建 因为我使用的IDEA是社区版&#xff0c;代码高亮补全缩进都有些问题&#xff0c;使用vscode是最好的选择~ 安装 Visual Stu…...

【数据结构--八大排序】之堆排序

&#x1f490; &#x1f338; &#x1f337; &#x1f340; &#x1f339; &#x1f33b; &#x1f33a; &#x1f341; &#x1f343; &#x1f342; &#x1f33f; &#x1f344;&#x1f35d; &#x1f35b; &#x1f364; &#x1f4c3;个人主页 &#xff1a;阿然成长日记 …...

c# 中的类

反射 Activator.CreateInstance class Program {static void Main(string[] args){//反射Type t typeof(Student);object o Activator.CreateInstance(t, 1, "FJ");Student stu o as Student;Console.WriteLine(stu.Name);//动态编程dynamic stu2 Activator.Cre…...

基于单片机的煤气泄漏检测报警装置设计

一、项目介绍 煤气泄漏是一种常见的危险情况&#xff0c;可能导致火灾、爆炸和人员伤亡。为了及时发现煤气泄漏并采取相应的安全措施&#xff0c;设计了一种基于单片机的煤气泄漏检测报警装置。 主控芯片采用STM32F103C8T6作为主控芯片&#xff0c;具有强大的计算和控制能力。…...

智慧医疗能源事业线深度画像分析(上)

引言 医疗行业作为现代社会的关键基础设施,其能源消耗与环境影响正日益受到关注。随着全球"双碳"目标的推进和可持续发展理念的深入,智慧医疗能源事业线应运而生,致力于通过创新技术与管理方案,重构医疗领域的能源使用模式。这一事业线融合了能源管理、可持续发…...

【Linux】C语言执行shell指令

在C语言中执行Shell指令 在C语言中&#xff0c;有几种方法可以执行Shell指令&#xff1a; 1. 使用system()函数 这是最简单的方法&#xff0c;包含在stdlib.h头文件中&#xff1a; #include <stdlib.h>int main() {system("ls -l"); // 执行ls -l命令retu…...

Mybatis逆向工程,动态创建实体类、条件扩展类、Mapper接口、Mapper.xml映射文件

今天呢&#xff0c;博主的学习进度也是步入了Java Mybatis 框架&#xff0c;目前正在逐步杨帆旗航。 那么接下来就给大家出一期有关 Mybatis 逆向工程的教学&#xff0c;希望能对大家有所帮助&#xff0c;也特别欢迎大家指点不足之处&#xff0c;小生很乐意接受正确的建议&…...

汽车生产虚拟实训中的技能提升与生产优化​

在制造业蓬勃发展的大背景下&#xff0c;虚拟教学实训宛如一颗璀璨的新星&#xff0c;正发挥着不可或缺且日益凸显的关键作用&#xff0c;源源不断地为企业的稳健前行与创新发展注入磅礴强大的动力。就以汽车制造企业这一极具代表性的行业主体为例&#xff0c;汽车生产线上各类…...

[Java恶补day16] 238.除自身以外数组的乘积

给你一个整数数组 nums&#xff0c;返回 数组 answer &#xff0c;其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法&#xff0c;且在 O(n) 时间复杂度…...

智能分布式爬虫的数据处理流水线优化:基于深度强化学习的数据质量控制

在数字化浪潮席卷全球的今天&#xff0c;数据已成为企业和研究机构的核心资产。智能分布式爬虫作为高效的数据采集工具&#xff0c;在大规模数据获取中发挥着关键作用。然而&#xff0c;传统的数据处理流水线在面对复杂多变的网络环境和海量异构数据时&#xff0c;常出现数据质…...

Maven 概述、安装、配置、仓库、私服详解

目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

如何在网页里填写 PDF 表格?

有时候&#xff0c;你可能希望用户能在你的网站上填写 PDF 表单。然而&#xff0c;这件事并不简单&#xff0c;因为 PDF 并不是一种原生的网页格式。虽然浏览器可以显示 PDF 文件&#xff0c;但原生并不支持编辑或填写它们。更糟的是&#xff0c;如果你想收集表单数据&#xff…...

以光量子为例,详解量子获取方式

光量子技术获取量子比特可在室温下进行。该方式有望通过与名为硅光子学&#xff08;silicon photonics&#xff09;的光波导&#xff08;optical waveguide&#xff09;芯片制造技术和光纤等光通信技术相结合来实现量子计算机。量子力学中&#xff0c;光既是波又是粒子。光子本…...

GruntJS-前端自动化任务运行器从入门到实战

Grunt 完全指南&#xff1a;从入门到实战 一、Grunt 是什么&#xff1f; Grunt是一个基于 Node.js 的前端自动化任务运行器&#xff0c;主要用于自动化执行项目开发中重复性高的任务&#xff0c;例如文件压缩、代码编译、语法检查、单元测试、文件合并等。通过配置简洁的任务…...