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

一个 SpringBoot 项目能同时处理多少请求?

目录

1 问题分析

2 Demo

3 答案

4 怎么来的?

5 标准答案及影响参数一Tomcat配置

6 影响参数二 Web容器

7 影响参数三 Async


1 问题分析

一个 SpringBoot 项目能同时处理多少请求?

不知道你听到这个问题之后的第一反应是什么?

我大概知道他要问的是哪个方向,但是对于这种只有一句话的面试题,我的第一反应是:会不会有坑?

所以并不会贸然答题,先追问一些消息,比如:这个项目具体是干什么的?项目大概进行了哪些参数配置?使用的 web 容器(tomcat、Jetty)是什么?部署的服务器配置如何?有哪些接口?接口响应平均时间大概是多少?

这样,在几个问题的拉扯之后,至少在面试题考察的方向方面能基本和面试官达成了一致。

比如前面的面试问题,经过几次拉扯之后,面试官可能会修改为:

一个 SpringBoot 项目,未进行任何特殊配置,全部采用默认设置,这个项目同一时刻,最多能同时处理多少请求?

能处理多少呢?

我也不知道,但是当问题变成上面这样之后,我找到了探索答案的角度。

既然“未进行任何特殊配置”,那我自己搞个 Demo 出来,压一把不就完事了吗?

坐稳扶好,准备发车。

https://why-image-1300252878.cos.ap-chengdu.myqcloud.com/img/79/20230716173852.png

2 Demo

小手一抖,先搞个 Demo 出来。

这个 Demo 非常的简单,就是通过 idea 创建一个全新的 SpringBoot 项目就行。

我的 SpringBoot 版本使用的是 2.7.13

整个项目只有这两个依赖:

https://why-image-1300252878.cos.ap-chengdu.myqcloud.com/img/79/20230716105209.png

整个项目也只有两个类,要得就是一个空空如也,一清二白。

https://why-image-1300252878.cos.ap-chengdu.myqcloud.com/img/79/20230716104148.png

项目中的 TestController,里面只有一个 getTest 方法,用来测试,方法里面接受到请求之后直接 sleep 一小时。

目的就是直接把当前请求线程占着,这样我们才能知道项目中一共有多少个线程可以使用:

@Slf4j
@RestController
public class TestController {@GetMapping("/getTest")public void getTest(int num) throws Exception {log.info("{} 接受到请求:num={}", Thread.currentThread().getName(), num);TimeUnit.HOURS.sleep(1);}
}

项目中的 application.properties 文件也是空的:

https://why-image-1300252878.cos.ap-chengdu.myqcloud.com/img/79/20230716104407.png

这样,一个“未进行任何特殊配置”的 SpringBoot 不就有了吗?

基于这个 Demo,前面的面试题就要变成了:我短时间内不断的调用这个 Demo 的 getTest 方法,最多能调用多少次?

问题是不是又变得更加简单了一点?

那么前面这个“短时间内不断的调用”,用代码怎么表示呢?

很简单,就是在循环中不断的进行接口调用就行了。

