PHP异步操作原理及实现

1.为啥PHP需要异步操作?

一般来说PHP适用的场合是web页面展示等耗时比较短的任务,如果对于比较花时间的操作如resize图片、大数据导入、批量发送EDM、SMS等,就很容易出现操作超时情况。你可以说我可以设置无限超时时间,等等你也要知道PHP有一个工作模式是fastcgi,PHP无限不超时,不代表
fastcgi相应不超时……如果你还想说要fastcgi相应永不超时,我建议你应该跟你们的运维人员讨论去……

这个时候异步的操作就发挥他的作用了,由于是非阻塞操作,操作会即时返回,然后在后台再慢慢干活。管你超时不超时的,我就没有在当前的进程/线程下干活。看吧是不是很美好,不过其实这也是个坑……

一、Apache是如何解析php文件的

Gearman 是什么?

2.PHP可以实现异步操作吗?

答案是肯定的,不过网上各种的纯PHP实现得就有点别扭了。socket模式、挂起进程模式、有的还直接fork进程。很好,各路神仙各显神通。如果运维人员看到的话,一定会×××××你们的,不把web
server跑死才怪……

那还有其他更好的方法去实现这个异步操作的可能么?有,现在我们只有想怎么开外挂了。查一下PECL主流的外挂方案有一堆的××MQ(消息队列),其中有个用于任务分配的外挂进入了我们的视线Gearman(其实这家伙才是角,我就不详细介绍了,点连接看介绍)。

我们常说的lamp架构是linux、apache、mysql、php,我们知道任何架构或者网站离不开数据库的支持,那么php和apache又是如何协同工作的呢?

Gearman是一个用来把工作委派给其他机器、分布式的调用更适合做某项工作的机器、并发的做某项工作在多个调用间做负载均衡、或用来在调用其它语言的函数的系统。

3.为啥选择Gearman?

别的不说,就说他的client多,支持很多语言的client,你可以使用大部分你喜欢的语言去写worker。我个人是很烦语言之争,你喜欢用神码语言写worker都随你喜欢。有数据持久化支持(就是把队列保存到数据库介质中,那故障恢复也好做),有群集支持(其实很多××MQ都有这些功能)。
PECL上有扩展,也有纯PHP实现扩展。反正这个Gearman也活了很久了,杂七杂八的问题都基本上解决了。

php是apache的一个外挂程序,必须依靠web服务器才可以运行。当客户端浏览器触发事件—>php程序提交到apache服务器—->apache服务器根据php程序的特点判断是php程序,并从内存或者硬盘中提取访问所需要的应用程序,将其提交给php引擎程序—>php引擎程序解析并读取数据库生成相应的页面—>php引擎将html页面返回给web服务器,web服务器再将页面返回给客户端。

Gearman 工作原理

4.基本思路

有了Gearman这外挂就简单多了。就是向gearman发送一个任务,把执行的任务发出去,然后等待worker去调用PHP
cli去运行我们的php代码。

我就写了一下一个python的worker(别问我为啥用python,1.我会python,2.linux下不用装runtime),你可以自己根据思路写一个PHP的worker,不过嘛,本人是不太信得过PHP跑的worker。其他语言饭可以用java、node.js
或者其他语言实现一个worker试试。对用Golang写worker有兴趣的朋友可以找我。

phpasync_worker_py

不好意思,里面是没有注释的。一个配置文件,一个py脚本。基本的功能也就是分析一下调用的参数,然后调用PHP
Cli,就是那样子而已。要让py脚本跑起来请自行安装python的gearman模块。

然后到PHP的部分先上测试代码:

<?php  
require_once 'PHPAsyncClient.php';  
date_default_timezone_set('Asia/Shanghai');  

class AsyncTest {  

    const 
        LOG_FILE = '/debug.log';  

    static public function run() {  
        if (PHPAsyncClient::in_callback(__FILE__)) {  
            self::log('php Async callback');  
            PHPAsyncClient::parse();  
            return;  
        }  
        if (PHPAsyncClient::is_main(__FILE__)) {  
            self::log('main run');  
            $async_call = PHPAsyncClient::getInstance();  
            $async_call->AsyncCall('AsyncTest', 'callback', array(  
                'content' => 'Hello World!!!',  
            ), array(  
                'class' => 'AsyncTest',  
                'method' => 'callback',  
                'params' => array(  
                    'content' => 'Hello Callback!',  
                ),  
            ), __FILE__);  
            return;  
        }  
    }  

