Tagged: PHP

PHP运行错误监控,PHP Monitor

所有运行在线网环境的程序应该都被监控,对线网而言,无论是 Fatal error、E_NOTICE 还是 E_STRICT,都应该被消灭。对开发者而言,线网发生任何异常或潜在bug时,应该第一时间修复、优化,而不是等反馈之后才知道,然后临时去排查问题!

这篇文章要讲啥?

  1. 了解PHP错误机制
  2. 注册PHP的异常处理函数、错误处理函数、脚本退出函数
  3. 配置PHP预加载(重要)
  4. 搭建日志中心(ELK,ElasticSearch + Logstash + Kibana)
  5. 基于 ElasticSearch RESTful 实现告警

要达到的目的?

所有PHP程序里的错误或潜在错误(支持自定义)都全部写入日志并作警,包含错误的所在文件名、行号、函数(如果有)、错误信息等等
PHP程序不需要做任何修改或接入!

一、了解PHP错误机制

截止PHP7.1,PHP共有16个错误级别:
http://php.net/manual/zh/errorfunc.constants.php

如果你对PHP错误机制还不太了解,网上有很多关于PHP错误机制的总结,因为这个不是文章的重点,这里不再详细介绍!

二、注册PHP的异常处理函数、错误处理函数、脚本退出函数

PHP有三个很重要的注册回调函数的函数

  • register_shutdown_function 注册PHP退出时的回调函数
  • set_error_handler 注册错误处理函数
  • set_exception_handler 注册异常处理函数

我们先定义一个日志处理类,专门用于写日志(也可以用于用户日志哦)

<?php
/**
 * 日志接口
 * @filename Loger.php
 * @since 2016-12-08 12:13:50
 * @author 979137.com
 * @version $Id$
 */
class Loger {

    // 日志目录,建议独立于Web目录,这个目录将作用于 Logstash 日志收集
    protected static $log_path = '/data/logs/';

    // 日志类型
    protected static $type = array('ERROR', 'INFO', 'WARN', 'CRITICAL');

    /**
     * 写入日志信息
     * @param mixed  $_msg  调试信息
     * @param string $_type 信息类型
     * @param string $file_prefix 日志文件名默认取当前日期,可以通过文件名前缀区分不同的业务
     * @param array  $trace TRACE信息,如果为空,则会从debug_backtrace里获取
     * @return bool
     */
    public static function write($_msg, $_type = 'info', $file_prefix = '', $trace = array()) {
        $_type = strtoupper($_type);
        $_msg  = is_string($_msg) ? $_msg : var_export($_msg, true);
        if (!in_array($_type, self::$type)) {
            return false;
        }
        $server = isset($_SERVER['SERVER_ADDR']) ? $_SERVER['SERVER_ADDR'] : '0.0.0.0';
        $remote = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '0.0.0.0';
        $method = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : 'CLI';
        if (!is_array($trace) || empty($trace)) {
            $dtrace = debug_backtrace();
            $trace  = $dtrace[0];
            if (count($dtrace) == 1) {
                //不是在类或函数内调用
                $trace['function'] = '';
            } else {
                if ($dtrace[1]['function'] == '__callStatic') {
                    $trace['file'] = $dtrace[2]['file'];
                    $trace['line'] = $dtrace[2]['line'];
                    $trace['function'] = empty($dtrace[3]['function']) ? '' : $dtrace[3]['function'];
                } else {
                    $trace['function'] = $dtrace[1]['function'];
                }
            }
        }
        $ace = $trace;
        $now = date('Y-m-d H:i:s');
        $pre = "[{$now}][%s][{$ace['file']}][{$ace['line']}][{$ace['function']}][{$remote}][{$method}][{$server}]%s";
        $msg = sprintf($pre, $_type, $_msg);

        $filename = 'phplog_' . ($file_prefix ?: 'netbar') . '_' . date('Ymd') . '.log';
        $destination = self::$log_path . $filename;
        is_dir(self::$log_path) || mkdir(self::$log_path, 0777, true);
        //文件不存在,则创建文件并加入可写权限
        if (!file_exists($destination)) {
            touch($destination);
            chmod($destination, 0777);
        }
        return error_log($msg.PHP_EOL, 3, $destination) ?: false;
    }

    /**
     * 静态魔术调用
     * @param $method
     * @param $args
     * @return mixed
     *
     * @method void error($msg) static
     * @method void info($msg) static
     * @method void warn($msg) static
     */
    public static function __callStatic($method, $args) {
        $method = strtoupper($method);
        if (in_array($method, self::$type)) {
            $_msg = array_shift($args); 
            return self::write($_msg, $method);
        }
        return false;
    }
}

接下来,再写一个系统处理类,定义回调函数和注册回调函数

<?php
/**
 * 注册系统处理函数
 * @filename Handler.php
 * @since 2016-12-08 12:13:50
 * @author 979137.com
 * @version $Id$
 */
class Handler {

    const LOG_FILE_PREFIX = 'handler';
    const LOG_TYPE = 'CRITICAL';

    /**
     * 函数注册
     * @return none
     */
    static public function set() {
        //注册致命错误处理方法
        register_shutdown_function(array(__CLASS__, 'fatalError'));
        //注册自定义错误处理方法
        set_error_handler(array(__CLASS__, 'appError'));
        //注册异常处理方法
        set_exception_handler(array(__CLASS__, 'appException'));
    }

    /**
     * 致命错误捕获,PHP错误级别预定义常量参考:
     * http://php.net/manual/zh/errorfunc.constants.php
     * @return none
     */
    static public function fatalError() {
        $error = error_get_last() ?: null;
        if (!is_null($error) && in_array($error['type'], array(E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR))) {
            $error['class'] = $error['function'] = '';
            Loger::write($error['message'], self::LOG_TYPE, self::LOG_FILE_PREFIX, $error);
            self::halt($error);
        }
    }
    /**
     * 自定义错误处理
     * @param int $errno 错误类型
     * @param string $errstr 错误信息
     * @param string $errfile 错误文件
     * @param int $errline 错误行数
     * @return void
     */
    static public function appError($errno, $errstr, $errfile, $errline) {
        $error['message'] = "[$errno] $errstr";
        $error['file'] = $errfile;
        $error['line'] = $errline;
        $error['class'] = $error['function'] = '';
        if (!in_array($errno, array(E_STRICT, E_DEPRECATED))) {
            Loger::write($error['message'], self::LOG_TYPE, self::LOG_FILE_PREFIX, $error);
            if (in_array($errno, array(E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR))) {
                self::halt($error);
            }
        }
    }

