Tagged: PHP

简单实用的PHPMonitor:运行错误监控

所有运行在线网环境的程序应该都被监控,对线网而言,无论是 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个错误级别:
https://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)

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

<?php
//是否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服务器如 ApacheNginx
写一个测试脚本 test.php

<?php
var_dump($tencent);

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

php -f test.php
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 的搭建教程了,这里就不重复写了!

下面是一个基于ELK搭建好的日志中心,好不好用,用了就知道了!^_^

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

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

ElasticSearch官方提供了一个PHP版本的SDK: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' => 18666665940,
    'xieshiliang' => 18600005940,
);

//需要告警的错误级别
$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

坐等告警吧!

对于报警方案,目前市场上也有很多实现方案,如 Yelp 公司的 ElastAlert,用 Python 写的一个基于 ELKElasticsearch RESTful 报警框架

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

MySQL异步查询示例脚本:

<?php
/**
 * 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并发请求,服务端接口示例脚本:

<?php
/**
 * 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并发请求,客户端调用示例脚本:

<?php
/**
 * 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:

总结

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时,修改以下几个项参数即可
[code lang="shell"]--with-mysql=mysqlnd
--with-mysqli=mysqlnd
--with-pdo-mysql=mysqlnd [/code]

PHP也支持多线程:cURL并发请求

PHP cURL所有函数列表:
https://secure.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

$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);
}

//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);
}

测试:
//隐藏手机号中间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是隐藏你想要的字符串,用*号代替

如何保存一个网页至桌面上成为快捷方式,PHP生成桌面快捷方式

PHP生成桌面快捷方式,最核心就是通过header函数设置头部信息!

<?php
header("Content-Type: application/octet-stream; charset=utf8");
header("Content-Disposition: attachment; filename=云厉的博客.url");
$shortcut = array(
    '[InternetShortcut]',
    //链接地址
    'URL=979137.com',
    'IDList=',
    //ICON文件地址,必须是HTTP绝对地址
    'IconFile=(http://cdn.979137.com/favicon.ico)',
    'IconIndex=1',
    //注册表值
    '[{000214A0-0000-0000-C000-000000000046}]',
    'Prop3=19,2',
);
echo implode(PHP_EOL, $shortcut);

不依赖扩展,PHP生成二维码类

PHP QR Code 是一个PHP二维码生成类库,利用它可以轻松生成二维码,注:请确保你的PHP支持GD2
项目地址:http://phpqrcode.sourceforge.net

  • 该类库原生不支持生成固定大小的二维码图片的(默认是根据二维码数据内容的长度而变化大小)
  • 该类库不支持生成带有icon的二维码

我对该类库进行了修改,同时封装了一个QRcode类,简化操作的同时,实现了上述两个功能:

<?php
/**
 * 二维码服务.
 *
 * @author 979137@qq.com
 * @copyright ©2015, Sina App Engine.
 * @version $Id$
 */
class SaeQRcode {
    private $errMsg = 'success';
    private $errNum = 0;

    //二维码配置参数
    public $data    = '';
    public $level   = 'M';
    public $width   = 200;
    public $height  = 200;
    public $margin  = 0;
    public $icon    = '';
    public $saveUrl = '';

    //生成的二维码文件
    private $code   = '';

    /**
     * 生成二维码图片 
     * 
     * @desc
     * 
     * @access public
     * @return void
     * @exception none
     */
    public function build() {
        static $qrcode = false;
        if (!$qrcode) {
            include(__DIR__.'/phpqrcode.php');
            $qrcode = true;
        }
        if (trim($this->data) == '') {
            $this->errNum = -1;
            $this->errMsg = 'data cannot be empty!';
            return false;
        } elseif (!in_array($this->level, array('L','M','Q','H'))) {
            $this->errNum = -2;
            $this->errMsg = 'level optional values: L, M, Q, H';
            return false;
        } elseif (!is_numeric($this->width) || !is_numeric($this->height)) {
            $this->errNum = -3;
            $this->errMsg = 'width and height parameter error';
            return false;
        }
        $this->code = $this->$saveUrl . md5((microtime(true)*10000).uniqid(time())) . '.png';
        try {
            defined('QRCODE_IMG_W') or define('QRCODE_IMG_W', $this->width);
            defined('QRCODE_IMG_H') or define('QRCODE_IMG_H', $this->height);
            QRcode::png($this->data, $this->code, $this->level, 3, $this->margin);
        } catch(Exception $e) {
            $this->errNum = -4;
            $this->errMsg = $e->getMessage();
            return false;
        }
        if (trim($this->icon) != '') {
            return $this->iconCover() ? $this->code : false;
        }
        return $this->code;
    }

