原文地址:https://www.jianshu.com/p/446ea7aaea86

最近在工作中遇到一个读取 HTTP 接口慢的问题(使用的是 PHP 服务器端语言),所以想谈谈服务器端读取外部资源超时机制的问题,谨以此文做个笔记。

在 Web 开发中,需要有大量的外部资源进行交互,比如说 Mysql、Redis、Memcached、HTTP 接口,这些资源具备这样一些特点:

  • 都是网络接口
  • 这些资源的可用性,连接速度、读取速度不可控
  • 分层模式,对于调用方来说,只明确是否能够读取数据、数据是否正确;对于资源提供方来说负责具体的数据逻辑。

对于资源的调用方来说,个人建议有以下的处理原则:

  • 超时机制:读取的资源假如特别慢,那么应该有读取超时机制,对于应用程序来说,一个 HTTP 接口,假如返回数据需要十秒,本身是不可接受的。
  • 重试机制:假如一个资源特别重要,比如说这个资源获取不到,但应用程序逻辑严重依赖它,为了尽可能保持可用,可以进行重试读取资源。
  • 异常处理机制,就是说资源获取不到,应该抛出一个异常,而不是一个警告,PHP 由于历史原因不强调异常机制,所以很多程序其实都是错误的,举个例子,访问 HTTP 接口超时,很多开发者武断的就认为返回数据为空,这是一个严重的逻辑错误。另外超时也是异常的一部分。

本文主要谈谈服务器程序读取 HTTP 接口超时机制问题,为什么强调服务器程序,主要是因为客户端 JavaScript 读取 HTTP 接口在处理机制上有很大的不同(或者说应用场景不同)。

超时应该设置多少

超时可以细分为连接超时和读取超时,设置多少,取决于两方面,第一是 HTTP 接口的承若,比如说微信公众平台接口,其速度和可用性要求应该是极高的,虽然官方没有说明,但是我相信对于微信内部来说,单个接口响应速度不可能超过 1 秒。第二就是使用者的考虑,比如说队列程序读取接口超时可以设置高一点,而其他程序相应超时时间不能设置太长,取决于程序、应用的性质和服务能力

说句题外话,假如 HTTP 接口出现故障,响应很慢,但是你的程序调用超时设置很大(假如再加上重试),就会进一步加重 HTTP 接口服务的可用性,可能会形成雪崩效应。

default_socket_timeout

那么如何设置超时呢,PHP 流机制可以通过 default_socket_timeout 指令来配置。
流是 PHP 中很重要的一个特性,以后可以说一说,简单的理解就是在 PHP 中,不管是读取磁盘文件、HTTP 接口,都可以认为是一种流(socket/stream)。

说明下, socket/stream 的等待时间是不包括在 PHP 最大执行时间内的。
比如说在 PHP.ini 中 配置 max_execution_time = 30max_execution_time = 20,那么这个 PHP 程序最大处理执行时间是 50 秒。

现在重点来了,原来自己认为超时时间假如为 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 扩展来处理超时控制吧

假如你想更精确的处理超时,就使用 cURL 扩展,它可以设置连接超时和读取超时(CURLOPT_TIMEOUT,CURLOPT_CONNECTTIMEOUT)。

假如希望控制 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();//超时可以起作用