public class MainTest {public static void main(String[] args) {for (int i = 0; i < 1000; i++) {int finalI = i;new Thread(() -> {HttpUtil.get("127.0.0.1:8080/getTest?num=" + finalI);}).start();}//阻塞主线程Thread.yield();}
}

当然了,这个地方你用一些压测工具,比如 jmeter 啥的,会显得逼格更高,更专业。我这里就偷个懒,直接上代码了。

3 答案

经过前面的准备工作,Demo 和测试代码都就绪了。

接下来就是先把 Demo 跑起来:

https://why-image-1300252878.cos.ap-chengdu.myqcloud.com/img/79/20230716111627.png

然后跑一把 MainTest。

当 MainTest 跑起来之后,Demo 这边就会快速的、大量的输出这样的日志:

https://why-image-1300252878.cos.ap-chengdu.myqcloud.com/img/79/20230716111730.png

也就是我前面 getTest 方法中写的日志:

https://why-image-1300252878.cos.ap-chengdu.myqcloud.com/img/79/20230716111854.png

好,现在我们回到这个问题:

我短时间内不断的调用这个 Demo 的 getTest 方法,最多能调用多少次?

来,请你告诉我怎么得到这个问题的答案?

我这里就是一个大力出奇迹,直接统计“接受到请求”关键字在日志中出现的次数就行了:

https://why-image-1300252878.cos.ap-chengdu.myqcloud.com/img/79/20230716112140.png

很显然,答案就是:

https://why-image-1300252878.cos.ap-chengdu.myqcloud.com/img/79/20230716174117.png

所以,当面试官问你:一个 SpringBoot 项目能同时处理多少请求?

你装作仔细思考之后,笃定的说:200 次。

面试官微微点头,并等着你继续说下去。

你也暗自欢喜,幸好看了这篇文章,背了个答案。然后等着面试官继续问其他问题。

气氛突然就尴尬了起来。

接着,你就回家等通知了。

https://why-image-1300252878.cos.ap-chengdu.myqcloud.com/img/79/20230716174231.png

200 次,这个回答是对的,但是你只说 200 次,这个回答就显得有点尬了。

重要的是,这个值是怎么来的?

4 怎么来的?

在开始探索怎么来的之前,我先问你一个问题,这个 200 个线程,是谁的线程,或者说是谁在管理这个线程?

是 SpringBoot 吗?

肯定不是,SpringBoot 并不是一个 web 容器。

应该是 Tomcat 在管理这 200 个线程。

这一点,我们通过线程 Dump 也能进行验证:

https://why-image-1300252878.cos.ap-chengdu.myqcloud.com/img/79/20230716113207.png

https://why-image-1300252878.cos.ap-chengdu.myqcloud.com/img/79/20230716113346.png

通过线程 Dump 文件,我们可以知道,大量的线程都在 sleep 状态。而点击这些线程,查看其堆栈消息,可以看到 Tomcat、threads、ThreadPoolExecutor 等关键字:

at org.apache.Tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1791) at org.apache.Tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) at org.apache.Tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) at org.apache.Tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) at org.apache.Tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)

基于“短时间内有 200 个请求被立马处理的”这个现象,结合你背的滚瓜烂熟的、非常扎实的线程池知识,你先大胆的猜一个:Tomcat 默认核心线程数是 200。

接下来,我们就是要去源码里面验证这个猜测是否正确了。

其中最重要的一条就是打一个有效的断点,然后基于断点处的调用栈去定位源码。

这里我再教你一个不用打断点也能获取到调用栈的方法。

在前面已经展示过了,就是线程 Dump。

右边就是一个线程完整的调用栈:

https://why-image-1300252878.cos.ap-chengdu.myqcloud.com/img/79/20230716113346.png

从这个调用栈中,由于我们要找的是 Tomcat 线程池相关的源码,所以第一次出现相关关键字的地方就是这一行:

org.apache.Tomcat.util.threads.ThreadPoolExecutor.Worker#run

https://why-image-1300252878.cos.ap-chengdu.myqcloud.com/img/79/20230716120846.png

然后我们在这一行打上断点。

重启项目,开始调试。

进入 runWorker 之后,这部分代码看起来就非常眼熟了:

https://why-image-1300252878.cos.ap-chengdu.myqcloud.com/img/79/20230716132249.png

简直和 JDK 里面的线程池源码一模一样。

随着断点往下走,在 getTask 方法里面,可以看到关于线程池的几个关键参数:

org.apache.Tomcat.util.threads.ThreadPoolExecutor#getTask

https://why-image-1300252878.cos.ap-chengdu.myqcloud.com/img/79/20230716132830.png

corePoolSize,核心线程数,值为 10。

maximumPoolSize,最大线程数,值为 200。

而且基于 maximumPoolSize 这个参数,你往前翻代码,会发现这个默认值就是 200:

https://why-image-1300252878.cos.ap-chengdu.myqcloud.com/img/79/20230716164409.png

好,到这里,你发现你之前猜测的“Tomcat 默认核心线程数是 200”是不对的。

但是你一点也不慌,再次结合你背的滚瓜烂熟的、非常扎实的线程池知识。

并在心里又默念了一次:当线程池接受到任务之后,先启用核心线程数,再使用队列长度,最后启用最大线程数。

因为我们前面验证了,Tomcat 可以同时间处理 200 个请求,而它的线程池核心线程数只有 10,最大线程数是 200。

这说明,我前面这个测试用例,把队列给塞满了,从而导致 Tomcat 线程池启用了最大线程数:

https://why-image-1300252878.cos.ap-chengdu.myqcloud.com/img/79/20230716134005.png

嗯,一定是这样的!

那么,现在的关键问题就是:Tomcat 线程池默认的队列长度是多少呢?

在当前的这个 Debug 模式下,队列长度可以通过 Alt+F8 进行查看:

https://why-image-1300252878.cos.ap-chengdu.myqcloud.com/img/79/20230716133352.png

wc,这个值是 Integer.MAX_VALUE,这么大?