    /**
     * 自定义异常处理
     * @param mixed $e 异常对象
     * @return void
     */
    static public function appException($e) {
        $error = array();
        $error['message']  = $e->getMessage();
        $error['file'] = $e->getFile();
        $error['line'] = $e->getLine();
        $trace = $e->getTrace();
        if(empty($trace[0]['function']) && $trace[0]['function'] == 'exception') {
            $error['file'] = $trace[0]['file'];
            $error['line'] = $trace[0]['line'];
        }
        //$error['trace']  = $e->getTraceAsString();
        $error['function'] = $error['class'] = '';
        Loger::write($error['message'], self::LOG_TYPE, self::LOG_FILE_PREFIX, $error);
        self::halt($error);
    }

    /**
     * 错误输出
     * @param mixed $error 错误
     * @return void
     */
    static public function halt($error) {
        ob_get_contents() && ob_end_clean();
        $e = array();
        if (IS_DEBUG || IS_CLI) {
            //调试模式下输出错误信息
            $e = $error;
            if(IS_CLI){
                $e_message  = $e['message'].' in '.$e['file'].' on line '.$e['line'].PHP_EOL;
                if (isset($e['treace']) ) {
                    $e_message .= $e['trace'];
                }
                exit($e_message);
            }
        } else {
            //线网不显示错误信息,显示固定字符串,保护系统安全
            //TODO:比较友好的做法是重定向到一个漂亮的错误页面
            exit('Sorry, the system error');
        }
        // 包含异常页面模板
        $exceptionFile = __DIR__ . '/exception.tpl';
        include $exceptionFile;
        exit(0);
    }
}

二、自动加载脚本(auto_append_file)

以上两个脚本准备就绪以后,我们可以把它们合并到一个文件,并增加两个重要常量:

//是否CLI模式
define('IS_CLI', PHP_SAPI == 'cli' ? true : false);
//当前是否开发模式,用于区分线网和开发模式,默认false
//开发模式下,所有错误会打印出来。非开发模式下,不会打印到页面,但会记录日志
define('IS_DEBUG', isset($_SERVER['DEV_ENV']) ? true : false);
//注册系统处理函数
Handler::set();

假设合并后的文件名叫:auto_prepend_file.php
我们把这个文件进行预加载(即自动包含进所有PHP脚本)
这时候到了很重要的一步就是,就是配置 php.ini

auto_prepend_file = /your_path/auto_prepend_file.php

重启你的Web服务器,让配置生效!
写一个测试脚本 test.php

<?php
var_dump($tencent);

因为 $tencent 未定义,所以这时候就会回调我们注册的函数,可以看到已经有错误日志了

[root@TENCENT64 /data/logs]# php -f test.php
[root@TENCENT64 /data/logs]# tail -f phplog_handler_20161209.log
[2016-12-09 16:01:05][CRITICAL][/data/logs/test.php][2][][0.0.0.0][CLI][0.0.0.0][8] Undefined variable: tencent

三、搭建日志中心(ELK,ElasticSearch + Logstash + Kibana)