[转] PHP读取HTTP接口如何处理超时的更多相关文章

  1. Java接口响应超时监控

    为什么要监控 服务化接口是提供服务的,接口正确性.稳定性是最最重要的,在保证正确的同时需要尽量提高接口响应时间. 有的团队会有专门的工具来对系统响应时间.吞吐量做监控,但如果团队没有这种"待 ...

  2. java读取PHP接口数据的实现方法(四)

    PHP文件: ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 3 ...

  3. 接口偶尔超时,竟又是JVM停顿的锅!

    原创:扣钉日记(微信公众号ID:codelogs),欢迎分享,转载请保留出处. 简介 继上次我们JVM停顿十几秒的问题解决后,我们系统终于稳定了,再也不会无故重启了! 这是之前的文章:耗时几个月,终于 ...

  4. [PHP] java读取PHP接口数据

    和安卓是一个道理,读取json数据 PHP文件: <?php class Test{ //日志路径 const LOG_PATH="E:\phpServer\Apache\logs\\ ...

  5. RCA:收单设备调用云端接口频繁超时排查总结

    研发中心/王鹏 2019年7月 关键词:OKHTTP,安卓,连接复用,开源软件BUG 一.背景知识: OKHTTP已是安卓项目中被广泛使用的网络请求开源库,它有如下特性: 1.支持HTTP/2,允许所 ...

  6. php 读取webservice接口

    开启 extension = php_soap.dll extension = php_curl.dll extension = php_openssl.dll <script src=&quo ...

  7. asp.net读取xml接口

    //发送获取xml请求 public static string SentRequest(String url) { HttpWebRequest req = WebRequest.CreateHtt ...

  8. 读取url接口数据

    string url = "http://localhost:8180/city-smscenter/smscenter?cmd=flowsms.queryMobileSmsList& ...

  9. java 接口方法超时异常处理 设置超时时间

    原文:https://blog.csdn.net/coding_1994/article/details/87728374 使用线程池另起一个线程,可以使用  newFixedThreadPool() ...

随机推荐

  1. 嵌入的资源 和 Resource

    我们将资源文件添加至.net C#工程时,文件的生成操作有多种可选方式.通常用的多的是两种:[嵌入的资源]和[Resource],如果从需要从代码中使用这些资源文件,不同生成操作则对应不同的引用方式: ...

  2. WPF之坑——ICommandSource与RoutedUICommand

    最近在项目中自己写了一个控件A,继承自contentcontrol,实现了icommandsource接口.(因需求特殊并没有使用buttonbase及它的派生类为基类),控件A在测试程序中运转良好, ...

  3. 在sqlite中,如何删除字段? how to drop a column in sqlite

    在sqlite中可以使用ALTER TABLE语法对表结构进行修改,从官方的文档说明中,语法如下图: 从图中可以看出,ALTER TABLE仅仅支持表名重命名,添加字段,却没有删除字段的方法.那么该如 ...

  4. js:实现自定义事件对象接口

    网易2017内推笔试题 要求: 请实现下面的自定义事件Event对象的接口,功能见注释(测试1) 该Event对象的接口需要能被其他对象拓展复用(测试2) //测试1 Event.on('test', ...

  5. pageadmin CMS网站制作教程:模板中的站点数据调用

    pageadmin CMS网站建设教程:模板中的站点数据调用 1.获取当前站点Id,返回int数字 Html.CurrentSiteId() 2.获取当前站点url地址,返回string字符串 Htm ...

  6. pageadmin CMS网站建设教程:如何修改用户密码?

    pageadmin CMS网站建设教程: 当我们想修改密码,该如何修改呢? 1. 首先,登录会员中心,会员中心的地址是在网址后面加上/member/login: 2. 例:我的网站地址是localho ...

  7. 《Python绝技:运用Python成为顶级黑客》 用Python实现免杀

    1.免杀的过程: 使用Metasploit生成C语言风格的一些shellcode作为载荷,这里使用Windows bindshell,功能为选定一个TCP端口与cmd.exe进程绑定在一起,方便攻击者 ...

  8. hot code loading in nodejs

    Hot Code Loading in Node.js Node.js Web应用代码热更新的另类思路 Reading through Fever today, this post by Jack M ...

  9. 六,mysql优化——小知识点

    1,选择适当的字段类型,特别是主键 选择字段的一般原则是保小不保大,能占用字节小的字段就不用大字段.比如主键,建议使用自增类型,这样节省空间,空间就是效率!按4个字节和按32个字节定位一条记录,谁快谁 ...

  10. (转)C# Enum,Int,String的互相转换 枚举转换--非常实用

    Enum为枚举提供基类,其基础类型可以是除 Char 外的任何整型.如果没有显式声明基础类型,则使用 Int32.编程语言通常提供语法来声明由一组已命名的常数和它们的值组成的枚举. 注意:枚举类型的基 ...