我一共也才 1000 个任务,不可能被占满啊?

一个线程池:

  • 核心线程数,值为 10。
  • 最大线程数,值为 200。
  • 队列长度,值为 Integer.MAX_VALUE。

1000 个比较耗时的任务过来之后,应该是只有 10 个线程在工作,然后剩下的 990 个进队列才对啊?

难道我八股文背错了?

这个时候不要慌,嗦根辣条冷静一下。

https://why-image-1300252878.cos.ap-chengdu.myqcloud.com/img/79/20230716174538.png

目前已知的是核心线程数,值为 10。这 10 个线程的工作流程是符合我们认知的。

但是第 11 个任务过来的时候,本应该进入队列去排队。

现在看起来,是直接启用最大线程数了

所以,我们先把测试用例修改一下:

https://why-image-1300252878.cos.ap-chengdu.myqcloud.com/img/79/20230716140504.png

那么问题就来了:最后一个请求到底是怎么提交到线程池里面的?

前面说了,Tomcat 的线程池源码和 JDK 的基本一样。

往线程池里面提交任务的时候,会执行 execute 这个方法:

org.apache.Tomcat.util.threads.ThreadPoolExecutor#execute(java.lang.Runnable)

https://why-image-1300252878.cos.ap-chengdu.myqcloud.com/img/79/20230716141941.png

对于 Tomcat 它会调用到 executeInternal 这个方法:

org.apache.Tomcat.util.threads.ThreadPoolExecutor#executeInternal

https://why-image-1300252878.cos.ap-chengdu.myqcloud.com/img/79/20230716141541.png

这个方法里面,标号为 ① 的地方,就是判断当前工作线程数是否小于核心线程数,小于则直接调用 addWorker 方法,创建线程。

标号为 ② 的地方主要是调用了 offer 方法,看看队列里面是否还能继续添加任务。

如果不能继续添加,说明队列满了,则来到标号为 ③ 的地方,看看是否能执行 addWorker 方法,创建非核心线程,即启用最大线程数。

把这个逻辑捋顺之后,接下来我们应该去看哪部分的代码,就很清晰了。

主要就是去看 workQueue.offer(command) 这个逻辑。

如果返回 true 则表示加入到队列,返回 false 则表示启用最大线程数嘛。

这个 workQueue 是 TaskQueue,看起来一点也不眼熟:

当然不眼熟了,因为这个是 Tomcat 自己基于 LinkedBlockingQueue 搞的一个队列。

https://why-image-1300252878.cos.ap-chengdu.myqcloud.com/img/79/20230716142935.png

问题的答案就藏在 TaskQueue 的 offer 方法里面。

https://why-image-1300252878.cos.ap-chengdu.myqcloud.com/img/79/20230716174728.png

所以我重点带你盘一下这个 offer 方法:

org.apache.Tomcat.util.threads.TaskQueue#offer

https://why-image-1300252878.cos.ap-chengdu.myqcloud.com/img/79/20230716143324.png

标号为 ① 的地方,判断了 parent 是否为 null,如果是则直接调用父类的 offer 方法。说明要启用这个逻辑,我们的 parent 不能为 null。

那么这个 parent 是什么玩意,从哪里来的呢?

https://why-image-1300252878.cos.ap-chengdu.myqcloud.com/img/79/20230716143607.png

parent 就是 Tomcat 线程池,通过其 set 方法可以知道,是在线程池完成初始化之后,进行了赋值。

也就是说,你可以理解为,在 Tomcat 的场景下,parent 不会为空。

标号为 ② 的地方,调用了 getPoolSizeNoLock 方法:

https://why-image-1300252878.cos.ap-chengdu.myqcloud.com/img/79/20230716144049.png

这个方法是获取当前线程池中有多个线程。

所以如果这个表达式为 true:

parent.getPoolSizeNoLock() == parent.getMaximumPoolSize()

就表明当前线程池的线程数已经是配置的最大线程数了,那就调用 offer 方法,把当前请求放到到队列里面去。

标号为 ③ 的地方,是判断已经提交到线程池里面待执行或者正在执行的任务个数,是否比当前线程池的线程数还少。

如果是,则说明当前线程池有空闲线程可以执行任务,则把任务放到队列里面去,就会被空闲线程给取走执行。

然后,关键的来了,标号为 ④ 的地方。

如果当前线程池的线程数比线程池配置的最大线程数还少,则返回 false。

前面说了,offer 方法返回 false,会出现什么情况?

https://why-image-1300252878.cos.ap-chengdu.myqcloud.com/img/79/20230716141541.png