    /**
     * icon覆盖
     * 
     * @desc
     * 
     * @access public
     * @return boolean
     * @exception none
     */
    public function iconCover() {
        if (!is_file($this->code) || $this->fileType($this->code) != 'png') {
            $this->errNum = -10;
            $this->errMsg = 'QRcode file does not exist or file type is not supported(Only allow PNG)';
            return false;
        }
        //远程icon,先下载到本地
        if (filter_var($this->icon, FILTER_VALIDATE_URL)) {
            //TODO..
        }
        if (!is_file($this->icon) || !in_array($this->fileType($this->icon), array('png','jpg','gif'))) {
            $this->errNum = -11;
            $this->errMsg = 'icon file does not exist or file type is not supported(Only allow PNG,JPG,GIF)';
            return false;
        }
        $codeData = file_get_contents($this->code);
        $iconData = file_get_contents($this->icon);
        $code = imagecreatefromstring($codeData);
        $icon = imagecreatefromstring($iconData);
        list($code_w, $code_h) = array(imagesx($code), imagesy($code));
        list($icon_w, $icon_h) = array(imagesx($icon), imagesy($icon));
        //目标宽高(等比例缩小)
        $icon_code_w = $code_w / 5;
        $scale = $icon_w / $icon_code_w;
        $icon_code_h = $icon_h / $scale;
        //目标XY坐标(将icon置于二维码正中间)
        $dst_x = ($code_w - $icon_code_w) / 2;
        $dst_y = ($code_h - $icon_code_h) / 2;
        imagecopyresampled($code, $icon, $dst_x, $dst_y, 0, 0, $icon_code_w, $icon_code_h, $icon_w, $icon_h);
        return imagepng($code, $this->code);
    }

    /**
     * 取二进制文件头快速准确判断文件类型
     * 
     * @desc
     * 
     * @access public
     * @params $file 要判断的文件,支持相对和绝对路径
     * @return void
     * @exception none
     */
    public function fileType($file) {
        $filepath = realpath($file);
        $filetype = array(
            7790=>'exe', 7784=>'midi',
            8075=>'zip', 8297=>'rar',
            7173=>'gif', 6677=>'bmp', 13780=>'png', 255216=>'jpg'
        );
        if (!($fp = @fopen($filepath, 'rb'))) return false;
        $bin = fread($fp, 2);
        fclose($fp);
        $str_info = @unpack('C2chars', $bin);
        $str_code = intval($str_info['chars1'].$str_info['chars2']);
        return isset($filetype[$str_code]) ? $filetype[$str_code] : false;
    }

    /**
     * 获取错误信息 
     * 
     * @desc
     * 
     * @access public
     * @return string
     * @exception none
     */
    public function errmsg() {
        $ret = $this->errMsg;
        $this->errMsg = 'Success';
        return $ret;
    }

    /**
     * 获取错误码 
     * 
     * @desc
     * 
     * @access public
     * @return int
     * @exception none
     */
    public function errno() {
        $ret = $this->errNum;
        $this->errNum = 0;
        return $ret;
    }
}

调用示例:

