语言:PHP

接口超时问题是一个比较常见的场景,当接口中包含有较多的数据库查询语句,并且涉及到循环执行这些查询语句时,执行时间就会变大很多,初期可能因为新表数据量少还在接受范围,当后期数据量变大后,可能就会消耗极的时间,甚至是超时。

方法一:延长可执行时间

对面这个问题,我们可以延长可执行时间,

1
set_time_limit(30); //设置程序执行时间为30s

但这种方式,对用户其实特别不友好。除了一些特殊场景外,并不建议无限制的延长可执行时间。特别是不要将时间设置为set_time_limit(0)无限时长,这样一旦遇到死循环情况,执行就不会结束,可能会导致大量进程堆积,极大的消耗CPU。

方法二:消息队列

通过消息队列的方式,将请求插入队列(可以借助Redis等缓存),直接返回结果。再脚本通过获取队列信息执行需要的操作。

  • 优点:这样返回的速度会极快,而且内容也可以有效执行。
  • 缺点:脚本如果执行失败,但之前请求已经返回成功。
  • 优化:
    1
    2
    3
    1. 脚本完成时通过请求**回调接口**告知执行结果;(当然,脚本执行过程中的日志记录也很重要)
    2. 前端收到接口返回成功信息时,展示给用户的内容"友好"调整,例如执行完成、已执行。前端提供查看结果页,及时更新状态;
    3. 如果明确执行的时间是较长的,前端可以适当做一些等待动画,让整个完成等待时间有一定延长,对后续用户查看结果是友好的.

方法三:php 接口提前响应返回,然后继续执行后台逻辑

php 程序(以 php5.6 版本进行演示) 是可以在接口中,进行一些异步操作的, 不过写法有点特殊。

1. ob_end_clean()

这个是清除之前的缓冲内容,这是必需的,如果之前的缓存不为空的话,里面可能有 http 头或者其它内容,导致后面的内容不能及时的输出

2. header(“Connection: close”)

告诉浏览器,连接关闭了,这样浏览器就不用等待服务器的响应。

3. header(“HTTP/1.1 200 OK”)

发送 200 状态码,要不然可能浏览器会重试,特别是有代理的情况下

4. ob_start()

开启当前代码缓冲

5. ob_end_flush()

输出当前缓冲

6. flush()

输出PHP缓冲

7. ignore_user_abort(true)

在关闭连接后,继续运行php脚本

8. set_time_limit(0)

no time limit,不设置超时时间(根据实际情况使用)

9. fastcgi_finish_request()

这个在有用 fpm 的时候,会用到, 也是将提前返回响应,然后接下来的逻辑后台执行。

封装函数:

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
34
35
36
37
38
39
40
41
42
43
44
45
46
// 异步的成功返回函数
public function returnSuccessJsonDataAsync($otherReturnData = [], $timeout = 60, $allowCors = true){
$msgData = ['code' => 1, 'msg' => 'Success'];
if(empty($otherReturnData)){
$data = $msgData;
}else{
$data = array_merge($msgData, $otherReturnData);
}
// 接下来提前告诉 浏览器返回, 其他的后台允许
ob_end_clean();
//告诉浏览器,连接关闭了,这样浏览器就不用等待服务器的响应
header("Connection: close");
header("HTTP/1.1 200 OK");
ob_start();
$str = json_encode($data);
header('Content-type: application/json');
header('Content-Length: ' . strlen($str));

// 如果允许跨域,那么就设置跨域头
if($allowCors){
$origin = Yii::$app->request->getHeaders()->get('Origin');
header("Access-Control-Allow-Origin: {$origin}");
header('Access-Control-Allow-Credentials: true');
header('Access-Control-Allow-Methods: POST, GET, OPTIONS');
header('Access-Control-Allow-Headers: origin, content-type');
header('Access-Control-Max-Age: 86400');
}

echo $str;

ob_end_flush();
if(ob_get_length()){
ob_flush();
}
flush();
// yii或yaf默认不会立即输出,加上此句即可(前提是用的fpm)
if (function_exists("fastcgi_finish_request")) {
fastcgi_finish_request(); // 响应完成, 立即返回到前端,关闭连接
}

/******** background process starts here ********/
//在关闭连接后,继续运行php脚本
ignore_user_abort(true);
//no time limit,不设置超时时间(根据实际情况使用)
set_time_limit($timeout);
}

深入理解ob_flush和flush的区别(ob_flush()与flush()使用方法)

php:flush()和ob_flush(),ob_end_flush()用法区别详解

方法四:异步并行,引入swoole或用go