是不是直接开始到上图中标号为 ③ 的地方,去尝试添加非核心线程了?

也就是启用最大线程数这个配置了。

所以,朋友们,这个是什么情况?

这个情况确实就和我们背的线程池的八股文不一样了啊。

JDK 的线程池,是先使用核心线程数配置,接着使用队列长度,最后再使用最大线程配置。

Tomcat 的线程池,就是先使用核心线程数配置,再使用最大线程配置,最后才使用队列长度。

所以,以后当面试官给你说:我们聊聊线程池的工作机制吧?

你就先追问一句:你是说的 JDK 的线程池呢还是 Tomcat 的线程池呢,因为这两个在运行机制上有一点差异。

然后,你就看他的表情。

如果透露出一丝丝迟疑,然后轻描淡写的说一句:那就对比着说一下吧。

那么恭喜你,在这个题目上开始掌握了一点主动权。

最后,为了让你更加深刻的理解到 Tomcat 线程池和 JDK 线程池的不一样,我给你搞一个直接复制过去就能运行的代码。

当你把 taskqueue.setParent(executor) 这行代码注释掉的时候,它的运行机制就是 JDK 的线程池。

当存在这行代码的时候,它的运行机制就变成了 Tomcat 的线程池。

Tomcat线程池demo

        import org.apache.tomcat.util.threads.TaskQueue;import org.apache.tomcat.util.threads.TaskThreadFactory;import org.apache.tomcat.util.threads.ThreadPoolExecutor;import java.util.concurrent.TimeUnit;public class TomcatThreadPoolExecutorTest {public static void main(String[] args) throws InterruptedException {String namePrefix = "xxx-exec-";boolean daemon = true;TaskQueue taskqueue = new TaskQueue(300);TaskThreadFactory tf = new TaskThreadFactory(namePrefix, daemon, Thread.NORM_PRIORITY);ThreadPoolExecutor executor = new ThreadPoolExecutor(5,150, 60000, TimeUnit.MILLISECONDS, taskqueue, tf);taskqueue.setParent(executor);for (int i = 0; i < 300; i++) {try {executor.execute(() -> {logStatus(executor, "创建任务");try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}});} catch (Exception e) {e.printStackTrace();}}Thread.currentThread().join();}private static void logStatus(ThreadPoolExecutor executor, String name) {TaskQueue queue = (TaskQueue) executor.getQueue();System.out.println(Thread.currentThread().getName() + "-" + name + "-:" +"核心线程数:" + executor.getCorePoolSize() +"\t活动线程数:" + executor.getActiveCount() +"\t最大线程数:" + executor.getMaximumPoolSize() +"\t总任务数:" + executor.getTaskCount() +"\t当前排队线程数:" + queue.size() +"\t队列剩余大小:" + queue.remainingCapacity());}
}

5 标准答案及影响参数一Tomcat配置

如果你之前确实没了解过 Tomcat 线程池的工作机制,那么看到这里的时候也许你会觉得确实是有一点点收获。

但是,注意我要说但是了。

还记得最开始的时候面试官的问题吗?

面试官的原问题就是:一个 SpringBoot 项目能同时处理多少请求?

那么请问,前面我讲了这么大一坨 Tomcat 线程池运行原理,这个回答,和这个问题匹配吗?

是的,除了最开始提出的 200 这个数值之外,并不匹配,甚至在面试官的眼里完全是答非所问了。

所以,为了把这两个“并不匹配”的东西比较顺畅的链接起来,你必须要先回答面试官的问题,然后再开始扩展。

比如这样答:一个未进行任何特殊配置,全部采用默认设置的 SpringBoot 项目,这个项目同一时刻最多能同时处理多少请求,取决于我们使用的 web 容器,而 SpringBoot 默认使用的是 Tomcat

Tomcat 的默认核心线程数是 10,最大线程数 200,队列长度是Int最大值。但是由于其运行机制和 JDK 线程池不一样,在核心线程数满了之后,会直接启用最大线程数。所以,在默认的配置下,同一时刻,可以处理 200 个请求。

在实际使用过程中,应该基于服务实际情况和服务器配置等相关消息,对该参数进行评估设置。

这个回答就算是差不多了。

但是,如果很不幸,如果你遇到了我,为了验证你是真的自己去摸索过,还是仅仅只是看了几篇文章,我可能还会追问一下:

那么其他什么都不动,如果我仅仅加入 server.tomcat.max-connections=10 这个配置呢,那么这个时候最多能处理多少个请求?

你可能就要猜了:10 个。