<?php
//二维码名片,格式参考:http://en.wikipedia.org/wiki/VCard
$vCard  = 'BEGIN:VCARD'.PHP_EOL;
$vCard .= 'VERSION:4.0'.PHP_EOL;
$vCard .= 'FN:倒流'.PHP_EOL;
$vCard .= 'ORG:SINA Inc'.PHP_EOL;
$vCard .= 'TITLE:攻城师'.PHP_EOL;
$vCard .= 'TEL;WORK;VOICE:(010)62676155'.PHP_EOL;
$vCard .= 'TEL;HOME;VOICE:(010)88889999'.PHP_EOL;
$vCard .= 'TEL;TYPE=cell:18600005940'.PHP_EOL;
$vCard .= 'ADR;TYPE=work;LABEL="Office":理想国际大厦17层;北四环西路58号;海淀区;北京市;中国;100089'.PHP_EOL;
$vCard .= 'EMAIL:979137@qq.com'.PHP_EOL;
$vCard .= 'END:VCARD';
//注:不同的扫描工具解码方式不一样,所以不是所有的二维码扫描工具都能唤起相关的功能
$types  = array(
    'vCard'   => $vCard,
    'url'     => 'http://sae.sina.com.cn',
    'tel'     => 'tel:18600005940',
    'smsto'   => 'smsto:18600005940:晚上继续嗨皮',
    'mailto'  => 'mailto:979137@qq.com?subject='.urlencode('恭喜发财').'&body='.urlencode('红包拿来'),
    'skype'   => 'skype:'.urlencode('Skype用户名').'?call',
    'chinese' => '中文二维码内容',
);

$qr = new QRcode();
//设置二维码生成参数
//二维码内容数据
$qr->data   = $types['vCard'];
//校正级别(容错率):L(7%)、M(15%)、Q(25%)、H(30%),了解:http://baike.baidu.com/view/4144600.htm
$qr->level  = 'L';
//二维码宽高(包含间距),为保证二维码更易识别,请尽量保持二维码为正方形,即长宽大致相等,默认200*200
$qr->width  = 300;
$qr->height = 300;
//二维码图片边缘间距值,值越大,间距越宽,可自由调整,默认0
$qr->margin = 1;
//在二维码正中间放置icon,默认为空,即不放置,支持绝对与相对地址
$qr->icon   = __DIR__ . '/logo.png';
$qr->icon   = 'logo.png';
//图片保存路径
$qr->saveUrl = SAE_TMP_PATH;
//生成二维码图片,成功返回文件绝对地址,失败返回false
$file = $qr->build();
if (!$file) {
    var_dump($qr->errno(), $qr->errmsg());
    exit;
}

//直接输出图片
//header('Content-Type: image/png');
//exit(file_get_contents($file));

//根据实际需求,可上传至Storage(这里以SAE为例)
$name = 'test/'.pathinfo($file, PATHINFO_BASENAME);
$domain = 'public';
$st = new SaeStorage();
$st->upload($domain, $name, $file);
$url = sprintf('http://%s-%s.stor.sinaapp.com/%s', $_SERVER['HTTP_APPNAME'], $domain, $name);
echo '<img src="'.$url.'">';

云厉技术博客

PHP数组转XML函数:array2xml

<?php
 * 数组转XML函数
 * @author 979137@qq.com
 * @param array $arr 要转换的数组 
 * @param string $item 默认节点名称
 * @param object $xml XML节点对象
 * @return string XML格式的字符串
 */
function array2xml($arr, $item='item', $xml=NULL) {
    is_null($xml) && $xml = new \SimpleXMLElement('<xml></xml>');
    foreach ($arr as $key=>$val) {
        is_numeric($key) && $key = $item;
        if (is_array($val) || is_object($val)) {
            $child = $xml->addChild($key);
            array2xml($val, $item, $child);
        } elseif (is_numeric($val)) {
            $child = $xml->addChild($key, $val);
        } else {
            $child = $xml->addChild($key);
            $node = dom_import_simplexml($child);
            $_val = $node->ownerDocument->createCDATASection($val);
            $node->appendChild($_val);
        }
    }
    return $xml->asXML();
}

在新浪云SAE上实现PHP锁机制,简单解决并发问题

