不好意思,这里有一大块代码,但我认为这几乎是我的问题的最小复现。这个问题不仅出现在 example.com
上,而且还在许多其他网站上持续存在。
如果我有4个线程正在进行网络请求,则 curl 的工作完全正常。
如果我添加一个以上的线程,则该线程执行所需的时间会增加约10倍。我觉得肯定有些明显的东西我没注意到,但眼下我还没想出来。
更新 更多信息:这些测试是在虚拟机中进行的。无论机器可用的核心数是多少,其中四个请求都需要 ~100ms,其余请求则需要 ~5500ms。
更新 2: 实际上,在一个方面我错了,它并不总是 4
/ n-4
的分布 - 当我改为 4
个核心时,有时会得到不同的结果分布(至少在 1 个核心上运行似乎相对一致) - 这是当线程返回它们的延迟(毫秒)而不是 http 状态码时,在 4 核 VM 上运行的结果片段:
191 191
198 198 167
209 208 202 208
215 207 214 209 209
5650 213 5649 222 193 207
206 201 164 205 201 201 205
5679 5678 5666 5678 216 173 205 175
5691 212 179 206 5685 5688 211 5691 5680
5681 199 210 5678 5663 213 5679 212 5666 428
更新 3: 我从头构建了curl和openssl,移除了锁定(因为openssl 1.1.0g不需要它),但问题仍然存在。(以下是验证结果):
std::cout << "CURL:\n " << curl_version_info(CURLVERSION_NOW)->ssl_version
<< "\n";
std::cout << "SSLEAY:\n " << SSLeay_version(SSLEAY_VERSION) << "\n";
输出:
CURL:
OpenSSL/1.1.0g
SSLEAY:
OpenSSL 1.1.0g 2 Nov 2017
举个例子,延迟时间如下:
191 191
197 197 196
210 210 201 210
212 212 199 200 165
5656 5654 181 214 181 212
5653 5651 5647 211 206 205 162
5681 5674 5669 165 201 204 201 5681
5880 5878 5657 5662 197 209 5664 173 174
5906 5653 5664 5905 5663 173 5666 173 165 204
更新4:将CURLOPT_CONNECTTIMEOUT_MS
设置为x
,使x
成为返回所需时间的上限。
更新5,最重要的:
在5个线程下使用strace -T ./a.out 2>&1 | vim -
运行程序时,当程序只有1个缓慢请求时,会产生两个非常缓慢的行。它们是对同一futex的两次调用,第一次比第二次花费的时间更长,但两次调用都比所有其他futex调用(大多数为0.000011毫秒)花费更长的时间(这两次调用分别需要5.4秒和0.2秒才能解锁)。
此外,我验证了缓慢性完全出现在curl_easy_perform
中。
futex(0x7efcb66439d0, FUTEX_WAIT, 3932, NULL) = 0 <5.390086>
futex(0x7efcb76459d0, FUTEX_WAIT, 3930, NULL) = 0 <0.204908>
最终,在查看源代码后,我发现问题出在DNS查找中。无论问题在哪里或是什么原因,用IP地址替换主机名只是一种权宜之计。
-----------
下面是我精简复制的问题,使用g++ -lpthread -lcurl -lcrypto main.cc
编译,链接到从源代码构建的openssl和libcurl版本。
#include <chrono>
#include <iomanip>
#include <iostream>
#include <thread>
#include <vector>
#include <curl/curl.h>
#include <openssl/crypto.h>
size_t NoopWriteFunction(void *buffer, size_t size, size_t nmemb, void *userp) {
return size * nmemb;
};
int GetUrl() {
CURL *hnd = curl_easy_init();
curl_easy_setopt(hnd, CURLOPT_URL, "https://www.example.com/");
curl_easy_setopt(hnd, CURLOPT_HEADERFUNCTION, NoopWriteFunction);
curl_easy_setopt(hnd, CURLOPT_WRITEFUNCTION, NoopWriteFunction);
curl_easy_setopt(hnd, CURLOPT_SSH_KNOWNHOSTS, "/home/web/.ssh/known_hosts");
CURLcode ret = curl_easy_perform(hnd);
long http_code = 0;
curl_easy_getinfo(hnd, CURLINFO_RESPONSE_CODE, &http_code);
curl_easy_cleanup(hnd);
hnd = NULL;
if (ret != CURLE_OK) {
return -ret;
}
return http_code;
}
int main() {
curl_global_init(CURL_GLOBAL_ALL);
for (int i = 1; i < 10; i++) {
std::vector<std::thread> threads;
int response_code[10]{};
auto clock = std::chrono::high_resolution_clock();
auto start = clock.now();
threads.resize(i);
for (int j = 0; j < i; j++) {
threads.emplace_back(std::thread(
[&response_code](int x) { response_code[x] = GetUrl(); }, j));
}
for (auto &t : threads) {
if (t.joinable()) {
t.join();
}
}
auto end = clock.now();
int time_to_execute =
std::chrono::duration_cast<std::chrono::milliseconds>(end - start)
.count();
std::cout << std::setw(10) << time_to_execute;
for (int j = 0; j < i; j++) {
std::cout << std::setw(5) << response_code[j];
}
std::cout << "\n";
}
}
当我在我的机器上运行程序时,我得到了以下结果(我可以更改域名,但结果基本相同):
123 200
99 200 200
113 200 200 200
119 200 200 200 200
5577 200 200 200 200 200
5600 200 200 200 200 200 200
5598 200 200 200 200 200 200 200
5603 200 200 200 200 200 200 200 200
5606 200 200 200 200 200 200 200 200 200
以下是我的curl版本和openssl版本:
$curl --version
curl 7.52.1 (x86_64-pc-linux-gnu) libcurl/7.52.1 OpenSSL/1.0.2l zlib/1.2.8 libidn2/0.16 libpsl/0.17.0 (+libidn2/0.16) libssh2/1.7.0 nghttp2/1.18.1 librtmp/2.3
Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 pop3s rtmp rtsp scp sftp smb smbs smtp smtps telnet tftp
Features: AsynchDNS IDN IPv6 Largefile GSS-API Kerberos SPNEGO NTLM NTLM_WB SSL libz TLS-SRP HTTP2 UnixSockets HTTPS-proxy PSL
$ openssl version
OpenSSL 1.1.0f 25 May 2017
uname -a
的输出结果为:Linux web 4.9.0-4-amd64 #1 SMP Debian 4.9.65-3+deb9u1 (2017-12-23) x86_64 GNU/Linux
,而cat /proc/version
的输出结果为Linux version 4.9.0-4-amd64 (debian-kernel@lists.debian.org) (gcc version 6.3.0 20170516 (Debian 6.3.0-18) ) #1 SMP Debian 4.9.65-3+deb9u1 (2017-12-23)
。 - druckermanlyinit_locks()
吗?这个函数在示例中并不需要,而且我怀疑它可能会对OpenSSL造成影响(特别是如果curl_global_init
调用了任何OpenSSL库函数,这可能会导致顺序问题)。 - Myst