是的,我重新提交 1000 个任务过来,在控制台输出的确实是 10 个,

https://why-image-1300252878.cos.ap-chengdu.myqcloud.com/img/79/20230716154330.png

那么 max-connections 这个参数它怎么也能控制请求个数呢?

为什么在前面的分析过程中我们并没有注意到这个参数呢?

首先我们看一下它的默认值:

https://why-image-1300252878.cos.ap-chengdu.myqcloud.com/img/79/20230716160608.png

因为它的默认值是 8192,比最大线程数 200 大,这个参数并没有限制到我们,所以我们没有关注到它。

当我们把它调整为 10 的时候,小于最大线程数 200,它就开始变成限制项了。

那么 max-connections 这个参数到底是干啥的呢?

你先自己去摸索摸索吧。

同时,还有这样的一个参数,默认是 100:

server.tomcat.accept-count=100

https://why-image-1300252878.cos.ap-chengdu.myqcloud.com/img/79/20230716161005.png

它又是干什么的呢?

max-connections服务程序可以在一定时间内接收并处理的连接数目,超过这个数,会继续建立连接存放在queue-1队列中,数量不超过acceptCount。但是该连接不会被处理,只有当queue-2中的连接数小于maxConnections值,queue-1中的连接才会进入queue-2中,该连接才有可能被执行。当同时请求数大于maxConnections+acceptCount 时,新的请求将会被拒绝连接。                

6 影响参数二 Web容器

通过前面的分析,我们知道了,要回答“一个 SpringBoot 项目默认能处理的任务数”,这个问题,得先明确其使用的 web 容器。

那么问题又来了:SpringBoot 内置了哪些容器呢?

Tomcat、Jetty、Netty、Undertow

https://why-image-1300252878.cos.ap-chengdu.myqcloud.com/img/79/20230716163334.png

前面我们都是基于 Tomcat 分析的,如果我们换一个容器呢?

比如换成 Undertow,这个玩意我只是听过,没有实际使用过,它对我来说就是一个黑盒。

管它的,先换了再说。

从 Tomcat 换成 Undertow,只需要修改 Maven 依赖即可,其他什么都不需要动:

https://why-image-1300252878.cos.ap-chengdu.myqcloud.com/img/79/20230716163620.png

再次启动项目,从日志可以发现已经修改为了 Undertow 容器:

https://why-image-1300252878.cos.ap-chengdu.myqcloud.com/img/79/20230716163826.png

此时我再次执行 MainTest 方法,还是提交 1000 个请求:

https://why-image-1300252878.cos.ap-chengdu.myqcloud.com/img/79/20230716164026.png

从日志来看,发现只有 48 个请求被处理了。

就很懵逼,48 是怎么回事儿,怎么都不是一个整数呢,这让强迫症很难受啊。

这个时候你的想法是什么,是不是想要看看 48 这个数字到底是从哪里来的?

怎么看?

之前找 Tomcat 的 200 的时候不是才教了你的嘛,直接往 Undertow 上套就行了嘛。

打线程 Dump,然后看堆栈消息:

https://why-image-1300252878.cos.ap-chengdu.myqcloud.com/img/79/20230716165004.png

发现 EnhancedQueueExecutor 这个线程池,接着在这个类里面去找构建线程池时的参数。

很容易就找到了这个构造方法:

https://why-image-1300252878.cos.ap-chengdu.myqcloud.com/img/79/20230716170419.png

所以,在这里打上断点,重启项目。

通过 Debug 可以知道,关键参数都是从 builder 里面来的。

而 builder 里面,coreSize 和 maxSize 都是 48,队列长度是 Integer.MAX_VALUE

https://why-image-1300252878.cos.ap-chengdu.myqcloud.com/img/79/20230716170701.png

所以看一下 Builder 里面的 coreSize 是怎么来的。

点过来发现 coreSize 的默认值是 16:

https://why-image-1300252878.cos.ap-chengdu.myqcloud.com/img/79/20230716170808.png

不要慌,再打断点,再重启项目。

然后你会在它的 setCorePoolSize 方法处停下来,而这个方法的入参就是我们要找的 48:

https://why-image-1300252878.cos.ap-chengdu.myqcloud.com/img/79/20230716170937.png

顺藤摸瓜,重复几次打断点、重启的动作之后,你会找到 48 是一个名为 WORKER_TASK_CORE_THREADS 的变量,是从这里来的:

https://why-image-1300252878.cos.ap-chengdu.myqcloud.com/img/79/20230716171232.png

