为什么当通过 TCP 发送的消息速率增加时,请求-响应消息对的延迟会降低?

Why does the latency of a request-response message pair reduce when increasing rate of messages sent over TCP?

本文关键字:消息 响应 请求 延迟 增加 TCP 为什么 速率      更新时间:2023-10-16

Intro

我设置了一个客户端和一个通过TCP连接通信的服务器,我遇到了我无法理解的奇怪的延迟行为。

上下文

客户端向服务器发送请求消息,服务器通过响应消息响应客户端。 我将延迟定义为从发送请求消息到接收响应消息的时间。我可以以不同的速率发送请求消息(限制请求的频率(,但是我总是在任何时候最多有一个未完成的请求消息。即,没有并发/重叠的请求-响应消息对

我以三种方式实现了请求和响应消息的发送:首先是使用我自己的序列化方法等直接在TCP套接字上,其次是使用gRPC使用HTTP2通过RPC进行通信,第三是使用Apache Thrift(类似于gRPC的RPC框架(。 gRPC 依次在 4 种不同的客户端/服务器类型中实现,对于 Thrift,我有 3 种不同的客户端/服务器类型。

在所有解决方案中,在提高请求消息的发送速率时,我会遇到延迟减少的情况(在 gRPC 和 Thrift 中,请求-响应对通过 RPC 方法进行通信(。 当根本不限制请求速率,而是在收到响应后立即发送新请求时,会观察到最佳延迟。 延迟是使用 std::chrono::steady_clock 基元来测量的。 我不知道是什么原因造成的。我确保在开始实际测试之前通过发送 10k 请求消息来预热 TCP 连接(通过 TCP 慢启动阶段(。

我如何实现限制和测量延迟(在客户端 ofc 上(:

double rate;
std::cout << "Enter rate (requests/second):" << std::endl;
std::cin >> rate;
auto interval = std::chrono::microseconds(1000000)/rate;
//warmup-phase is here, but not included in this code.
auto total_lat = std::chrono::microseconds(0);
auto iter_time = start_time;
int i = 0;
for(i = 0; i < 10000; i++){ // send 10k requests.
iter_time = std::chrono::steady_clock::now();
RequestType request("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
ResponseType response;
auto start = std::chrono::steady_clock::now();
sendRequest(request); //these looks different depending on gRPC/Thrift/"TCP"
receiveResponse(&response);
auto end = std::chrono::steady_clock::now();
auto dur = std::chrono::duration_cast<std::chrono::microseconds>(end-start);
total_lat+=dur;
std::this_thread::sleep_until(iter_time+interval); //throttle the sending..
}
// mean latency: total_lat / i

我使用 docker-compose 在单独的 docker 容器中运行客户端/服务器,并且我还在 kubernetes 集群中运行它们。在这两种情况下,我都会经历相同的行为。我在想也许我的节流/时间测量代码正在做我不知道/不理解的事情。

TCP 套接字在所有情况下都设置为 TCP_NODELAY。 服务器是单/多线程非阻塞/阻塞,各种不同的变体,客户端有些是同步的,有些是异步的等。所以有很多变化,但同样的行为在他们之间。

这里有什么想法会导致这种行为吗?

现在我认为延迟问题不在于网络堆栈,而在于您生成和接收消息的速率。

测试代码似乎没有任何实时保证,这也需要在容器中设置。这意味着您的"for 循环"不会每次都以相同的速度运行。操作系统调度程序可以停止它以运行其他进程(这是进程共享 CPU 的方式(。使用容器化机制时,此行为可能会变得更加复杂。

虽然TCP中有一些机制可能会导致延迟变化(如@DNT所述(,但我认为您不会看到它们。特别是如果服务器和客户端是本地的。这就是为什么在查看 TCP 堆栈之前,我会先排除消息生成和接收速率的原因。