当前位置: 首页 > 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;具有强大的计算和控制能力。…...

Docker 离线安装指南

参考文章 1、确认操作系统类型及内核版本 Docker依赖于Linux内核的一些特性&#xff0c;不同版本的Docker对内核版本有不同要求。例如&#xff0c;Docker 17.06及之后的版本通常需要Linux内核3.10及以上版本&#xff0c;Docker17.09及更高版本对应Linux内核4.9.x及更高版本。…...

装饰模式(Decorator Pattern)重构java邮件发奖系统实战

前言 现在我们有个如下的需求&#xff0c;设计一个邮件发奖的小系统&#xff0c; 需求 1.数据验证 → 2. 敏感信息加密 → 3. 日志记录 → 4. 实际发送邮件 装饰器模式&#xff08;Decorator Pattern&#xff09;允许向一个现有的对象添加新的功能&#xff0c;同时又不改变其…...

《通信之道——从微积分到 5G》读书总结

第1章 绪 论 1.1 这是一本什么样的书 通信技术&#xff0c;说到底就是数学。 那些最基础、最本质的部分。 1.2 什么是通信 通信 发送方 接收方 承载信息的信号 解调出其中承载的信息 信息在发送方那里被加工成信号&#xff08;调制&#xff09; 把信息从信号中抽取出来&am…...

NFT模式:数字资产确权与链游经济系统构建

NFT模式&#xff1a;数字资产确权与链游经济系统构建 ——从技术架构到可持续生态的范式革命 一、确权技术革新&#xff1a;构建可信数字资产基石 1. 区块链底层架构的进化 跨链互操作协议&#xff1a;基于LayerZero协议实现以太坊、Solana等公链资产互通&#xff0c;通过零知…...

Android Bitmap治理全解析:从加载优化到泄漏防控的全生命周期管理

引言 Bitmap&#xff08;位图&#xff09;是Android应用内存占用的“头号杀手”。一张1080P&#xff08;1920x1080&#xff09;的图片以ARGB_8888格式加载时&#xff0c;内存占用高达8MB&#xff08;192010804字节&#xff09;。据统计&#xff0c;超过60%的应用OOM崩溃与Bitm…...

React---day11

14.4 react-redux第三方库 提供connect、thunk之类的函数 以获取一个banner数据为例子 store&#xff1a; 我们在使用异步的时候理应是要使用中间件的&#xff0c;但是configureStore 已经自动集成了 redux-thunk&#xff0c;注意action里面要返回函数 import { configureS…...

MacOS下Homebrew国内镜像加速指南(2025最新国内镜像加速)

macos brew国内镜像加速方法 brew install 加速formula.jws.json下载慢加速 &#x1f37a; 最新版brew安装慢到怀疑人生&#xff1f;别怕&#xff0c;教你轻松起飞&#xff01; 最近Homebrew更新至最新版&#xff0c;每次执行 brew 命令时都会自动从官方地址 https://formulae.…...

Oracle11g安装包

Oracle 11g安装包 适用于windows系统&#xff0c;64位 下载路径 oracle 11g 安装包...

十九、【用户管理与权限 - 篇一】后端基础:用户列表与角色模型的初步构建

【用户管理与权限 - 篇一】后端基础:用户列表与角色模型的初步构建 前言准备工作第一部分:回顾 Django 内置的 `User` 模型第二部分:设计并创建 `Role` 和 `UserProfile` 模型第三部分:创建 Serializers第四部分:创建 ViewSets第五部分:注册 API 路由第六部分:后端初步测…...

npm安装electron下载太慢,导致报错

npm安装electron下载太慢&#xff0c;导致报错 背景 想学习electron框架做个桌面应用&#xff0c;卡在了安装依赖&#xff08;无语了&#xff09;。。。一开始以为node版本或者npm版本太低问题&#xff0c;调整版本后还是报错。偶尔执行install命令后&#xff0c;可以开始下载…...