而 WORKER_TASK_CORE_THREADS 这个变量设置的地方是这样的:

io.undertow.Undertow#start

https://why-image-1300252878.cos.ap-chengdu.myqcloud.com/img/79/20230716171424.png

而这里的 workerThreads 取值是这样的:

io.undertow.Undertow.Builder#Builder

https://why-image-1300252878.cos.ap-chengdu.myqcloud.com/img/79/20230716171520.png

取的是机器的 CPU 个数乘以 8

https://why-image-1300252878.cos.ap-chengdu.myqcloud.com/img/79/20230716171629.png

所以我这里是 6*8=48。

哦,真相大白,原来 48 是这样来的。

没意思。

确实没意思,但是既然都已经替换为 Undertow 了,那么你去研究一下它的 NIO ByteBuffer、NIO Channel、BufferPool、XNIO Worker、IO 线程池、Worker 线程池...

然后再和 Tomcat 对比着学,

就开始有点意思了。

https://why-image-1300252878.cos.ap-chengdu.myqcloud.com/img/79/20230716180859.png

7 影响参数三 Async

这篇文章是基于“一个 SpringBoot 项目能同时处理多少请求?”这个面试题出发的。

但是经过我们前面简单的分析,你也知道,这个问题如果在没有加一些特定的前提条件的情况下,答案是各不一样的。

比如我再给你举一个例子,还是我们的 Demo,只是使用一下 @Async 注解,其他什么都不变:

https://why-image-1300252878.cos.ap-chengdu.myqcloud.com/img/20220716/20230716210625.png

再次启动项目,发起访问,日志输出变成了这样:

https://why-image-1300252878.cos.ap-chengdu.myqcloud.com/img/20220716/20230716212906.png

同时能处理的请求,直接从 Tomcat 的默认 200 个变成了 8 个?

因为 @Async 注解对应的线程池,默认的核心线程数是 8。

相关文章:

一个 SpringBoot 项目能同时处理多少请求?

目录 1 问题分析 2 Demo 3 答案 4 怎么来的&#xff1f; 5 标准答案及影响参数一Tomcat配置 6 影响参数二 Web容器 7 影响参数三 Async 1 问题分析 一个 SpringBoot 项目能同时处理多少请求&#xff1f; 不知道你听到这个问题之后的第一反应是什么&#xff1f; 我大概…...

计算机网络——网络

