在Web 开发中,需要有大量的外部资源进行交互,比如说 Mysql、Redis、Memcached、HTTP 接口,这些资源具备这样一些特点:
- 都是网络接口
- 这些资源的可用性,连接速度、读取速度不可控
分层模式,对于调用方来说,只明确是否能够读取数据、数据是否正确;对于资源提供方来说负责具体的数据逻辑。
- 超时机制:读取的资源假如特别慢,那么应该有读取超时机制,对于应用程序来说,一个
- HTTP 接口,假如返回数据需要十秒,本身是不可接受的。
- 重试机制:假如一个资源特别重要,比如说这个资源获取不到,但应用程序逻辑严重依赖它,为了尽可能保持可用,可以进行重试读取资源。
- 异常处理机制,就是说资源获取不到,应该抛出一个异常,而不是一个警告,PHP 由于历史原因不强调异常机制,所以很多程序其实都是错误的,举个例子,访问 HTTP 接口超时,很多开发者武断的就认为返回数据为空,这是一个严重的逻辑错误。另外超时也是异常的一部分。
超时应该设置多少
- HTTP 接口的承诺。比如说微信公众平台接口,其速度和可用性要求应该是极高的,虽然官方没有说明,但是我相信对于微信内部来说,单个接口响应速度不可能超过 1 秒。
- 使用者的考虑。比如说队列程序读取接口超时可以设置高一点,而其他程序相应超时时间不能设置太长,取决于程序、应用的性质和服务能力。
default_socket_timeout
那么如何设置超时呢,PHP 流机制可以通过 default_socket_timeout 指令来配置。
流是PHP 中很重要的一个特性,以后可以说一说,简单的理解就是在 PHP 中,不管是读取磁盘文件、HTTP 接口,都可以认为是一种流(socket/stream)。
现在重点来了,原来自己认为超时时间假如为m 秒,那么访问接口最终响应(包括网络传输时间)超过 m 秒,调用程序就会报错。实际并不是这样,只要在 m 秒数据包一直在传输,那么调用程序就不会报错。
ob_implicit_flush(1); for($i=0; $i<6; $i++){ echo $i; echo str_repeat(' ',1024*64);
sleep(1);
}
现在看看调用代码,可以看出虽然接口最后输出需要6 秒,但由于数据库包一直在传输,代码并不报错。
ini_set("default_socket_timeout", 3); $url = "http://localhost/api.php"; function e_filegetcontents() {
global $url;
var_dump(file_get_contents($url));
} function e_fopenfgets(){
global $url; $context = stream_context_create(array('http'=> array( 'timeout' => 3.0,
))); $handle = fopen($url, "r",true,$context); if ($handle) { while (($buffer = fgets($handle, 4096)) !== false) {
}
fclose($handle);
}
}
e_filegetcontents();
e_fopenfgets();
还是让我们使用cURL 扩展来处理超时控制吧
假如希望控制HTTP 接口必须在毫秒级别返回,还可以使用 CURLOPT_TIMEOUT_MS and CURLOPT_CONNECTTIMEOUT_M 常量。
注意假如使用这两个常量,必须设置curl_setopt($ch, CURLOPT_NOSIGNAL, 1);
神奇的来了,cURL 扩展机制很特别,在指定的读取时间获取到多少数据就返回多少,然后调用也终止,程序并不报错。
function e_curl() {
global $url; $ch = curl_init($url);
curl_setopt($ch, CURLOPT_NOSIGNAL, 1);
curl_setopt($ch, CURLOPT_TIMEOUT, 3);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 1); $response = curl_exec($ch); if ($response === false) { $info = curl_getinfo($ch); if ($info['http_code'] === 0) { return false;
}
} return true;
}
e_curl();
服务端模拟代码:
<?php
ob_implicit_flush(1); for($i=0; $i<60; $i++){ echo $i; echo str_repeat(' ',1024*64);
sleep(1);
}
客户端读取代码:
<?php $url="https://api.sopans.com/stream.php";
ini_set("default_socket_timeout", 3);
//使用file_get_contents读取 function e_filegetcontents() {
global $url;
var_dump(file_get_contents($url));
}
//使用fopen读取 function e_fopenfgets(){
global $url; $context = stream_context_create(array('http'=> array( 'timeout' => 3.0,
))); $handle = fopen($url, "r",true,$context); if ($handle) { while (($buffer = fgets($handle, 4096)) !== false) {
}
fclose($handle);
}
}
//使用curl读取 function e_curl() {
global $url; $ch = curl_init($url);
curl_setopt($ch, CURLOPT_NOSIGNAL, 1);
curl_setopt($ch, CURLOPT_TIMEOUT, 3);//设置执行最大超时时间
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 1);//设置连接的超时时间 $response = curl_exec($ch); if ($response === false) { $info = curl_getinfo($ch); if ($info['http_code'] === 0) { return false;
}
} return true;
}
e_filegetcontents();//超时不会起作用
//e_fopenfgets();//当数据一直在发送时,设置的超时并不会起作用
//e_curl();//超时可以起作用
扫码二维码 获取免费视频学习资料
- 本文固定链接: http://phpxs.com/post/7278/
- 转载请注明:转载必须在正文中标注并保留原文链接
- 扫码: 扫上方二维码获取免费视频资料