    static public function callback($args) {  
        self::log('AsyncTest callback run');  
        self::log('AsyncTest callback args:'.print_r($args, true));  
    }  

    static public function log($content) {  
        $fullname = dirname(__FILE__).self::LOG_FILE;  
        $content = date('[Y-m-d H:i:s]').$content."n";  
        file_put_contents($fullname, $content, FILE_APPEND);  
    }  
}  

AsyncTest::run();

就3个静态方法,一个是用于调试的log方法,其他都是字面意思。这个例子是对这种调用方式有个初步印象。然后直接上PHP的所有源码:

php_async.zip

然后应该会有很多人会说,win下安装不了gearman……所以我把java版的gearman
server也放上去吧。

java-gearman-service-0.6.6.zip

大致上这就是apache和php交互工作的过程。apache单独不能处理php代码,php(php
应用程序服务器)不能单独存在的,需要以apache为依托。也可以说php是apache功能模块的一种扩展,php作为apache的外挂,用于解
析php代码。经过解析的动态网页此时就不存在<?php
?>了。当apache服务启动的同时也启动了php。也就是说php必须和apache安装在同一台服务器上,不可以分割。需要注意的是,mysql与apache无关,只是在响应php代码中的sql操作的时候才被访问。不需要与apache安装在同一台服务器上,可以单独存在,只需php远程连接即可。

  • Client(客户端):创建一个Job。
  • Server(服务):找到合适的Worker,把 Job 交给 Worker。
  • Worker(工人):执行Job。

5.结论

经过以上配置犀牛一样大的家伙后(要装一个Gearman,还要跑个Py脚本),我们基本上就使PHP拥有了异步调用功能,当然其中还有一个状态维护神马的要自己去实现。所以发现,其实这个方案不咋样,太复杂了。还是使用一些web
service的方式去做web callback会好点(问题是web
callback一样会超时……),这个请留意后续。

php作为一个动态的、共享的模块被apache调用,在启动apache时,自动加载。

图片 1

二、nginx是如何解析php文件的呢?

Web 中常用的场景。

要想搞清楚nginx是如何与php协同工作的,先来了解一下cgi和fastcgi两个协议。CGI:common
gateway interface,是web
server与后台编程语言交互的一种协议。有了这个协议,开发者可以使用任何语言处理
Web Server 发来的请求,动态的生成内容。但 CGI
有一个致命的缺点,那就是每处理一个请求都需要 fork 一个全新的进程,随着
Web
的兴起,高并发越来越成为常态,这样低效的方式明显不能满足需求。就这样,FastCGI
诞生了,CGI 很快就退出了历史的舞台。FastCGI,顾名思义为更快的
CGI,它允许在一个进程内处理多个请求,而不是一个请求处理完毕就直接结束进程,性能上有了很大的提高。

  • 裁剪图片,生成缩略图。
  • 文件分发(针对用户上传的文件,进行多台服务器分发)。
  • 视频转码(对上传的视频,进行转码存储)。
  • 系统报警(当系统出现问题的时候,第一时间通知相关人)。

PHP-CGI是PHP自带的FASTCGI管理器,但有很多的缺点。FPM同样也是fastcgi的实现,相比php-cgi有很多的优点。到了PHP5.3.3已经集成php-fpm了,不再是第三方的包了。PHP-FPM提供了更好的PHP进程管理方式,可以有效控制内存和进程、可以平滑重载PHP配置,只需要在./configure时,加上–enable-fpm。

这篇文章主要讲解 系统报警 场景

FPM 是一个 PHP 进程管理器,包含 master 进程和 worker
进程两种进程:master 进程只有一个,负责监听端口,接收来自 Web Server
的请求,而 worker 进程则一般有多个
(具体数量根据实际需要配置),每个进程内部都嵌入了一个 PHP 解释器,是 PHP
代码真正执行的地方。

在开发系统的过程中,往往程序会出现这样,那样的问题。

从 FPM 接收到请求,到处理完毕,其具体的流程如下:

我们要第一时间获取错误问题,通知短信,邮件通知给相关人员。

1.FPM 的 master 进程接收到请求

因为,短信、邮件的发送比较耗时,并发量大的情况下,会出现延时现象。

