15

您将不得不原谅相当大的代码块,但我相信这几乎是对我的问题的最小再现。该问题并非孤立存在,example.com而是存在于许多其他站点中。

如果我有 4 个线程主动发出网络请求,curl 可以 100% 正常工作。

如果我再添加一个线程,该线程的执行时间约为 10 倍。我觉得我一定遗漏了一些明显的东西,但它现在逃脱了我。

更新更多信息:这些测试在虚拟机中。与机器可用的内核数量无关,其中四个请求大约需要 100 毫秒,其余请求大约需要 5500 毫秒。

更新 2:实际上,我在一个方面是错误的,它并不总是4/n-4分布——当我更改为4内核时,有时我会得到不同的结果分布(在 1 个内核上运行至少看起来相对一致)——这是一个当线程在 4 核 VM 上运行时返回其延迟 (ms) 而不是其 http 代码时的结果片段:

   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等于对返回时间设置xx上限。

更新 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
4

2 回答 2

7

正如我的UPDATE 5所示,该错误位于 DNS 解析中。

这与查找 IPV6 有关,在getaddrinfo.

搜索表明这通常是一个 ISP 问题,或者一个过度激进的数据包过滤问题,再加上其他一些东西(我不知道是什么),这使得这成为一个非常奇怪的边缘情况。

按照此页面上的说明会导致以下解决方法/解决方案:

curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);

这消除了我认为的问题。IPV6 很难。:(

于 2018-02-05T07:10:31.547 回答
-1

如果http服务是基于mongoose或者CivetWeb的,看这个答案

libcurl 在上传数据前延迟 1 秒,命令行 curl 不会

问题是 curl 在标头中发出Expect:100-continue,但 mongoose/civetweb 没有响应。卷曲在 1000 毫秒后超时并继续。

上面的答案显示了如何修复 curl 或 CivetWeb。

于 2021-06-17T22:57:42.687 回答