在第一部分中,您提到了:
“我决定通过向系统注入一些负载来进行压力测试,这将触发微服务执行一个同步的HTTP调用。 (每个调用需要约1秒钟返回响应,即阻塞1秒钟)。”
然后,“此部分占据了50%的恒定时间。”
重复的HTTP调用占用更多的CPU时间和线程创建时间
进程背后的CPU使用率
导致CPU使用率高的原因不是轮询,而是上下文切换和线程创建。
当应用程序运行重复的HTTP调用时,它会创建多个线程,就像在网络驱动程序中一样。 每个线程持续约1秒钟才能完成。
![Histogram: Process launch dominates over thread launch over context switching etc](https://istack.dev59.com/Ehqsu.webp)
每次线程启动和上下文切换都会获得 CPU 时间,如果您连续执行它们,就像运行一个无限循环
while(true)
,但如果它是完全顺序的任务,比如无限循环,它将占用一个 CPU 核心。
异步任务让并行核更忙碌
那么发生了什么?
从脚本中创建线程不是一个顺序任务。它是并行的。
因为您的脚本在一个循环中以顺序方式执行以下操作:
while(true){
create_http_request();
}
假设这个函数调用会导致系统调用创建一个线程:
function create_network_socket(){
@syscall.createThread();
}
假设您的脚本
create_http_request()
调用异步系统调用需要约1微秒。
因此,它可以在1秒内迭代大约
一百万次(到线程关闭)。
但是,在每次调用中,脚本都会调用操作系统系统调用以获取作业(创建线程)。
假设这需要5微秒。
因此,以下内容将在一秒钟内发生:
- 您的脚本从一个CPU核心运行一个顺序任务。
- 它要求操作系统提供一百万个作业,这需要4000000微秒的CPU时间才能完成。
- 并且需要5个额外的CPU核心异步执行作业。
这使您拥有6个完全负载的核心。
逆关系瓶颈
HTTP请求脚本和系统调用之间的异步使并行作业成为可能。这就是导致50%的CPU使用率的原因。
运行刺激任务的瓶颈是顺序调用HTTP请求。
因此,它越快,每秒执行的HTTP请求就越多,因此它会消耗更多的CPU来完成其他部分(操作系统)的工作。
所以它似乎具有反向关系瓶颈。
Http异步请求
数据流中发生了什么
当脚本请求HTTP调用时,所发生的事情可以看作是从脚本到目的地的数据流,并返回。我将用一些简单的步骤来说明它,以后再讨论:
script function call
--> framework/lib function call
--> interpreter/module call
--> OS socket lib call
--> OS system call
--> driver call
--> hardware invocation (NIC)
--> physical media buffering and signalling
在这个流程中,您可以考虑一些节点和一些链接。
根据技术的不同,每个链接都可以是半双工或全双工。而每个节点可以使用不同的技术来实现,我们稍后会讨论到它。
但是,在我们的冯·诺伊曼体系结构和运行顺序机器代码的CPU的低级层面上会发生什么?
这些代码将一个接一个地运行,它们能够从数据变化中得到通知的唯一方法有两种:
- 它们检查某个位置:正如您所提到的那样,进行轮询。
- 其他信号中断它们的工作并通知它们。(需要额外的信号链)
当上述任何一个节点(脚本、驱动程序、任何函数调用)需要处理数据时,如果数据还没有准备好,它应该等待直到数据可用,此时使用轮询或中断通知它。
但是,当数据尚未准备好时,它可以表现出两种不同的方式:
- 阻塞:同步。线程等待数据并将CPU传递给操作系统进行另一个进程。
- 非阻塞:异步。线程等待数据。但是,另一个线程开始执行不需要此数据的另一部分。
同步和异步在每个节点中
该流程链中的每个节点都有自己的机制来与链中的其他节点连接。每个单独的节点可以与其他节点同步或异步,每个链接都可以得到响应(使用轮询或中断)。
例如:
即使您对服务器运行同步HTTP请求,您的驱动程序也不会阻塞其他作业。
在某些链接中,可能是一种中断,例如NIC可以从以太网上的两个RX线接收信号,或者它可以像线程轮询IPC管道一样进行轮询。
结论
如果您异步调用HTTP请求,则数据流链中的其他节点将像以前一样操作(大多数情况下是异步的)(取决于程序/技术)。
链接保持不变。
唯一的区别在于您的基准脚本。
如果您的脚本中有另一个Foo
作业,则可以执行以下操作:
- 阻塞:在HTTP响应后
- 非阻塞:在HTTP请求调用后
您是否有其他作业并不重要。
长期运行
因为基准测试是一个持续1秒的无状态作业循环,所以在1秒后,在请求或响应之后执行
Foo Job 没有任何区别。