计算机网络——网络 小程一言专栏链接: [link](http://t.csdnimg.cn/ZUTXU)前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家&#xff0c; [跳转到网站](https://www.captainbed.cn/qianqiu) 无线网络和移动网…...

C语言探索:选择排序的实现与解读

当我们需要对一组数据进行排序时&#xff0c;选择排序&#xff08;Selection Sort&#xff09;是一种简单但效率较低的排序算法。它的基本思想是每次从未排序的数据中选择最小&#xff08;或最大&#xff09;的元素&#xff0c;然后将其放置在已排序序列的末尾。通过重复这个过…...

Golang 学习(二)进阶使用

二、进阶使用 性能提升——协程 GoRoutine go f();一个 Go 线程上&#xff0c;可以起多个协程&#xff08;有独立的栈空间、共享程序堆空间、调度由用户控制&#xff09;主线程是一个物理线程&#xff0c;直接作用在 cpu 上的。是重量级的&#xff0c;非常耗费 cpu 资源。协…...

ubuntu22.04@laptop OpenCV定制化安装

ubuntu22.04laptop OpenCV定制化安装 1. 源由2. 默认配置3. 定制配置4. 定制安装5. 定制OpenCV-4.9.05.1 修改opencv.conf5.2 加载so文件5.3 修改bash环境变量5.4 增加pkgconfig5.5 检查OpenCV-4.9.0安装 6. 总结7. 参考资料 1. 源由 目前&#xff0c;能Google到的代码层次不齐…...

linux系统非关系型数据库redis

redis 介绍redis的特点:缓存 安装安装单机版redisredis的相关工具 介绍 redis是一个开源的、使用C语言编写的、支持网络交互的、可基于内存也可持久化的Key-Value数据库 redis的官网&#xff1a;redis.ioredis的特点: 丰富的数据结构 支持持久化 支持事务 支持主从缓存 类型 …...

【LeetCode: 292. Nim 游戏+ 博弈问题】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…...

Android 9.0 禁用adb reboot recovery命令实现正常重启功能

1.前言 在9.0的系统rom定制化开发中,在定制recovery模块的时候,由于产品开发需要要求禁用recovery的相关功能,比如在通过adb命令的 adb reboot recovery的方式进入recovery也需要实现禁用,所以就需要了解相关进入recovery流程来禁用该功能 2.禁用adb reboot recovery命…...

分析网站架构:浏览器插件

一、Wappalyzer 1.1 介绍 Wappalyzer 是一款用于识别网站所使用技术栈的浏览器插件。它能够分析正在浏览的网页&#xff0c;检测出网站所使用的各种技术和框架&#xff0c;如内容管理系统&#xff08;CMS&#xff09;、JavaScript库、Web服务器等。用户只需安装 Wappalyzer 插…...

CentOS7搭建Hadoop集群

准备工作 1、准备三台虚拟机&#xff0c;参考&#xff1a;CentOS7集群环境搭建&#xff08;3台&#xff09;-CSDN博客 2、配置虚拟机之间免密登录&#xff0c;参考&#xff1a;CentOS7集群配置免密登录-CSDN博客 3、虚拟机分别安装jdk&#xff0c;参考&#xff1a;CentOS7集…...

10.0 Zookeeper 权限控制 ACL

zookeeper 的 ACL&#xff08;Access Control List&#xff0c;访问控制表&#xff09;权限在生产环境是特别重要的&#xff0c;所以本章节特别介绍一下。 ACL 权限可以针对节点设置相关读写等权限&#xff0c;保障数据安全性。 permissions 可以指定不同的权限范围及角色。 …...

容器化技术基础概念:雪花服务器与凤凰服务器

雪花服务器与凤凰服务器&#xff1a;两种软件部署领域的基础设施对比 在软件部署领域&#xff0c;服务器管理在正常运行时间、效率和安全性方面发挥着关键作用。存在两种截然不同的方法&#xff1a;雪花服务器和凤凰服务器。了解它们之间的区别将帮助您选择最适合您需求的策略…...

解决maven 在IDEA 下载依赖包速度慢的问题

1.idea界面双击shift键 2.打开setting.xml文件 复制粘贴 <?xml version"1.0" encoding"UTF-8"?> <settings xmlns"http://maven.apache.org/SETTINGS/1.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:sc…...

用python编写爬虫,爬取二手车信息+实验报告

题目 报告要求 工程报告链接放在这里 https://download.csdn.net/download/Samature/88805518使用 1.安装jupyter notebook 2.用jupyter notebook打开工程里的ipynb文件&#xff0c;再run all就行 注意事项 可能遇到的bug 暂无&#xff0c;有的话私信我...

代码随想录算法训练营第二十九天|491.非递减子序列、46.全排列、47.全排列II

491.非递减子序列 思路&#xff1a;这道题最开始的时候&#xff0c;我想到两个问题&#xff1a;一个是如何维持递增的序列&#xff0c;一个是如何去重&#xff0c;写了一版代码&#xff0c;用的前面的去重方法&#xff0c;但是遇到一个case始终过不了&#xff0c;[1,2,3,4,5,6,…...

(2)(2.14) SPL Satellite Telemetry

文章目录 前言 1 本地 Wi-Fi&#xff08;费用&#xff1a;30 美元以上&#xff0c;范围&#xff1a;室内&#xff09; 2 蜂窝电话&#xff08;费用&#xff1a;100 美元以上&#xff0c;范围&#xff1a;蜂窝电话覆盖区域&#xff09; 3 手机卫星&#xff08;费用&#xff…...

OTG -- STM32 OTG驱动代码下载及简述(三)

目录 前沿 1 STM32 OTG标准库的获取 2 设备模式代码匹配开发板 2.1 OTG FS全速代码修改 2.2 OTG HS代码修改 2.2.1 OTG HS外部高速PHY运行在高速模式代码修改 2.2.2 OTG HS外部高速PHY运行在全速模式代码修改 2.2.3 OTG HS内部全速PHY运行在全速模式代码修改 前沿 前面…...

STM32F407 CAN参数配置 500Kbps

本篇CAN参数适用 芯片型号&#xff1a;STM32F407xx系统时钟&#xff1a;168MHz&#xff0c;CAN挂载总线APB1为42M波 特 率 &#xff1a;500Kpbs引脚使用&#xff1a;TX_PB9&#xff0c;RX_PB8&#xff1b;修改为PA11PA12后&#xff0c;参数不变。 步骤一、打勾开启CAN&#xf…...

python常用的深度学习框架

目录 一&#xff1a;介绍 二&#xff1a;使用 Python中有几个非常受欢迎的深度学习框架&#xff0c;它们提供了构建和训练神经网络所需的各种工具和库。以下是一些最常用的Python深度学习框架&#xff1a; 一&#xff1a;介绍 TensorFlow&#xff1a;由Google开发的TensorF…...

将xyz格式的GRACE数据转成geotiff格式

我们需要将xyz格式的文件转成geotiff便于成图&#xff0c;或者geotiff转成xyz用于数据运算&#xff0c;下面介绍如何实现这一操作&#xff0c;采用GMT和matlab两种方法。 1.GMT转换 我们先准备一个xyz文件&#xff0c;这里是一个降水文件。在gmt中采用以下的语句实现xyz转grd…...

【机器学习】机器学习流程之收集数据

&#x1f388;个人主页&#xff1a;甜美的江 &#x1f389;欢迎 &#x1f44d;点赞✍评论⭐收藏 &#x1f917;收录专栏&#xff1a;机器学习 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共同学习、交流进步…...

IP风险画像在企业网络统计与安全防范中应用

随着企业在数字化时代的迅速发展&#xff0c;网络安全问题变得尤为突出。IP风险画像作为一种全面的网络安全工具&#xff0c;在企业网络统计与安全防范中展现出卓越的应用价值。本文将以一个实际案例为例&#xff0c;深入探讨IP风险画像在企业网络中的成功应用&#xff0c;以及…...

Unity类银河恶魔城学习记录3-6 Finalize BattleState源代码 P52

Alex教程每一P的教程原代码加上我自己的理解初步理解写的注释&#xff0c;可供学习Alex教程的人参考 此代码仅为较上一P有所改变的代码 【Unity教程】从0编程制作类银河恶魔城游戏_哔哩哔哩_bilibili Enemy.cs using System.Collections; using System.Collections.Generic; …...

【语音合成】中文-多情感领域-16k-多发音人

模型介绍 语音合成-中文-多情感领域-16k-多发音人 框架描述 拼接法和参数法是两种Text-To-Speech(TTS)技术路线。近年来参数TTS系统获得了广泛的应用&#xff0c;故此处仅涉及参数法。 参数TTS系统可分为两大模块&#xff1a;前端和后端。 前端包含文本正则、分词、多音字预…...

07-使用Package、Crates、Modules管理项目

上一篇&#xff1a;06-枚举和模式匹配 当你编写大型程序时&#xff0c;组织代码将变得越来越重要。通过对相关功能进行分组并将具有不同功能的代码分开&#xff0c;您可以明确在哪里可以找到实现特定功能的代码&#xff0c;以及在哪里可以改变功能的工作方式。 到目前为止&…...

spring.jpa.hibernate 配置和源码解析

版本 spring-boot:3.2.2 hibernate:6.4.1.Final 配置项目 DDL模式 生成定义语句修改表结构 配置路径&#xff1a;spring.jpa.hibernate.ddl-auto配置值&#xff1a;org.hibernate.tool.schema.Action枚举类型值 可选值&#xff1a; 可选值说明none默认值。不操作create-…...

2019年江苏省职教高考计算机技能考试——一道程序改错题的分析

题目&#xff1a;函数将str字符串中的5个数字字符串转换为整数&#xff0c;并保存在二维数组m的最后一行&#xff0c;各元素为3、-4、16、18、6。并经函数move处理后&#xff0c;运行结果如下&#xff1a; 18 6 3 -4 16 16 18 6 3 -4 -4 16 …...

邦芒支招:职场白领必备的10条护身符

​在职场生存除了小心驶得万年船&#xff0c;怎样躲过不长眼的办公室风暴&#xff0c;职场八卦及不成为上司利益的牺牲品呢&#xff1f;职场就是个小社会&#xff0c;人际关系说复杂也复杂&#xff0c;说简单也简单。现在送你10道有用的职场护身符&#xff0c;希望你能够通过利…...

python实现飞书群机器人消息通知(消息卡片)

python实现飞书群机器人消息通知 直接上代码 """ 飞书群机器人发送通知 """ import time import urllib3 import datetimeurllib3.disable_warnings()class FlybookRobotAlert():def __init__(self):self.webhook webhook_urlself.headers {…...

网站服务器中毒或是被入侵该怎么办?

随着互联网的普及和发展&#xff0c;网站服务器已经成为企业和个人不可或缺的资源。然而&#xff0c;网络安全问题也日益突出&#xff0c;其中服务器中毒或被入侵是常见的问题之一。一旦服务器中毒或被入侵&#xff0c;不仅会导致数据泄露、网站瘫痪等严重后果&#xff0c;还可…...