江湖慣例,先上結(jié)論:
- PHP 7和libcurl 7.60.0對(duì)curl毫秒超時(shí)理論都是支持的毙籽,但如果DNS使用同步解析模式(部分Linux發(fā)行版缺省為依賴alram信號(hào)的同步模式),則無(wú)法支持毫秒
- 通過設(shè)置
CURLOPT_NOSIGNAL
(Guzzle毫秒的缺省處理)毡庆,會(huì)設(shè)置DNS解析不超時(shí)坑赡,一旦DNS異常將導(dǎo)致服務(wù)阻塞,簡(jiǎn)單粗暴風(fēng)險(xiǎn)高 - 建議方案:配置curl時(shí)啟用異步DNS解析特性么抗,兩種異步DNS解析特性均可
-
./configure --enable-ares
:開啟c-ares -
./configure --enable-threaded-resolver
:開啟threaded-resolver - 二者對(duì)比說明:libcurls-name-resolving
-
- CentOS 7中缺省編譯的Curl已配置異步DNS解析毅否,也就說CentOS7下PHP 7 完美支持毫秒超時(shí)
鳥哥在14年拋出的問題
問題起源:Laruence:21 Jan 14 Curl的毫秒超時(shí)的一個(gè)”Bug”
問題描述:
libcurl
新版本支持毫秒級(jí)超時(shí),升級(jí)PHP后蝇刀,設(shè)置毫秒超時(shí)直接返回超時(shí)錯(cuò)誤(Timeout reached 28
)
解決方案:
- 方案1:
-
curl_setopt($ch, CURLOPT_NOSIGNAL, 1);
禁用信號(hào)螟加,直接跳過DNS解析超時(shí)校驗(yàn) - 缺點(diǎn):DNS解析不受控制,如果DNS服務(wù)異常吞琐,且無(wú)超時(shí)捆探,導(dǎo)致整個(gè)應(yīng)用大面積阻塞
-
- 方案2:
-
libcurl
編譯時(shí)啟用c-ares
進(jìn)行DNS解析,如此配置./configure --enable-ares[=PATH]
- 缺點(diǎn):依賴libcurl編譯時(shí)配置
-
PHP文檔中關(guān)于TIMEOUT_MS
說明
看下PHP文檔里的說明:http://php.net/manual/zh/function.curl-setopt.php
- CURLOPT_CONNECTTIMEOUT_MS
- 嘗試連接等待的時(shí)間站粟,以毫秒為單位黍图。設(shè)置為0,則無(wú)限等待卒蘸。 如果 libcurl 編譯時(shí)使用系統(tǒng)標(biāo)準(zhǔn)的名稱解析器( standard system name resolver)雌隅,那部分的連接仍舊使用以秒計(jì)的超時(shí)解決方案翻默,最小超時(shí)時(shí)間還是一秒鐘。
- 在 cURL 7.16.2 中被加入恰起。從 PHP 5.2.3 開始可用修械。
- The number of milliseconds to wait while trying to connect. Use 0 to wait indefinitely. If libcurl is built to use the standard system name resolver, that portion of the connect will still use full-second resolution for timeouts with a minimum timeout allowed of one second.
- Added in cURL 7.16.2. Available since PHP 5.2.3.
2018年的今天這個(gè)問題如何
結(jié)論:
-
curl-7.60.0
中同樣保持此段代碼,原因在于使用Linux默認(rèn)的SIGALARM
進(jìn)行DNS解析時(shí)检盼,alarm()最小時(shí)間為1秒 - 因此缺省情況下肯污,直接使用毫秒仍然會(huì)有問題
-
curl-7.60.0.tar.gz
中相關(guān)部分代碼如下:
int Curl_resolv_timeout(struct connectdata *conn,
const char *hostname,
int port,
struct Curl_dns_entry **entry,
time_t timeoutms)
{
...
...
#ifdef USE_ALARM_TIMEOUT
if(data->set.no_signal) // 設(shè)置no_signal,則timeout為0吨枉,忽略超時(shí)
/* Ignore the timeout when signals are disabled */
timeout = 0;
else
timeout = (timeoutms > LONG_MAX) ? LONG_MAX : (long)timeoutms;
if(!timeout) // timeout=0蹦渣,則無(wú)超時(shí)解析
/* USE_ALARM_TIMEOUT defined, but no timeout actually requested */
return Curl_resolv(conn, hostname, port, entry);
if(timeout < 1000) { // 如果timeout < 1000,則直接返回CURLRESOLV_TIMEDOUT超時(shí)貌亭,增加了日志輸出
/* The alarm() function only provides integer second resolution, so if
we want to wait less than one second we must bail out already now. */
failf(data,
"remaining timeout of %ld too small to resolve via SIGALRM method",
timeout);
return CURLRESOLV_TIMEDOUT;
}
Guzzle Http中是否有規(guī)避策略
-
guzzlehttp/guzzle 6.2
中- 如果設(shè)置timeout時(shí)間小于1柬唯,則guzzle會(huì)設(shè)置
CURLOPT_NOSIGNAL=true
- 代碼如下:
- 如果設(shè)置timeout時(shí)間小于1柬唯,則guzzle會(huì)設(shè)置
$timeoutRequiresNoSignal = false;
if (isset($options['timeout'])) {
$timeoutRequiresNoSignal |= $options['timeout'] < 1; // 超時(shí)時(shí)間小于1,則設(shè)置NoSignal=1
$conf[CURLOPT_TIMEOUT_MS] = $options['timeout'] * 1000;
}
// CURL default value is CURL_IPRESOLVE_WHATEVER
if (isset($options['force_ip_resolve'])) {
if ('v4' === $options['force_ip_resolve']) {
$conf[CURLOPT_IPRESOLVE] = CURL_IPRESOLVE_V4;
} else if ('v6' === $options['force_ip_resolve']) {
$conf[CURLOPT_IPRESOLVE] = CURL_IPRESOLVE_V6;
}
}
if (isset($options['connect_timeout'])) {
$timeoutRequiresNoSignal |= $options['connect_timeout'] < 1; // 超時(shí)時(shí)間小于1圃庭,則設(shè)置NoSignal=1
$conf[CURLOPT_CONNECTTIMEOUT_MS] = $options['connect_timeout'] * 1000;
}
if ($timeoutRequiresNoSignal && strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN') {
$conf[CURLOPT_NOSIGNAL] = true;
}
- 在某個(gè)老版本guzzle中發(fā)現(xiàn)并未兼容的老代碼:
if (isset($options['timeout'])) {
$conf[CURLOPT_TIMEOUT_MS] = $options['timeout'] * 1000;
}
if (isset($options['connect_timeout'])) {
$conf[CURLOPT_CONNECTTIMEOUT_MS] = $options['connect_timeout'] * 1000;
}
CentOS 7中curl
CentOS 7中缺省攜帶的curl編譯已配置threaded-resolver
锄奢,也就是說CentOS 7 + PHP 7組合使用毫秒超時(shí)很安全。
補(bǔ)充一點(diǎn)curl支持異步DNS的方式有兩種:c-ares和threaded-resolver(centos 7中缺省啟用threaded-resolver)
附帶兩個(gè)查看本機(jī)curl編譯配置的命令:
-
curl --version
:AsynchDNS表示支持異步DNS特性 -
curl-config --configure
:查看編譯時(shí)配置
附錄不同CentOS下curl缺省編譯配置:
- CentOS 6.5
>curl --version
curl 7.37.0 (x86_64-unknown-linux-gnu) libcurl/7.37.0 OpenSSL/1.0.1e zlib/1.2.3
Protocols: dict file ftp ftps gopher http https imap imaps pop3 pop3s rtsp smtp smtps telnet tftp
Features: Largefile NTLM NTLM_WB SSL libz
> curl-config --configure
'--with-ssl' '--with-ipv6' '--enable-ldap' '--enable-ldaps'
- CentOS 7
> cat /etc/redhat-release
CentOS Linux release 7.0.1406 (Core)
> curl --version
curl 7.29.0 (x86_64-redhat-linux-gnu) libcurl/7.29.0 NSS/3.19.1 Basic ECC zlib/1.2.7 libidn/1.28 libssh2/1.4.3
Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 pop3s rtsp scp sftp smtp smtps telnet tftp
Features: AsynchDNS GSS-Negotiate IDN IPv6 Largefile NTLM NTLM_WB SSL libz
> curl-config --configure
'--build=x86_64-redhat-linux-gnu' '--host=x86_64-redhat-linux-gnu' '--program-prefix=' '--disable-dependency-tracking' '--prefix=/usr' '--exec-prefix=/usr' '--bindir=/usr/bin' '--sbindir=/usr/sbin' '--sysconfdir=/etc' '--datadir=/usr/share' '--includedir=/usr/include' '--libdir=/usr/lib64' '--libexecdir=/usr/libexec' '--localstatedir=/var' '--sharedstatedir=/var/lib' '--mandir=/usr/share/man' '--infodir=/usr/share/info' '--disable-static' '--enable-hidden-symbols' '--enable-ipv6' '--enable-ldaps' '--enable-manual' '--enable-threaded-resolver' '--with-ca-bundle=/etc/pki/tls/certs/ca-bundle.crt' '--with-gssapi' '--with-libidn' '--with-libssh2' '--without-ssl' '--with-nss' 'build_alias=x86_64-redhat-linux-gnu' 'host_alias=x86_64-redhat-linux-gnu' 'CFLAGS=-O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong --param=ssp-buffer-size=4 -grecord-gcc-switches -m64 -mtune=generic' 'LDFLAGS=-Wl,-z,relro '