2.master 进程根据配置指派特定的 worker
进程进行请求处理,如果没有可用进程,返回错误,这也是我们配合 Nginx
遇到502错误比较多的原因。

所以,使用 Gearman 实现短信,邮件的异步发送。

3.worker 进程处理请求,如果超时,返回504错误

Gearman 安装的两种方式

4.请求处理结束,返回结果

  • 方式一:官网推荐安装方法

    官网地址:Getting Started [Gearman Job
    Server]

    yum install gearmand
    
  • 方式二:自定义安装

    • 安装libevent:

      wget get https://github.com/libevent/libevent/releases/download/release-2.0.22-stable/libevent-2.0.22-stable.tar.gz
      
      tar zxvf libevent-2.0.22-stable.tar.gz
      
      ./configure --prefix=/usr
      
      make && make install
      
    • 安装Gearman server and library:

      wget get https://launchpad.net/gearmand/1.2/1.1.12/+download/gearmand-1.1.12.tar.gz
      
      tar zxvf gearmand-1.1.12.tar.gz
      
      cd gearmand-1.1.12
      
      ./configure
      
      如果报错:configure: error: could not find boost
      
      yum install -y boost boost-devel
      
      如果报错:configure: error: could not find gperf
      
      yum install gperf
      
      如果报错:configure: error: Unable to find libuuid
      
      yum install libuuid-devel
      
      make && make install
      
      安装成功后,执行 gearmand -V 查询版本号。
      
      gearmand -d 开启服务。
      
      温馨提示:
      仅供参考,遇到不可预测的问题,请进行Google。
      
    • 安装Gearman PHP extension:

      wget get http://pecl.php.net/get/gearman-1.1.2.tgz
      
      tar zxvf gearman-1.1.2.tgz
      
      cd gearman-1.1.2
      
      /usr/local/php/bin/phpize
      
      ./configure --with-php-config=/usr/local/php/bin/php-config --with-gearman
      
      如果报错:configure: error: Please install libgearman
      
      yum install -y libgearman-devel.x86_64
      

FPM 从接收到处理请求的流程就是这样了,那么Nginx 又是如何发送请求给fpm
的呢?这就需要Nginx 层面来说明了。

案例(系统报警)

我们知道,Nginx 不仅仅是一个Web 服务器,也是一个功能强大的Proxy
服务器,除了进行http
请求的代理,也可以进行许多其他协议请求的代理,包括本文与fpm
相关的fastcgi 协议。为了能使Nginx 理解fastcgi 协议,Nginx 提供了fastcgi
模块来将http 请求映射为对应的 fastcgi 请求。

  • 开启服务

    gearmand -d

  • 执行Worker

    php worker.php

  • client.php

    //在脚本结束时运行的函数
    register_shutdown_function(‘fatalErrorHandler’);

    //这里发生一个警告错误,被errorHandler 捕获
    $a = $b;

    //发生致命错误,脚本停止运行触发 fatalErrorHandler
    $c = new Test();

    /**

    • 错误处理
    • @param int $err_no 错误代码
    • @param string $err_msg 错误信息
    • @param string $err_file 错误文件
    • @param int $err_line 错误行号
    • @param int $is_fatal_error 是否为致命错误
    • @return string
      */
      function errorHandler($err_no = 0, $err_msg = ”, $err_file = ”, $err_line = 0, $is_fatal_error = 0)
      {
      $strEmailInfo = ($is_fatal_error == 1) ? “【致命错误】n” : “【警告错误】n”;

      $strEmailInfo .= “时间:”.date(‘Y-m-d H:i:s’).”n”;
      $strEmailInfo .= “错误代码:{$err_no}n”;
      $strEmailInfo .= “错误信息:{$err_msg}n”;
      $strEmailInfo .= “错误文件:{$err_file}n”;
      $strEmailInfo .= “错误行号:{$err_line}n”;

      $strSmsInfo = “[致命错误]错误代码:{$err_no},错误信息:{$err_msg},错误文件:{$err_file}”;

      $client= new GearmanClient();
      $client->addServer(“127.0.0.1”, 4730);
      $client->doNormal(“send_mail”, $strEmailInfo);
      if ($is_fatal_error == 1) {

       $client->doNormal("send_sms", $strSmsInfo);
      

      }
      }

    /**

    • 捕捉致命错误
    • @return string
      */
      function fatalErrorHandler() {
      $e = error_get_last();
      switch ($e[‘type’]) {

       case 1:
           errorHandler($e['type'], $e['message'], $e['file'], $e['line'], 1);
           break;
      

      }
      }

  • worker.php

    addServer(“127.0.0.1”, 4730);
    $worker->addFunction(“send_mail”, “doSendMail”);
    $worker->addFunction(“send_sms”, “doSendSms”);
    while ($worker->work());

    /**

    • 执行发送邮件的Job
    • @param $job
    • @return string
      */
      function doSendMail($job)
      {
      $strEmailInfo = $job->workload();

      /**

      • 在这个方法里完善发送邮件的操作
      • ……
      • Demo是把信息写入到文件中
        */

      return file_put_contents(“gearman.txt”, $strEmailInfo.”n”, FILE_APPEND);
      }

      /**

    • 执行发送短信的Job
    • @param $job
    • @return string
      */
      function doSendSms($job)
      {
      $strSmsInfo = $job->workload();

      /**

      • 在这个方法里完善发送短信的操作
      • ……
      • Demo是把信息写入到文件中
        */

      return file_put_contents(“gearman.txt”, $strSmsInfo.”n”, FILE_APPEND);
      }

  • gearman.txt

    【警告错误】
    时间:2016-09-22 23:15:10
    错误代码:8
    错误信息:Undefined variable: b
    错误文件:/home/www/mi/gearman/client.php
    错误行号:9

    【致命错误】
    时间:2016-09-22 23:15:10
    错误代码:1
    错误信息:Class ‘Test’ not found
    错误文件:/home/www/mi/gearman/client.php
    错误行号:12

    [致命错误]错误代码:1,错误信息:Class ‘Test’ not found,错误文件:/home/www/mi/gearman/client.php