因为我们的Web服务器一般都是分布式的,线网可能有N台服务器,我们的日志又是写本地,所以这时候就需要一个日志中心了,
日志系统目前已经有很多成熟的解决方案了,这里推荐 ELK 部署

  • ElasticSearch,一个基于 Lucene 的搜索服务器,它提供了一个分布式多用户能力的全文搜索引擎,基于 RESTful 架构。
  • Logstash,分布式日志收集、分析、存储工具
  • Kibana,基于 ElasticSearch 的前端展示工具
  • ELK 是比较常见的日志系统搭建部署方案,网上已经有很多教程了,推荐两篇:
    https://my.oschina.net/itblog/blog/547250
    http://baidu.blog.51cto.com/71938/1676798
    我这里就不重复写了!

    下面是基于 ELK 搭建好的日志中心,好不好用,用了就知道了!^_^
    倒流’s Bolg

    四、利用 ElasticSearch RESTful 实现告警功能

    前面我们提到 ElasticSearch 是基于 RESTful 架构!
    ElasticSearch 提供了非常多的接口,包括索引的增删改查,文档的增删改查,各种搜索

    ElasticSearch官方提供了一个PHP版本的SDK:Elasticsearch-PHP
    https://github.com/elastic/elasticsearch-php

    官方文档(全英文,目前没有中文版,看起来可能吃力,英文就英文吧,认真啃):
    https://www.elastic.co/guide/en/elasticsearch/client/php-api/current/index.html

    我们要做告警要用到的就是搜索接口!
    说白了,就是定时去把前面写的那些日志,全扫出来,然后告警给关系人!

    Elasticsearch-PHP 安装很简单,通过 Composer 来安装,我这里以 1.0 为例子
    1、编写一个 composer.json 文件

    {
        "require": {
            "elasticsearch/elasticsearch": "~1.0"
        }
    }

    2、cd到你的项目里,下载安装包

    curl -s http://getcomposer.org/installer | php

    3、安装 Elasticsearch-PHP 及其依赖

    php composer.phar install --no-dev

    完事之后,就可以写告警脚本了!下面是我写的一个示例

    <?php 
    //ElasticSearch Server,这是你的ELK服务器和端口
    $es_server = array( '127.0.0.1:9200', ); //接受告警人员
    $alert_staff = array( 'shiliangxie' => 18666666666, 'xieshiliang' => 18600000000);
    
    //需要告警的错误级别
    $alert_level = array('ERROR', 'WARN', 'CRITICAL');
    
    //告警周期,单位:分钟,这个需要和 cron 设置成一样的时间
    $alert_cycle = 10;
    
    ############################################################################
    
    //搜索最近 $alert_cycle 的错误日志
    $q['index'] = 'logstash-'.date('Y.m.d');
    $q['ignore_unavailable'] = true;
    $q['type'] = 'php';
    $q['size'] = 500;
    $q['sort'] = array('@timestamp:desc');
    $range_time['lte'] = sprintf('%.3f', microtime(true)) * 1000;
    $range_time['gte'] = $range_time['lte'] - $alert_cycle * 60 * 1000;
    $range = array('@timestamp' => $range_time);
    $match = array('level' => implode(' OR ', $alert_level));
    $q['body']['query']['filtered'] = array(
        'filter' => array('range' => $range),
        'query'  => array('match' => $match),
    );
    $params['hosts'] = $es_server;
    require __DIR__.'/vendor/autoload.php';
    $client = new Elasticsearch\Client($params);
    $ret = $client->search($q);
    $hit = $ret['hits']['hits'];
    $hit = is_array($hit) && count($hit) ? $hit : array();
    
    //组织告警内容
    $alerts = array();
    foreach($hit as $h) {
        $source = $h['_source'];
        $tag = md5($source['level'] . $source['file'] . $source['line']. $source['msg']);
        if (isset($alerts[$tag])) {
            $alerts[$tag]['num']++;
        } else {
            $alerts[$tag]['num'] = 1;
            $content = '[%s][%s][%s][%s]%s';
            $alerts[$tag]['content'] = sprintf($content, date('Y-m-d H:i:s', strtotime($source['@timestamp'])),
                $source['level'], $source['file'], $source['line'], $source['msg']
            );
        }
    }
    
    //var_dump($alerts);exit;
    
    //发送告警
    if (is_array($alerts) && count($alerts)) {
        array_walk($alerts, function($alert, $tag) use($alert_staff) {
            $msg = sprintf('[Repeat:%d]%s', $alert['num'], $alert['content']);
            array_map(function($mobile) use($msg) {
                return send_sms($mobile, $msg);
            }, $alert_staff);
            return ;
        });
    }

    然后我们把告警脚本加到 crontab 即可

    */10 * * * * /usr/local/bin/php /data/cron/PHPMonitor.php

    坐等告警吧!!
    倒流’s Bolg

    对于报警方案,目前市场上也有很多实现方案,如 Yelp 公司的 ElastAlert,用 Python 写的一个基于 ELK 用 Elasticsearch RESTful 报警框架
    https://github.com/Yelp/elastalert

    PHP实现MySQL并发查询

    一般的,一个看似很简单的页面,一次http请求后,到达服务端,穿过Cache层,落到后台后,实际可能会有很多很多的数据查询逻辑!而这些查询实际是不相互依赖的,也即可以同时查询。比如各种用户信息,用户的APP列表,每个APP对应的流量数据、消耗记录、服务状态,平台运行状态,消息通知,新闻资讯等等。
    这篇文章主要介绍了数据查询层,如何把串行变并行,提高查询效率、提升应用性能。实现方式包括:mysqlnd异步查询,cURL并发请求,Swoole异步非阻塞!

    PHP脚本是按文档流的形式来执行的,所以我们在编写PHP程序时,代码基本都是串行的,尤其是SQL,比如:
    倒流’s Bolg
    这种方式,是每次查询都需要等待结果返回之后再开始下一次的查询,
    运行时间 = (第1次发送请求时间 + 0.01 + 第1次返回时间)+(第2次发送请求时间 + 0.05 + 第2次返回时间)+(第3次发送请求时间 + 0.03 + 第3次返回时间)

    如果是并发查询,那么流程就成了:
    倒流’s Bolg
    运行时间 = 发送请求时间 + 0.05 + 返回时间

    显然,并发查询要比串行查询快!

    那么PHP可以实现并发查询吗?答案是肯定的!

    一、利用MySQL的异步查询功能

    目前 MySQL 的异步查询,只在 MySQLi 扩展提供,查询方法分别是:

    1. 使用 MYSQLI_ASYNC 模式执行 mysqli::query
    2. 获取异步查询结果:mysqli::reap_async_query

    需要注意的是,使用异步查询,需要使用 mysqlnd 作为PHP的MySQL数据库驱动,
    mysqlnd(MySQL Native Driver) 是 Zend 公司开发的 MySQL 数据库驱动,采用PHP开源协议,用于代替旧版的由 MySQL AB公司(现在的Oracle)开发的 libmysql,PHP 5.3 及以上版本开始提供,PHP5.4 之后的版本 mysqlnd 为默认配置选项,
    如果 PHP 小于 5.4,编译时需要指定编译参数:

    --with-mysqli=mysqlnd --with-pdo-mysql=mysqlnd

    点击了解 mysqlnd 数据库驱动 >>

    MySQL异步查询示例脚本:

    /**
     * MySQL异步查询示例脚本:
     * @filename p_async.php
     * @url http://test.979137.com/ParallelSQL/p_async.php
     */
    
    //期望结果集:获取以下用户的每个月每个APP的消费统计
    $top = array('979137', '555555', '666666', '888888', '999999');
    $ret = array_fill_keys($top, array());
    
    //组织结构查询
    $cmd = $resources = array();
    $sql = "SELECT access_key,SUM(amount) sum_amount FROM consume_2016%s WHERE uid=%d AND product='SAE' GROUP BY access_key"; 
    foreach($top as $uid) {
        for($i = 1; $i <= 12; $i++) { $ret[$uid][$i] = $tmp = array(); $tmp['uid'] = $uid; $tmp['month'] = $i; $tmp['resource'] = $resources[] = new \mysqli('localhost', 'root', '123456', 'sae', '3306'); $tmp['resource']->set_charset('utf8');
            $tmp['resource']->query(sprintf($sql, sprintf('%02d', $i), $uid), MYSQLI_ASYNC);
            $tag = spl_object_hash($tmp['resource']);
            $cmd[$tag] = $tmp;
        }   
    }
    
    $total = $query_times = count($resources);
    
    //获取结果
    do {
        $read = $error = $reject = $resources;
        //等待查询结束
        if (!\mysqli::poll($read, $error, $reject, 1)) {
            continue;
        }   
        //批量获取结果
        foreach($read as $resource) {
            $result = $resource->reap_async_query();
            if ($result) {
                $tag = spl_object_hash($resource);
                $uid = $cmd[$tag]['uid'];
                $month = $cmd[$tag]['month'];
                while(($row = $result->fetch_assoc()) != false) {
                    $ret[$uid][$month][$row['access_key']] = $row['sum_amount'];
                }   
                $result->free();
                $total--;
        
            } else die('MySQLi error: '.$resource->error);
        }   
    } while ($total > 0);
    
    var_dump($ret);

    二、cURL实现并发请求

    先解释下 cURL 和 SQL 怎么就扯上关系了呢!

    我们知道在很多系统架构里,PHP是不会直接操作DB的,而是 RESTful 架构,这时候所有操作都接口化了,
    这时上述所讲的 SQL 就演变成 接口调用 了,
    因为 API,所以 cURL!

    以下是PHP中cURL多线程相关函数:

    curl_multi_add_handle — 向curl批处理会话中添加单独的curl句柄
    curl_multi_close — 关闭一组cURL句柄
    curl_multi_exec — 运行当前 cURL 句柄的子连接
    curl_multi_getcontent — 如果设置了CURLOPT_RETURNTRANSFER,则返回获取的输出的文本流
    curl_multi_info_read — 获取当前解析的cURL的相关传输信息
    curl_multi_init — 返回一个新cURL批处理句柄
    curl_multi_remove_handle — 移除curl批处理句柄资源中的某个句柄资源
    curl_multi_select — 等待所有cURL批处理中的活动连接
    curl_multi_setopt — 为 cURL 并行处理设置一个选项
    curl_multi_strerror — 返回描述错误码的字符串文本

    我们可以利用这些多线程函数,实现 cURL 并发请求,从而实现并发 SQL!

    cURL并发请求,服务端接口示例脚本:

    /**
     * cURL并发请求,服务端接口示例脚本
     * @filename consume.php
     * @url http://test.979137.com/ParallelSQL/consume.php
     */
    $resource = new \mysqli('localhost', 'root', '123456', 'sae', '3306');
    $resource->set_charset('utf8');
    
    $sql = "SELECT access_key,SUM(amount) sum_amount FROM consume_%d WHERE uid=%d AND product='%s' GROUP BY access_key";
    $sql = sprintf($sql, $_GET['ym'], $_GET['uid'], $_GET['product']);
    $res = $resource->query($sql);
    
    $out['code'] = $resource->errno;
    $out['message'] = $resource->error;
    $data = array();
    if (is_object($res)) {
        while(($row = $res->fetch_assoc()) != false) {
            $data[$row['access_key']] = $row['sum_amount'];
        }   
    }
    $out['data'] = $data;
    
    header('Content-Type: application/json; charset=utf-8');
    echo json_encode($out);

    cURL并发请求,客户端调用示例脚本:

    /**
     * cURL并发请求,客户端调用示例脚本
     * @filename p_curl.php
     * @url http://test.979137.com/ParallelSQL/p_curl.php
     */
    
    //期望结果集:获取以下用户的每个月每个APP的消费统计
    $top = array('979137', '555555', '666666', '888888', '999999');
    $ret = array_fill_keys($top, array());
    
    $mch = curl_multi_init();
    $opt[CURLOPT_HEADER] = 0;
    $opt[CURLOPT_CONNECTTIMEOUT] = 60; 
    $opt[CURLOPT_RETURNTRANSFER] = true;
    $opt[CURLOPT_HTTPHEADER] = array('Host: api.979137.com');
    
    //生成句柄并加入到批处理
    $cmd = array();
    foreach($top as $uid) {
        for($i = 1; $i <= 12; $i++) { $ret[$uid][$i] = $tmp = array(); $tmp['url'] = sprintf('http://127.0.0.1/ParallelSQL/consume.php?ym=2016%02d&uid=%d&product=SAE', $i, $uid); $tmp['uid'] = $uid; $tmp['month'] = $i; $tmp['resource'] = curl_init($tmp['url']); curl_setopt_array($tmp['resource'], $opt); curl_multi_add_handle($mch, $tmp['resource']); $cmd[] = $tmp; } } //并发执行,直到全部结束。 do { curl_multi_exec($mch, $active); } while ($active > 0); 
    
    //获取全部结果
    foreach($cmd as $c) {
        $res = curl_multi_getcontent($c['resource']);
        $http_code = curl_getinfo($c['resource'], CURLINFO_HTTP_CODE);
        if ($res === false || $http_code != 200) {
            die(curl_error($c['resource']));
        }   
        $res = json_decode($res, true);
        $res['code'] && die($res['message']);
        $ret[$c['uid']][$c['month']] = $res['data'];
    }
    curl_multi_close($mch);
    
    var_dump($ret);

    查询效率对比

    1、数据表机构:

    CREATE TABLE `consume_201612` (
      `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
      `uid` int(10) unsigned NOT NULL,
      `product` enum('UID','SAE','SC2','SCE','SEM','SCS','SLS') NOT NULL DEFAULT 'SAE',
      `access_key` varchar(255) NOT NULL,
      `service_code` varchar(255) NOT NULL,
      `amount` int(10) unsigned NOT NULL,
      `remark` varchar(255) NOT NULL,
      `data` text NOT NULL,
      `times` int(10) unsigned NOT NULL,
      PRIMARY KEY (`id`),
      KEY `uid_pa` (`uid`,`product`,`access_key`)
    ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

    2、数据量:总共分12张表,每张表的数量在 500万~1100万 之间,全量数据在1亿以上

    3、MySQL异步查询结果:

    $ mysql.server restart
    $ php p_async.php
    Query times: 60
    Run time: 2.4453s
    (CPU)User time: 0.087966s
    (CPU)System time: 0.117625s

    4、cURL并发请求查询结果

    $ mysql.server restart
    $ php p_curl.php
    Query times: 60
    Run time: 2.173035s
    (CPU)User time: 0.40652s
    (CPU)System time: 0.869902s

    5、普通串行查询结果

    $ mysql.server restart
    $ php p_sync.php
    Query times: 60
    Run time: 20.485623s
    (CPU)User time: 0.083185s
    (CPU)System time: 0.036566s
    Memory usage: 304.72kb

    并发执行时,我们可以在 MySQL 服务器看到所有的正在执行的SQL:
    倒流’s Bolg

    总结

    1、在并发查询下,查询效率提高了近10倍
    2、使用 MySQL 异步查询,因为需要给所有查询都创建一个新的连接,而 MySQL 服务端会为每个连接创建一个单独的线程进行处理,如果创建的线程数过多,会给系统造成负担,请谨慎使用
    3、使用 cURL 并发请求后端接口时,CPU负载明显上升,所以并发请求后端接口,一定程度上会增加后端压力,这和前端大流量下的高并发原理是一样的
    4、使用 cURL 并发请求,还需要考虑一个网络延时的问题,网络延时越小,查询效率提升越明显。如果你是想代替类方法或函数调用,在条件允许的情况,建议直接连接服务器本机即127.0.0.1
    5、在并发请求下,因为需要一次性接收全部返回结果,所以会占用更多的内存资源

    需要说明的是,在实际应用中 cURL 的并发请求,一般不只单用于数据查询,而是为了完成更多的后台业务逻辑,
    所以,在服务器负载能力允许的情况下,推荐使用 cURL 并行转发的形式,提升前端响应速度!

    最后说一下 Swoole,

    Swoole 是 PHP 的异步、并行、高性能网络通信引擎,使用纯C语言编写,提供了PHP语言的异步多线程服务器,异步TCP/UDP网络客户端,异步MySQL,异步Redis,数据库连接池,AsyncTask,消息队列,毫秒定时器,异步文件读写,异步DNS查询!
    其中的异步MySQL,其原理是通过 MYSQLI_ASYNC 模式查询,然后获取 mysql 连接的 socket,加入到 epoll 时间循环中,当数据库返回结果时会回调指定函数。
    这个过程是完全异步非阻塞的,不浪费CPU,具体实现方式这里不再详细介绍!

    php –with-mysqli=mysqlnd –with-pdo-mysql=mysqlnd

    1、什么是mysqlnd驱动?

    PHP手册上的描述:

    MySQL Native Driver is a replacement for the MySQL Client Library (libmysql).
    MySQL Native Driver is part of the official PHP sources as of PHP 5.3.0

    mysqldnd即MySQL Native Driver简写,即是由PHP源码提供的mysql驱动连接代码,它的目的是代替旧的libmysql驱动。

    传统的安装php的方式中,我们在编译PHP时,一般需要指定以下几项:

    –with-mysql=/usr/local/mysql
    –with-mysqli=/usr/local/mysql
    –with-pdo-mysql=/usr/local/mysql

    这实际上就是使用了MySQL官方自带的libmysql驱动,这是比较老的驱动,PHP5.3开始已经不建议使用它了,而建议使用mysqlnd

    2、PDO与mysqlnd, libmysql又是何种关系?

    PDO是一个应用层抽象类,底层和mysql server连接交互需要mysql驱动的支持。也就是说无论你使用了何种驱动,都可以使用PDO.
    PDO提供了PHP应用程序层API接口,而mysqlnd,libmysql则负责与mysql server进行网络协议交互(它并不提供php应用程序层API功能)

    3、为何要使用mysqlnd驱动?

    PHP官方手册描述:

    A.libmysql驱动是由mysql AB公司(现在是oracle公司)编写, 并按mysql license许可协议发布,所以在PHP中默认是被禁用的.
    而mysqlnd是由php官方开发的驱动,以php license许可协议发布,故就规避了许可协议和版权的问题

    B.因为mysqlnd内置于PHP源代码,故你在编译安装php时就不需要预先安装mysql server也可以提供mysql client API (mysql_connect, pdo , mysqli), 这将减化一些工作量.

    C. mysqlnd是专门为php优化编写的驱动,它使用了PHP本身的特性,在内存管理,性能上比libmysql更有优势. php官方的测试是:libmysql将每条记录在内存中保存了两份,而mysqlnd只保存了一份

    D. 一些新的或增强的功能
    增强的持久连接
    引入特有的函数mysqli_fetch_all()
    引入一些性能统计函数mysqli_get_cache_stats(), mysqli_get_client_stats(),
    mysqli_get_connection_stats(),
    使用上述函数,可很容易分析mysql查询的性能瓶颈!
    SSL支持(从php 5.3.3开始有效)
    压缩协议支持
    命名管道支持(php 5.4.0开始有效)

    4、看到这里,你可能跃跃欲试,很想使用mysqlnd驱动

    提示: 如果使用mysqlnd,并不需要预先安装mysql
    编译php时,修改以下几个项参数即可

    --with-mysql=mysqlnd
    --with-mysqli=mysqlnd
    --with-pdo-mysql=mysqlnd 

    PHP多线程,利用PHP cURL 多线程 模拟并发请求

    PHP cURL所有函数列表:
    http://php.net/manual/zh/ref.curl.php]http://php.net/manual/zh/ref.curl.php

    以下是PHP中cURL多线程相关函数:

    • curl_multi_add_handle — 向curl批处理会话中添加单独的curl句柄
    • curl_multi_close — 关闭一组cURL句柄
    • curl_multi_exec — 运行当前 cURL 句柄的子连接
    • curl_multi_getcontent — 如果设置了CURLOPT_RETURNTRANSFER,则返回获取的输出的文本流
    • curl_multi_info_read — 获取当前解析的cURL的相关传输信息
    • curl_multi_init — 返回一个新cURL批处理句柄
    • curl_multi_remove_handle — 移除curl批处理句柄资源中的某个句柄资源
    • curl_multi_select — 等待所有cURL批处理中的活动连接
    • curl_multi_setopt — 为 cURL 并行处理设置一个选项
    • curl_multi_strerror — Return string describing error code

    一般来说,想到要用这些函数时,目的显然应该是要同时请求多个URL,而不是一个一个依次请求,否则不如自己循环去调curl_exec好了。

    步骤总结如下:

    1、调用curl_multi_init,初始化一个批处理handle
    2、循环调用 curl_multi_add_handle,往1中的批处理handle 添加curl_init来的子handle
    3、持续调用 curl_multi_exec,直到所有子handle执行完毕。
    4、根据需要循环调用 curl_multi_getcontent 获取结果
    5、调用 curl_multi_remove_handle,并为每个字handle调用curl_close
    6、调用 curl_multi_close

    以下是一个通过并发请求抓取网页的demo:

    <?php
    $urls = array(
        'https://979137.com/',
        'http://www.sina.com.cn/',
        'http://www.sohu.com/',
        'http://www.163.com/'
    );   
    //1、初始化一个批处理handle
    $mh = curl_multi_init();
    
    //2、往批处理handle 添加curl_init来的子handle
    foreach ($urls as $i => $url) {   
        $conn[$i] = curl_init($url);   
        curl_setopt($conn[$i], CURLOPT_HEADER, 0);   
        curl_setopt($conn[$i], CURLOPT_CONNECTTIMEOUT, 60);   
        curl_setopt($conn[$i], CURLOPT_RETURNTRANSFER, true);  
        curl_multi_add_handle($mh, $conn[$i]);   
    }   
    
    //3、并发执行,直到全部结束。
    do {   
        curl_multi_exec($mh, $active);   
    } while ($active);   
    
    //4、获取结果
    foreach ($urls as $i => $url) {   
        $data = curl_multi_getcontent($conn[$i]);
        echo ($data);
        echo '<hr />';
    }
    
    //5、移除子handle,并close子handle
    foreach ($urls as $i => $url) {   
      curl_multi_remove_handle($mh,$conn[$i]);   
      curl_close($conn[$i]);   
    }   
    
    //6、关闭批处理handle
    curl_multi_close($mh);  
    

    隐藏字符串中部分字符的PHP函数,如:姓名、用户名、身份证、IP、手机号等

    <?php
    /**
     * 将一个字符串部分字符用$re替代隐藏
     * @param string    $string   待处理的字符串
     * @param int       $start    规定在字符串的何处开始,
     *                            正数 - 在字符串的指定位置开始
     *                            负数 - 在从字符串结尾的指定位置开始
     *                            0 - 在字符串中的第一个字符处开始
     * @param int       $length   可选。规定要隐藏的字符串长度。默认是直到字符串的结尾。
     *                            正数 - 从 start 参数所在的位置隐藏
     *                            负数 - 从字符串末端隐藏
     * @param string    $re       替代符
     * @return string   处理后的字符串
     */
    function hidestr($string, $start = 0, $length = 0, $re = '*') {
        if (empty($string)) return false;
        $strarr = array();
        $mb_strlen = mb_strlen($string);
        while ($mb_strlen) {
            $strarr[] = mb_substr($string, 0, 1, 'utf8');
            $string = mb_substr($string, 1, $mb_strlen, 'utf8');
            $mb_strlen = mb_strlen($string);
        }
        $strlen = count($strarr);
        $begin  = $start >= 0 ? $start : ($strlen - abs($start));
        $end    = $last   = $strlen - 1;
        if ($length > 0) {
            $end  = $begin + $length - 1;
        } elseif ($length < 0) {
            $end -= abs($length);
        }
        for ($i=$begin; $i<=$end; $i++) {
            $strarr[$i] = $re;
        }
        if ($begin >= $end || $begin >= $last || $end > $last) return false;
        return implode('', $strarr);
    }
    

    demo:

    //隐藏手机号中间4位
    hidestr('18600005940', 3, 4); //186****5940
    
    //只保留姓名里的最后一个字,常见与ATM,网银等
    hidestr('谢世亮', 0, -1); //**亮
    
    //隐藏邮箱部分内容,常见网站帐号,如支付宝等
    list($name, $domain) = explode('@', '979137@qq.com');
    hidestr($name, 1, -1) . '@' . hidestr($domain, 0, 2); // 9****7@**.com
    

    此函数用法和PHP系统函数函数substr原理和用法是一样的。只不过substr是用于截取你想要的字符串,而hidestr是隐藏你想要的字符串,用*号代替

    linux下杀死进程的N种方法

    [font=微软雅黑][size=4][b][color=SeaGreen]常规篇:[/b][/size][/font]

     首先,用ps查看进程,方法如下:

    ps -ef

    ……
    smx 1822 1 0 11:38 ? 00:00:49 gnome-terminal
    smx 1823 1822 0 11:38 ? 00:00:00 gnome-pty-helper
    smx 1824 1822 0 11:38 pts/0 00:00:02 bash
    smx 1827 1 4 11:38 ? 00:26:28 /usr/lib/firefox-3.6.18/firefox-bin
    smx 1857 1822 0 11:38 pts/1 00:00:00 bash
    smx 1880 1619 0 11:38 ? 00:00:00 update-notifier
    ……
    smx 11946 1824 0 21:41 pts/0 00:00:00 ps -ef

    或者:

    ps -aux

    ……
    smx 1822 0.1 0.8 58484 18152 ? Sl 11:38 0:49 gnome-terminal
    smx 1823 0.0 0.0 1988 712 ? S 11:38 0:00 gnome-pty-helper
    smx 1824 0.0 0.1 6820 3776 pts/0 Ss 11:38 0:02 bash
    smx 1827 4.3 5.8 398196 119568 ? Sl 11:38 26:13 /usr/lib/firefox-3.6.18/firefox-bin
    smx 1857 0.0 0.1 6688 3644 pts/1 Ss 11:38 0:00 bash
    smx 1880 0.0 0.6 41536 12620 ? S 11:38 0:00 update-notifier
    ……
    smx 11953 0.0 0.0 2716 1064 pts/0 R+ 21:42 0:00 ps -aux

    此时如果我想杀了火狐的进程就在终端输入:

    kill -s 9 1827

    其中-s 9 制定了传递给进程的信号是9,即强制、尽快终止进程。各个终止信号及其作用见附录。

    1827则是上面ps查到的火狐的PID。

    简单吧,但有个问题,进程少了则无所谓,进程多了,就会觉得痛苦了,
    无论是ps -ef 还是ps -aux,每次都要在一大串进程信息里面查找到要杀的进程,看的眼都花了。

    [font=微软雅黑][size=4][b][color=SeaGreen]改进1:[/b][/size][/font]

    把ps的查询结果通过管道给grep查找包含特定字符串的进程。管道符“|”用来隔开两个命令,管道符左边命令的输出会作为管道符右边命令的输入。

    ps -ef | grep firefox

    smx 1827 1 4 11:38 ? 00:27:33 /usr/lib/firefox-3.6.18/firefox-bin
    smx 12029 1824 0 21:54 pts/0 00:00:00 grep –color=auto firefox

    这次就清爽了。然后就是

    kill -s 9 1827

    还是嫌打字多?

    [font=微软雅黑][size=4][b][color=SeaGreen]改进2 ———使用pgrep:[/b][/size][/font]

    一看到pgrep首先会想到什么?没错,grep!pgrep的p表明了这个命令是专门用于进程查询的grep。

    pgrep firefox

    1827

    看到了什么?没错火狐的PID,接下来又要打字了:

    kill -s 9 1827

    [font=微软雅黑][size=4][b][color=SeaGreen]改进3 ——使用pidof:[/b][/size][/font]

    看到pidof想到啥?没错pid of xx,字面翻译过来就是 xx的PID。

    pidof firefox-bin

    1827

    和pgrep相比稍显不足的是,pidof必须给出进程的全名。然后就是老生常谈:

    kill -s 9 1827

    无论使用ps 然后慢慢查找进程PID
    还是用grep查找包含相应字符串的进程,
    亦或者用pgrep直接查找包含相应字符串的进程PID,然后手动输入给kill杀掉,都稍显麻烦。
    有没有更方便的方法?有!

    [font=微软雅黑][size=4][b][color=SeaGreen]改进4:[/b][/size][/font]

    ps -ef | grep firefox | grep -v grep | cut -c 9-15 | xargs kill -s 9

    说明:

    “grep firefox”的输出结果是,所有含有关键字“firefox”的进程。
    “grep -v grep”是在列出的进程中去除含有关键字“grep”的进程。
    “cut -c 9-15”是截取输入行的第9个字符到第15个字符,而这正好是进程号PID。
    “xargs kill -s 9”中的xargs命令是用来把前面命令的输出结果(PID)作为“kill -s 9”命令的参数,并执行该命令。“kill -s 9”会强行杀掉指定进程。

    难道你不想抱怨点什么?没错太长了

    [font=微软雅黑][size=4][b][color=SeaGreen]改进5:[/b][/size][/font]

    知道pgrep和pidof两个命令,干嘛还要打那么长一串!

    pgrep firefox | xargs kill -s 9

    [font=微软雅黑][size=4][b][color=SeaGreen]改进6:[/b][/size][/font]

    ps -ef | grep firefox | awk '{print $2}' | xargs kill -9

    kill: No such process

    有一个比较郁闷的地方,进程已经正确找到并且终止了,但是执行完却提示找不到进程。

    其中awk ‘{print $2}’ 的作用就是打印(print)出第二列的内容。根据常规篇,可以知道ps输出的第二列正好是PID。就把进程相应的PID通过xargs传递给kill作参数,杀掉对应的进程。

    [font=微软雅黑][size=4][b][color=SeaGreen]改进7:[/b][/size][/font]
    难道每次都要调用xargs把PID传递给kill?答案是否定的:

    kill -s 9 `ps -aux | grep firefox | awk '{print $2}'`

    [font=微软雅黑][size=4][b][color=SeaGreen]改进8:[/b][/size][/font]

    没错,命令依然有点长,换成pgrep。

    kill -s 9 `pgrep firefox`

    [font=微软雅黑][size=4][b][color=SeaGreen]改进9——pkill:[/b][/size][/font]

    看到pkill想到了什么?没错pgrep和kill!pkill=pgrep+kill。

    pkill -9 firefox

    说明:”-9″ 即发送的信号是9,pkill与kill在这点的差别是:pkill无须 “s”,终止信号等级直接跟在 “-“ 后面。之前我一直以为是 “-s 9″,结果每次运行都无法终止进程。

    [font=微软雅黑][size=4][b][color=SeaGreen]改进10——killall:[/b][/size][/font]

    killall和pkill是相似的,不过如果给出的进程名不完整,killall会报错。pkill或者pgrep只要给出进程名的一部分就可以终止进程。

    killall -9 firefox

    附录:各种信号及其用途
    [table=100%]

    Signal Description Signal number on Linux x86[1] SIGABRT Process aborted 6 SIGALRM Signal raised by alarm 14 SIGBUS Bus error: “access to undefined portion of memory object” 7 SIGCHLD Child process terminated, stopped (or continued*) 17 SIGCONT Continue if stopped 18 SIGFPE Floating point exception: “erroneous arithmetic operation” 8 SIGHUP Hangup 1 SIGILL Illegal instruction 4 SIGINT Interrupt 2 SIGKILL Kill (terminate immediately) 9 SIGPIPE Write to pipe with no one reading 13 SIGQUIT Quit and dump core 3 SIGSEGV Segmentation violation 11 SIGSTOP Stop executing temporarily 19 SIGTERM Termination (request to terminate) 15 SIGTSTP Terminal stop signal 20 SIGTTIN Background process attempting to read from tty (“in”) 21

    SIGTTOU Background process attempting to write to tty (“out”) 22

    SIGUSR1 User-defined 1 10

    SIGUSR2 User-defined 2 12

    SIGPOLL Pollable event 29

    SIGPROF Profiling timer expired 27

    SIGSYS Bad syscall 31

    SIGTRAP Trace/breakpoint trap 5

    SIGURG Urgent data available on socket 23

    SIGVTALRM Signal raised by timer counting virtual time: “virtual timer expired” 26

    SIGXCPU CPU time limit exceeded 24

    SIGXFSZ File size limit exceeded 25

    示例浅谈PHP与手机APP开发,即API接口开发

    API(Application Programming Interface,应用程序接口)架构,已经成为目前互联网产品开发中常见的软件架构模式,并且诞生很多专门API服务的公司,如:聚合数据百度APIStore

    作为最流行的服务端语言PHP(PHP: Hypertext Preprocessor),在开发API方面,是很简单且极具优势的
    这篇文章写给不太了解PHP开发API接口的开发者

    一、先简单回答两个问题

    1、PHP 可以开发客户端吗?
    答:正确的回答是,不适合,因为PHP是服务端脚本语言,负责 B/S或C/S 架构的S部分,即:Server端的开发。
    (别去纠结 GTK、WinBinder)

    2、为什么选择 PHP 作为开发服务端的首选?
    答:跨平台(可以运行在UNIX、LINUX、WINDOWS、Mac OS下)、低消耗(PHP消耗相当少的系统资源)、运行效率高(相对而言)、MySQL的完美搭档,本身是免费开源的,……

    二、如何使用 PHP 开发 API 呢?

    有兴趣细研究的,可以先看看百科介绍:
    http://baike.baidu.com/item/api/10154]http://baike.baidu.com/item/api/10154
    百科写的比较泛,嫌文字多?好吧,那就不看了,先了解下 API 是什么鬼
    1、API 比开发 WEB 更简洁,但可能逻辑更复杂,API 只返回结果,也就是只完成数据输出,不呈现页面,
    2、WEB 开发,更多的是 GET 和 POST 请求,API 还有 PUT、DELETE 请求
    3、和 WEB 开发一样,首先需要一些相关的参数,这些参数,都会由客户端传过来,也许是 GET 也许是 POST,这个需要开发团队相互之间约定好,或者制定统一规范
    4、有了参数,根据应用需求,完成数据处理,例如:获取用户信息、发朋友圈、发消息、一局游戏结束数据提交等等
    5、数据逻辑处理完之后,返回客户端所需要用到的相关数据,例如:用户信息数组、朋友圈列表、消息状态、游戏结果数据等等,那数据是怎么返给客户端呢?常见有XML、JSON,设置相应的header并把要返回的数据直接打印出来即可
    6、客户端获取到你返回的数据后,在客户端本地和用户进行交互

    所以我们大概知道,API 其实不存在Web领域的 MVC 架构模式,若要分层的,API 也只有 M 和 C 两层,当然,后端可能会有更加复杂的架构!

    通过下面一个HTTP协议的API实例来理解PHP怎么开发API:

    <?php
    /**
     * 比较标准的接口输出函数
     * @param string  $info 消息
     * @param integer $code 接口错误码,很关键的参数
     * @param array   $data 附加数据
     * $param string  $location 重定向
     * @return array
     */
    function var_json($info = '', $code = 10000, $data = array(), $location = '') {
        $out['code'] = $code ?: 0;
        $out['info'] = $info ?: ($out['code'] ? 'error' : 'success');
        $out['data'] = $data ?: array();
        $out['location'] = $location;
        header('Content-Type: application/json; charset=utf-8');
        echo json_encode($out, JSON_HEX_TAG);
        exit(0);
    }
    
    $a = empty($_GET['a']) ? '' : $_GET['a'];
    $qq = empty($_GET['qq']) ? 0 : intval($_GET['qq']);
    
    //假设这是数据源,如MySQL
    $data = array();
    $data[979136] = array('qq'=>979136, 'vip'=>5,'level'=>128, 'reg_time'=>1376523234, 'qb'=>300);
    $data[979137] = array('qq'=>979137, 'vip'=>8,'level'=>101, 'reg_time'=>1377123144, 'qb'=>300);
    
    preg_match('/^[a-zA-Z]+$/', $a) || var_json('非法调用');
    isset($data[$qq]) || var_json('用户不存在', 100001);
    
    switch ($a) {
        //获取用户基本信息
        case 'info': 
            //你的更多业务逻辑 ...
            var_json('success', 0, $data[$qq]);
            break;
        //获取动态消息
        case 'message':
            var_json('您正在调用动态消息接口', 0);
            break;
        //获取好友列表
        case 'friends':
            var_json('你正在调用好友列表接口', 0);
            break;
        default:
            var_json('非法调用');
    }
    

    把它部署到服务器之后,任何语言都可以通过HTTP协议调用诸如下面的URL接口:
    http://demo.979137.com/api/test/user.php
    http://demo.979137.com/api/test/user.php?a=info&qq=979137&ticket=test
    http://demo.979137.com/api/test/user.php?a=info&qq=979138&ticket=test
    http://demo.979137.com/api/test/user.php?a=friends&qq=979137&ticket=test
    http://demo.979137.com/api/test/user.php?a=fuck&qq=979137&ticket=test

    接口输出示例,返回的是一串json:

    {
        "code": 0,
        "info": "success",
        "data": {
            "qq": 979137,
            "vip": 8,
            "level": 101,
            "reg_time": 1377123144,
            "qb": 300
        },
        "location": ""
    }
    

    json具有很强的跨平台性,几乎每种语言都有解析json的函数,下面是一个PHP作为客户端调用的示例:

    <?php
    header('Content-type:text/html;charset=utf-8');
    $url = "http://demo.979137.com/api/test/user.php";
    $arg = array(
        'a'  => 'info',
        'qq' => '979137',
    );
    $query_string = http_build_query($arg);
    $ch = curl_init($url.'?'.$query_string);
    curl_setopt($ch, CURLOPT_HTTP_VERSION , CURL_HTTP_VERSION_1_1);
    curl_setopt($ch, CURLOPT_USERAGENT , 'QQ_Mobile_V5.5');
    curl_setopt($ch, CURLOPT_CONNECTTIMEOUT , 60 );
    curl_setopt($ch, CURLOPT_TIMEOUT , 60);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER , true);
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
    $response = curl_exec($ch);
    $httpcode = curl_getinfo($ch , CURLINFO_HTTP_CODE);
    curl_close($ch);
    if ($response === false) {
        var_dump(curl_error($ch));
    } elseif ($httpcode != 200) {
        var_dump($httpcode, '接口请求失败');
    } else {
        $ret = json_decode($response, true);
        var_dump($ret);
    }
    

    页面输出结果:

    array(4) {
        [“code”]=>int(0)
        [“info”]=>string(7) “success”
        [“data”]=>
        array(5) {
            [“qq”]=>int(979137)
            [“vip”]=>int(8)
            [“level”]=>int(101)
            [“reg_time”]=>int(1377123144)
            [“qb”]=>int(300)
        }
        [“location”]=>string(0) “”
    }

    实际业务中,就是拿到了接口返回的数据之后,结合自身的业务为用户提供服务!

    三、实际项目中,我们在开发 API 时应该注意的几个点(仅供参考)

    1、单文件实现多接口的形式有很多种,例如:if..elseif.. 或 switch 或 很多框架里用到的统一入口通过调用类函数体
    2、数据输出建议使用json,json具有很强的跨平台性,大多编程语言都支持json解析,json正在逐步取代xml,成为网络数据的通用格式
    3、为了保证接口安全,一定要加入鉴权体系
    4、对于线上的API,务必关闭所有错误显示同时把错误写到日志里,PHP中可以通过 error_reporting(0) 屏蔽所有错误
    这样做的目的,一方面是保护接口安全,防止输出不该打印的错误信息
    另一方面是保证输出的是正确的数据格式,如json,假如不是标准的json格式,客户端在解析时就会出错,由此影响客户端的正常运转
    PS:我们平时在使用手机APP时,手机会闪退,多半是这个原因,即接口调用异常
    5、开发 API 和 WEB 有一定的区别,如果是 WEB 的话,如果程序写的有问题,比如有个notice 或 warning 级别的错误,在 WEB 里可能不会有什么问题,也许就只是导致 WEB 的某个部分错位或乱码。但如果是 API,就会严重调用的客户端了,如果是手机APP,那闪推啥的,是必然的,如果同样也是Web调用,也可能会出现 Server Error 了
    6、一定要重点考虑稳定性和响应速度,因为我们在使用手机APP时,都不希望APP经常闪推、而且希望应用很流畅
    7、不要随便使用一些 PHP 开源框架,原因概括起来有两点:
    1)如6所述,客户端一般对 API 响应速度有极高要求,目前PHP领域的开源框架非常多,根据笔者的了解,目前比较流行的框架,普遍做的比较重,而且基本都是为WEB而生,因此,框架多了很多 API 用不到的东西,框架在加载和执行冗余文件时,实际是在消耗你的性能
    2)如4和5所述,框架对于WEB开发,是件很幸福的事,但对于 API 而言,你实在不敢想象它会给你出什么岔子,因为很多框架并没有全面的考虑到 API 场景

    关于这点,还是有很多人纠结的,认为用框架没问题,这个怎么说呢,关键还得看架构和编码之人,有些人做的架构、写的代码,远不如框架写的稳定、跑得快的也比比皆是!
    这里只是建议,关键看自己的实际情况,上线前测试全面才是硬道理!

    话说,牛逼的架构,一般都有自己的 API 框架!

    这里给 ThinkPHP 打个广告:
    目前 ThinkPHP 5ThinkPHP 家族的一个颠覆性重构版本,Slogan:为 API 而生。
    本站数据接口版面提供的所有API,均采用 ThinkPHP 5框架开发,并部署在腾讯云

    再扯一下腾讯、微博、淘宝等这些个开放平台吧,
    它们所谓的开放,其实就是给开发者提供一系列的API,开发者根据他们提供的技术文档,按规定的调用方法,调它们提供的接口,你就可以获取到他们的相关信息,
    例如:QQ用户基本信息、微博登录、淘宝店铺、商品消息等等。然后开发者再根据这些数据,在你的应用里完成交互。
    另外,我们常用的 Ajax 其实也是 API 调用的一种体现形式

    最新文章

    Return Top