LinuxEye - Linux系统教程

LinuxEye - Linux系统教程

当前位置: 主页 > 脚本编程 >

PHP中使用fsock实现伪异步

时间:2013-12-20 00:10来源:ljf.me 编辑:ljf.me 点击:
在Web开发中,我们经常会遇到这样一个场景:用户发起一个请求,处理这个请求的业务很简单,可能几毫秒或者几十毫秒就能完成;但是这个请求会影响另外一个业务系统,所以我们在
在Web开发中,我们经常会遇到这样一个场景:用户发起一个请求,处理这个请求的业务很简单,可能几毫秒或者几十毫秒就能完成;但是这个请求会影响另外一个业务系统,所以我们在处理完用户的请求后,不得不发起一个新的后台请求到相关的业务系统,而相关的业务系统响应我们的后台请求可能非常慢,这样就会延长我们的整个响应时间。

实例:我们的用户头像会保存到CDN,当用户更换头像的时候,上传写入可以很快的完成,但是调用CDN的purge接口可能要很久(好几秒),这样就会导致整个上传过程耗时很长。


当然,要解决这个问题有很多办法;比如把purge接口的请求丢给消息队列,后台计划任务不停地消费消息队列发起purge请求;如果这么做确实可以解决问题,但是把整体架构复杂化了。

不用消息队列,很多人就会想到用异步;但是项目使用的是PHP,它是顺序执行,不支持异步的,怎么办?

拜读Laruence早期的文章:
  1. 《PHP实现异步调用方法研究》
  2. 《使用fscok实现异步调用PHP》
根据文章中的介绍,果断采用fsockopen的方法:
<?php
function asyncGet($url)
{
    $parts = parse_url($url);
 
    $fp = fsockopen(
        $parts['host'],
        isset($parts['port']) ? $parts['port'] : 80,
        $errno, $errstr, 3
    );
 
    $out = "GET " . $parts['path'] . " HTTP/1.1\r\n";
    $out .= "Host: " . $parts['host'] . "\r\n";
    $out .= "Connection: Close\r\n\r\n";
    fwrite($fp, $out);
    fclose($fp);
}
 
$start = microtime(true);
asyncGet(
    'http://cdn.ljf.me/purge.php'
);
$end = microtime(true);
$cost = $end - $start;
echo "purge cdn cost: $cost s\n";
问题来了,无论如何发送请求,服务器都没有正常地清洗缓存(直接输入url访问是可以正常清洗的,排除purge接口本身的问题)。


用WireShark抓到数据包来看,请求确实是有发出,也是正常的。

为什么请求有发出,但是服务器并没有进行清洗CDN工作,问题可能出现在哪里?

本地搭出一个测试环境。使用相同的脚本发完请求后,查看php-fpm的access日志没有找到刚才的请求,但是nginx的access是有这个请求的,状态码是499。

127.0.0.1 - - [18/Dec/2013:11:57:57 +0800] "GET /purge.php HTTP/1.1" 499 0 "-" "-" "-"
在HTTP协议中没有直接定义499的状态码,这个状态码是nginx指定的。
/* from ngx_http_request.h */
/*
 * HTTP does not define the code for the case when a client closed
 * the connection while we are processing its request so we introduce
 * own code to log such situation when a client has closed the connection
 * before we even try to send the HTTP header to it
 */
#define NGX_HTTP_CLIENT_CLOSED_REQUEST     499

nginx对499的定义是”client has closed connection”,并且在这些情况下会返回这个状态码:

  1. upstream 在收到读写事件处理之前时发现连接不可用。
  2. server处理请求未结束,而client提前关闭了连接。
  3. upstream出错,执行next_upstream时发现连接不可用。

现在的问题是:我们要等nginx的upstream处理完并且把请求交给fastcgi之后,才能主动关闭连接,否则就不能正常的清洗CDN的缓存了。

function asyncGet($url)
{
    $parts = parse_url($url);
 
    $fp = fsockopen(
        $parts['host'],
        isset($parts['port']) ? $parts['port'] : 80,
        $errno, $errstr, 3
    );
 
    $out = "GET " . $parts['path'] . " HTTP/1.1\r\n";
    $out .= "Host: " . $parts['host'] . "\r\n";
    $out .= "Connection: Close\r\n\r\n";
    fwrite($fp, $out);
    usleep(10000);
    fclose($fp);
}
一个不安全的做法是在fclose之前,让当前的进程先睡眠一段时间;我这里设置为10毫秒,这10毫秒的延迟对我完成整个请求的影响不大,同时我也认为nginx一定能在10毫米内把请求转到fastcgi去执行。这个时间间隔很难把握,不能保证php一定有执行到。

这种方式并不是真正的异步,只是很取巧的强制关闭连接而不等待服务器端响应。所以在Laruence的那2篇文章中,有2个问题:
  1. PHP使用fsock不能叫做异步,只是伪异步。
  2. fwrite之后马上执行fclose,nginx会直接返回499。

Laruence这2篇博文都是08年写的,不知道当时是不是用apache做的测试。因为没有使用apache的场景,所以也就不打算用apache再验证一次。

转载请保留固定链接: https://linuxeye.com/program/1989.html

------分隔线----------------------------
标签:fsock
栏目列表
推荐内容