Nginx 的fastcgi 模块提供了fastcgi_param
指令来主要处理这些映射关系,除此之外,非常重要的就是 fastcgi_pass
指令了,这个指令用于指定 fpm 进程监听的地址,Nginx 会把所有的 php
请求翻译成 fastcgi 请求之后再发送到这个地址。

温馨提示

location ~ .php$ {

  • 也可以用 Redis Queue 来实现上面的需求。
  • 也可以用 Swoole 来实现上面的需求。
  • 也可以了解下 Gearman 与 Swoole 的区别。

include fastcgi_params;

Thanks ~

fastcgi_pass unix:/tmp/www.sock;

#fastcgi_pass 127.0.0.1:9000;

fastcgi_index index.php;

fastcgi_param SCRIPT_FILENAME /data/www$fastcgi_script_name;

}

在这个配置文件中,/tmp/www.sock 
是php-fpm监听的socket文件,nginx通过location将所有的php文件,即http请求都交给fpm处理,实现了nginx和php的协同工作。

换言之,nginx就是作了fastcgi的一个代理,用fastcgi的方式结合使用了php。所以,需要开启php-fpm服务。

CentOS
7.2下编译安装PHP7.0.10+MySQL5.7.14+Nginx1.10.1 
http://www.linuxidc.com/Linux/2016-09/134804.htm

搭建基于Linux6.3+Nginx1.2+PHP5+MySQL5.5的Web服务器全过程
http://www.linuxidc.com/Linux/2013-09/89692.htm

CentOS 6.3下Nginx性能调优
http://www.linuxidc.com/Linux/2013-09/89656.htm

CentOS 6.3下配置Nginx加载ngx_pagespeed模块
http://www.linuxidc.com/Linux/2013-09/89657.htm

CentOS 6.4安装配置Nginx+Pcre+php-fpm
http://www.linuxidc.com/Linux/2013-08/88984.htm

Nginx安装配置使用详细笔记
http://www.linuxidc.com/Linux/2014-07/104499.htm

Nginx日志过滤 使用ngx_log_if不记录特定日志
http://www.linuxidc.com/Linux/2014-07/104686.htm

Nginx的500,502,504错误解决方法
http://www.linuxidc.com/Linux/2015-03/115507.htm

CentOS 7 编译安装Nginx1.10.2 脚本启动失败解决思路
http://www.linuxidc.com/Linux/2017-01/139794.htm

Nginx
的详细介绍
:请点这里
Nginx
的下载地址
:请点这里

本文永久更新链接地址:http://www.linuxidc.com/Linux/2017-02/140100.htm

图片 2

发表评论

电子邮件地址不会被公开。 必填项已用*标注