很多时候需要让系统的某些操作串行化,如抢购(先到先得)、报名等,这个时候就要对这些操作来加上一把锁。(好比上厕所,需要挨个来,待你用完之后把门打开,别人再进去,保证厕所永远只有1个人, 上厕所的过程是串行化的)

也许通过文件(加锁创建一个文件/解锁再删掉,或直接使用flock函数) 或者 MySQL结合InnoDB引擎 去实现,方法也比较多。但个人感觉都不是很优秀,尤其在新浪云SAE这种不支持IO的PaaS上,文件锁的实现方式是不能用的。

在SAE,其实有非常多的方法、很简单的实现这个需求,这里写个利用 Counter 服务模拟锁的实现方法:

<?php

class Lock {

    const LKEY = 'GOLD_LOCK';

    static private $ct = NULL;

    //初始化操作,取得计数器实例,如果计数器不存在则创建
    static public function init() {
        is_null(self::$ct) && self::$ct = new SaeCounter();
        self::$ct->exists(self::LKEY) || self::$ct->create(self::LKEY);
    }   

    //加锁
    static public function add() {
        self::init();
        return self::$ct->set(self::LKEY, 1); 
    }   

    //开锁
    static public function del() {
        self::init();
        return self::$ct->set(self::LKEY, 0); 
    }   

    //获取锁状态
    static public function get() {
        self::init();
        return self::$ct->get(self::LKEY) > 0;
    }   
}

//1、判断是否已加锁
if (Lock::get()) {
    /*
     * 这里可以直接exit
     * 也可以while写个死循环结合sleep,实现类似小米抢购的那种等待状态,在服务器内部进行排队抢购
     * ....
     * ....
     */   
} 

//2、加锁
Lock::add();

/*
 * 这里就是你业务逻辑代码
 * ....
 * ....
 */

//3、开锁
Lock::del();

Counter是SAE为开发者提供的计数器服务,用来实现高并发情景下的计数功能。用户可以在控制面板或程序中创建计数器,通过API对计数器进行设置,加减和统计。
Counter简化了计数应用的开发.开发者可以轻松实现高并发情景下的计数功能,实现如兔年春晚投票,广告渠道访问计数等应用,同时可以使用Counter的统计功能轻松实现数据汇总。

你可以通过SAE的KVDB、Memcache 等服务,轻松实现串行,当然还有专门为队列而生的 TaskQueue,这里就不一一介绍了

linux下杀死进程的N种方法

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

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

[code]ps -ef[/code]

……
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

或者:
[code]ps -aux[/code]

……
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

此时如果我想杀了火狐的进程就在终端输入:
[code]kill -s 9 1827[/code]

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

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

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

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

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

[code]ps -ef | grep firefox[/code]

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

这次就清爽了。然后就是
[code]kill -s 9 1827[/code]

还是嫌打字多?

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

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

[code]pgrep firefox[/code]

1827

看到了什么?没错火狐的PID,接下来又要打字了:
[code]kill -s 9 1827[/code]

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

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

[code]pidof firefox-bin[/code]

1827

和pgrep相比稍显不足的是,pidof必须给出进程的全名。然后就是老生常谈:
[code]kill -s 9 1827[/code]

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

[font=微软雅黑][size=4][b][color=SeaGreen]改进4:[/b][/size][/font]
[code]ps -ef | grep firefox | grep -v grep | cut -c 9-15 | xargs kill -s 9[/code]

说明:

“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两个命令,干嘛还要打那么长一串!
[code]pgrep firefox | xargs kill -s 9[/code]

[font=微软雅黑][size=4][b][color=SeaGreen]改进6:[/b][/size][/font]
[code]ps -ef | grep firefox | awk '{print $2}' | xargs kill -9[/code]

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?答案是否定的:
[code]kill -s 9 `ps -aux | grep firefox | awk '{print $2}'`[/code]

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

没错,命令依然有点长,换成pgrep。
[code]kill -s 9 `pgrep firefox`[/code]

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

看到pkill想到了什么?没错pgrep和kill!pkill=pgrep+kill。
[code]pkill -9 firefox[/code]

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

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

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

附录:各种信号及其用途
[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
Return Top