存档

‘PHP’ 分类的存档

关于HHVM/Hack/Jit和PHP的性能问题

2014年4月18日 没有评论

最近HHVM/Hack很火,群里有不少人已经开始预言HHVM将会取代Zend。

HHVM为什么比Zend PHP快?

读过PHP内核实现文章的人都懂得PHP的zend vm是一个很高级的虚拟机,zend先将PHP代码进行编译生成一种二进制指令,称之为opcode。然后再用zend vm逐条执行。而PHP的用户函数、运行时局部变量,全局变量,常量都是放在一个Hashtable中。每一条opcode指令都对应一个C函数。

执行一次C函数的开销主要是1:参数的入栈出栈,2:CPU寄存器状态保存。比如在PHP里执行1千万次加法。

$j = 0;
for ($i = 0; $i < 10000000; $i++) {
    $j += $i;
}

这里会发生1000万次函数调用。但如果是编译为机器码就快得多了。一个2.0GHz的CPU,理论上每秒可以执行20亿次指令。如果换成函数调用,1秒差不多只能运行一千万次。所以编译为机器码执行的语言,如C/C++,Golang或者拥有JIT的语言,如Java,Node.js(V8),LuaJIT,HHVM单从指令执行的角度上来看至少比PHP快几十倍。

对于字符串处理,JSON解码编码,iconv编码转换,数组操作等,PHP比C++,Java慢,这是不正确的。在PHP里这类操作都是由C扩展函数完成的,性能与编译型语言一致。所以真正慢的只是PHP的写的代码,如用户函数,类和对象操作等。

运算密集型/IO密集型

运算密集型程序,程序内需要大量执行内存复制操作、循环、运算指令等。此类程序的瓶颈就是CPU,增加性能的方法就是提升CPU硬件配置,改进算法,提升语言/工具的执行性能。对于此类程序,PHP的性能问题就会凸显出来。执行相同的逻辑,PHP要比C/C++慢几十倍甚至上百倍,这是不可接受的。

IO密集型程序,程序内主要的瓶颈是IO等待,比如我们有一个Http请求要执行100ms才返回结果,其中有90ms在查询数据库,8ms的时间在读写文件。那无论使用C/C++还是PHP,请求响应时间总是100ms左右。语言性能优化只有这2ms的空间。

我们常见的程序里大部分都是IO密集型程序,很少有运算密集的,语言本身的性能差异可以忽略。遇到需要密集计算的模块,也可以使用C/C++来实现,并提供PHP扩展。

像HHVM这样提升PHP性能的工具,在超大型PHP集群确实是有价值的,提升性能后可以节约一部分机器成本。

 

分类: Linux, PHP, PHP系统编程 标签:

PHP开发异步高性能的MySQL代理服务器

2014年3月6日 2 条评论

MySQL数据库对每个客户端连接都会分配一个线程,所以连接非常宝贵。开发一个异步的MySQL代理服务器,PHP应用服务器可以长连接到这台Server,既减轻MYSQL的连接压力,又使PHP保持长连接减少connect/close的网络开销。

此Server考虑到了设置了数据库连接池尺寸,区分忙闲,mysqli断线重连,并设置了负载保护。基于swoole扩展开发,io循环使用epoll,是全异步非阻塞的,可以应对大量TCP连接。

程序的逻辑是:启动时创建N个MySQL连接,收到客户端发来的SQL后,分配1个MySQL连接,将SQL发往数据库服务器。然后等待数据库返回查询结果。当数据库返回结果后,再发给对应的客户端连接。

核心的数据结构是3个PHP数组。idle_pool是空闲的数据库连接,当有SQL请求时从idle_pool中移到busy_pool中。当数据库返回结果后从busy_pool中再移到idle_pool中,以供新的请求使用。当SQL请求到达时如果没有空闲的数据库连接,那会自动加入到wait_queue中。一旦有SQL完成操作,将自动从wait_queue中取出等待的请求进行处理。

如此循环使用。由于整个服务器是异步的单进程单线程所以完全不需要锁。而且是完全异步的,效率非常高。

当然本文的代码,如果要用于生产环境,还需做更多的保护机制和压力测试。在此仅抛砖引玉,提供一个解决问题的思路。


class DBServer
{
    protected $pool_size = 20;
    protected $idle_pool = array(); //空闲连接
    protected $busy_pool = array(); //工作连接
    protected $wait_queue = array(); //等待的请求
    protected $wait_queue_max = 100; //等待队列的最大长度,超过后将拒绝新的请求

    /**
     * @var swoole_server
     */
    protected $serv;

    function run()
    {
        $serv = new swoole_server("127.0.0.1", 9509);
        $serv->set(array(
            'worker_num' => 1,
        ));

        $serv->on('WorkerStart', array($this, 'onStart'));
        //$serv->on('Connect', array($this, 'onConnect'));
        $serv->on('Receive', array($this, 'onReceive'));
        //$serv->on('Close', array($this, 'onClose'));
        $serv->start();
    }

    function onStart($serv)
    {
        $this->serv = $serv;
        for ($i = 0; $i < $this->pool_size; $i++) {
            $db = new mysqli;
            $db->connect('127.0.0.1', 'root', 'root', 'test');
            $db_sock = swoole_get_mysqli_sock($db);
            swoole_event_add($db_sock, array($this, 'onSQLReady'));
            $this->idle_pool[] = array(
                'mysqli' => $db,
                'db_sock' => $db_sock,
                'fd' => 0,
            );
        }
        echo "Server: start.Swoole version is [" . SWOOLE_VERSION . "]\n";
    }

    function onSQLReady($db_sock)
    {
        $db_res = $this->busy_pool[$db_sock];
        $mysqli = $db_res['mysqli'];
        $fd = $db_res['fd'];

        echo __METHOD__ . ": client_sock=$fd|db_sock=$db_sock\n";

        if ($result = $mysqli->reap_async_query()) {
            $ret = var_export($result->fetch_all(MYSQLI_ASSOC), true) . "\n";
            $this->serv->send($fd, $ret);
            if (is_object($result)) {
                mysqli_free_result($result);
            }
        } else {
            $this->serv->send($fd, sprintf("MySQLi Error: %s\n", mysqli_error($mysqli)));
        }
        //release mysqli object
        $this->idle_pool[] = $db_res;
        unset($this->busy_pool[$db_sock]);

        //这里可以取出一个等待请求
        if (count($this->wait_queue) > 0) {
            $idle_n = count($this->idle_pool);
            for ($i = 0; $i < $idle_n; $i++) {
                $req = array_shift($this->wait_queue);
                $this->doQuery($req['fd'], $req['sql']);
            }
        }
    }

    function onReceive($serv, $fd, $from_id, $data)
    {
        //没有空闲的数据库连接
        if (count($this->idle_pool) == 0) {
            //等待队列未满
            if (count($this->wait_queue) < $this->wait_queue_max) {
                $this->wait_queue[] = array(
                    'fd' => $fd,
                    'sql' => $data,
                );
            } else {
                $this->serv->send($fd, "request too many, Please try again later.");
            }
        } else {
            $this->doQuery($fd, $data);
        }
    }

    function doQuery($fd, $sql)
    {
        //从空闲池中移除
        $db = array_pop($this->idle_pool);
        /**
         * @var mysqli
         */
        $mysqli = $db['mysqli'];

        for ($i = 0; $i < 2; $i++) {
            $result = $mysqli->query($sql, MYSQLI_ASYNC);
            if ($result === false) {
                if ($mysqli->errno == 2013 or $mysqli->errno == 2006) {
                    $mysqli->close();
                    $r = $mysqli->connect();
                    if ($r === true) continue;
                }
            }
            break;
        }

        $db['fd'] = $fd;
        //加入工作池中
        $this->busy_pool[$db['db_sock']] = $db;
    }
}

$server = new DBServer();
$server->run();
分类: Linux, PHP, Swoole扩展 标签:

swoole的异步和同步

2014年2月1日 没有评论

有人问node.js和swoole有何不同,最大的不同当然是语言了,一个是Javascript一个是PHP。除此之外最大的不同是,swoole不仅支持异步,还支持同步。而node.js只支持异步,代码中不能有任何阻塞,否则程序就会变得效率很差。

swoole如果选择异步模式,所有网络连接应当使用swoole_client。其他数据库查询,API调用都应当使用swoole_event_add将socket加入到异步事件循环中。一些必须要阻塞的操作就用swoole提供的task/finish功能进行异步化。异步方式单个worker进程的程序效率很高,不存在IOWait,所以一个进程就能同时处理成千上万的请求,只需要开启CPU核数的1倍或2倍的worker进程即可。

如果选择阻塞模式,那在你的代码中就像php-fpm或mod_php中那样编码即可。另外根据系统的负载情况设定足够多的worker进程。比如每个请求需要50ms,那单个worker进程只能达到20QPS。启动100个worker进程就可以有2000QPS的处理能力。当然进程越多那浪费在进程间切换的CPU时间就会越多,系统的效率越来越差。

有人还提出假如Server的处理能力是100QPS,那同时并发10000个请求怎么办?这个问题就像银行窗口一样,假如他们的处理能力是每小时100个客户,假设来了1万个人,那只能排队等候处理或者丢弃一部分人。Server也是一样,超过处理能力的请求都会排队放在socket缓冲区中,如果socket缓冲区也满了,那新的请求就将丢弃。

分类: PHP, Swoole扩展 标签:

基于swoole扩展实现真正的PHP数据库连接池

2014年1月15日 2 条评论

PHP的数据库连接池一直以来都是一个难题,很多从PHP语言转向Java的项目,大多数原因都是因为Java有更好的连接池实现。PHP的MySQL扩展提供了长连接的API,但在PHP机器数量较多,规模较大的情况下,mysql_pconnect非但不能节约MySQL资源,反而会加剧数据库的负荷。

假设有100台PHP的应用服务器,每个机器需要启动100个apache或fpm工作进程,那每个进程都会产生一个长连接到MySQL。这一共会产生1万个My SQL连接。大家都知道MySQL是每个连接会占用1个线程。那MYSQL就需要创建1万个线程,这样大量的系统资源被浪费在线程间上下文切换上。而你的业务代码中并不是所有地方都在做数据库操作,所以这个就是浪费的。

连接池就不同了,100个worker进程,公用10个数据库连接即可,当操作完数据库后,立即释放资源给其他worker进程。这样就算有100台PHP的服务器,那也只会创建1000个MySQL的连接,完全可以接受的。

以前确实没有好的办法来解决此问题的,现在有了swoole扩展,利用swoole提供的task功能可以很方便做出一个连接池来。

代码如下:


$serv = new swoole_server("127.0.0.1", 9508);
$serv->set(array(
    'worker_num' => 100,
    'task_worker_num' => 10, //MySQL连接的数量
));

function my_onReceive($serv, $fd, $from_id, $data)
{
    //taskwait就是投递一条任务,这里直接传递SQL语句了
    //然后阻塞等待SQL完成
    $result = $serv->taskwait("show tables");
    if ($result !== false) {
        list($status, $db_res) = explode(':', $result, 2);
        if ($status == 'OK') {
            //数据库操作成功了,执行业务逻辑代码,这里就自动释放掉MySQL连接的占用
            $serv->send($fd, var_export(unserialize($db_res), true) . "\n");
        } else {
            $serv->send($fd, $db_res);
        }
        return;
    } else {
        $serv->send($fd, "Error. Task timeout\n");
    }
}

function my_onTask($serv, $task_id, $from_id, $sql)
{
    static $link = null;
    if ($link == null) {
        $link = mysqli_connect("127.0.0.1", "root", "root", "test");
        if (!$link) {
            $link = null;
            $serv->finish("ER:" . mysqli_error($link));
            return;
        }
    }
    $result = $link->query($sql);
    if (!$result) {
        $serv->finish("ER:" . mysqli_error($link));
        return;
    }
    $data = $result->fetch_all(MYSQLI_ASSOC);
    $serv->finish("OK:" . serialize($data));
}

function my_onFinish($serv, $data)
{
    echo "AsyncTask Finish:Connect.PID=" . posix_getpid() . PHP_EOL;
}

$serv->on('Receive', 'my_onReceive');
$serv->on('Task', 'my_onTask');
$serv->on('Finish', 'my_onFinish');
$serv->start();

这里task_worker_num就是要启用的数据库连接池数量,worker进程为100时,连接池数量为10就可以设置为worker_num = 100, task_worker_num = 10。

Yii/Yaf/Swoole3个框架的压测性能对比

2013年12月18日 1 条评论

本次压测使用的是一台4核I5 CPU,8G内存的笔记本。使用的版本是:

swoole_framework github最新版
Yaf 2.2.9
Yii-1.1.14

php-fpm和swoole应用服务器均启用了8个进程,因为仅比较hello world,不存在阻塞,8进程足以压满CPU。php5-fpm中已经启用了apc对所有PHP进行缓存。

压测使用ab工具,参数为:

 ab -c 100 -n 10000

Yaf

Server Software: nginx/1.2.6
 Server Hostname: localhost
 Server Port: 80
 Document Path: /yafapp/
 Document Length: 11 bytes
 Concurrency Level: 100
 Time taken for tests: 1.336 seconds
 Complete requests: 10000
 Failed requests: 0
 Write errors: 0
 Total transferred: 1770000 bytes
 HTML transferred: 110000 bytes
 Requests per second: 7486.03 [#/sec] (mean)
 Time per request: 13.358 [ms] (mean)
 Time per request: 0.134 [ms] (mean, across all concurrent requests)
 Transfer rate: 1293.97 [Kbytes/sec] received

Yii

Server Software: nginx/1.2.6
 Server Hostname: localhost
 Server Port: 80
 Document Path: /yiiapp/
 Document Length: 11 bytes
 Concurrency Level: 100
 Time taken for tests: 8.016 seconds
 Complete requests: 10000
 Failed requests: 0
 Write errors: 0
 Total transferred: 1770000 bytes
 HTML transferred: 110000 bytes
 Requests per second: 1247.55 [#/sec] (mean)

SwooleFramework

 Server Software: Swoole
 Server Hostname: 127.0.0.1
 Server Port: 8888
 Document Path: /hello/index
 Document Length: 11 bytes 
 Concurrency Level: 100
 Time taken for tests: 1.470 seconds
 Complete requests: 10000
 Failed requests: 0
 Write errors: 0
 Total transferred: 2750000 bytes
 HTML transferred: 110000 bytes
 Requests per second: 6801.78 [#/sec] (mean)
 Time per request: 14.702 [ms] (mean)
 Time per request: 0.147 [ms] (mean, across all concurrent requests)
 Transfer rate: 1826.65 [Kbytes/sec] receive

结果评价

有些人说框架的消耗和业务逻辑相比不算什么,这句话在你的QPS不到10个时是完全正确的。但如果你的网站访问量很大,机器负载很高,那么框架的性能消耗就会很明显的体现出来了。

Yii压测只有1200QPS,假设网站的平均QPS为200,因为框架的消耗是纯CPU消耗,也就是光Yii就会带来(200/1200)*100%=16.7%的CPU消耗。而Yaf/Swoole的QPS可达7000,200qps仅占用

(200/7000)*100%=2.8%的CPU消耗。当然200QPS对于一个巨型网站来说只是一个小数目,框架的CPU消耗会随着QPS增加而直线上升。

Swoole框架的早期版本可达9000QPS,swoole框架本身还是很复杂的,因为全部是PHP的代码实现,仅底层使用了swoole扩展,如果做了足够的精简和优化,还是有很大的上升空间。

压测证明Yaf的性能非常好。如果你希望使用PHP应用服务器可以选择Swoole,如果是LAMP/LNMP架构可以使用Yaf框架。

分类: PHP, Swoole扩展, Swoole框架 标签:

PHP实现真正的异步MySQL

2013年11月7日 1 条评论

node.js之类的语言可以实现异步的数据库查询功能,执行SQL语句之后不必等待数据库返回结果。继续去执行其他的代码,当数据库返回结果是再对数据进行处理,如渲染页面,并将HTML页面发送给客户端。这样应用程序完全不需要阻塞等待。这种方式运行效率非常高。

PHP中是否可以实现类似的异步非阻塞MySQL查询呢?使用github搜索发现一个项目貌似是做此功能的,https://github.com/kaja47/async-mysql,查看代码是基于React.PHP.的。但并不是真正的异步SQL。实现的原理是设置一个定时器,每0.02秒轮询一次。虽然也可以用,但这样很浪费CPU资源。不是真正的异步MYSQL。

现在Swoole1.6.2提供了swoole_event_add和swoole_get_mysqli_sock 2个新的函数,使用它完全可以实现真正的PHP异步MySQL。下面讲一下具体的实现。

代码:


$db = new mysqli;
$db->connect('127.0.0.1', 'root', 'root', 'test');
$db->query("show tables", MYSQLI_ASYNC);
swoole_event_add(swoole_get_mysqli_sock($db), function($db_sock) {
global $db;
$res = $db->reap_async_query();
var_dump($res->fetch_all(MYSQLI_ASSOC));
swoole_event_exit();
});

首先连接mysql,连接成功后执行SQL语句,要在第二个参数指定MYSQLI_ASYNC。表示此查询为异步。query函数会立即返回,这时候并没有得到mysqli_result。

然后调用swoole_get_mysqli_sock函数取到mysql连接的socket,并调用swoole_event_add将它加入到swoole的epoll事件循环中。当数据库返回结果是会回调我们刚才制定的函数。

这时候再调用mysqli_reap_async_query得到result,调用fetch_all函数得到数据。

这个过程是完全异步非阻塞的,不存在任何浪费CPU的代码。这个代码还可以用在服务器端程序上,具体代码实现后续会再写一篇文章详细介绍。

 

分类: PHP, Swoole扩展 标签:

PHP服务器端守护进程怎么实现MySQL连接池

2013年8月22日 没有评论

在启动时创建 10个MySQL连接和10个锁。然后fork出100个worker进程。worker进程随机从pool中取一个连接,先进行加锁操作,然后执行mysql_query。返回后释放锁,让其他进程可以使用此连接。

为什么要加锁呢,原因是mysql_query是一个收发包的过程,如果并发执行mysql_query,就会同时有多个进程发出去请求。这时候没办法区分到底哪个回包是对应的。mysql_query函数会直接抛出致命错误,并切断连接。

另外最好使用mysql_pconnect。避免子进程结束时执行mysql_close破坏此连接,其他子进程也没法用这个连接了。

这里的随机取也可以继续改进一下,使用共享内存来做一个忙闲区分。当执行mysql_query前将此连接设为忙。其他worker取连接时,判断为忙的话跳过此连接。

用下面的代码演示一下。


<?php
for($i = 0; $i < 1; $i ++)
{
	$db = new mysqli("localhost", "root", "root", "test");
	$lock = fopen("/tmp/mysql_lock.txt", 'w+');
	if ($db->connect_errno) 
	{
		printf("Connect failed: %s\n", $db->connect_error);
		exit();
	}
}

for($i = 0; $i < 5; $i++)
{
	if(pcntl_fork() > 0)
	{
		continue;
	}
	if (flock($lock, LOCK_EX))
	{
		$result = $db->query('show tables');
	}
	flock($lock, LOCK_UN); 
	
	if (empty($result)) 
	{
    		print('Invalid query: ' . $db->error."\n");
	}
	else
	{
		while ($row = $result->fetch_assoc()) 
		{
			echo "#".posix_getpid(),"\t";
			var_dump($row);
			echo "\n";
		}
		$result->close();
	}
	sleep(1000);
	exit;
}
sleep(1000);
分类: PHP 标签:

PHP中phar包的使用

2013年7月17日 没有评论

PHP5.3之后支持了类似Java的jar包,名为phar。用来将多个PHP文件打包为一个文件。

首先需要修改php.ini配置将phar的readonly关闭,默认是不能写phar包的,include是默认开启的。

phar.readonly => On

创建一个phar压缩包

<?php
$phar = new Phar('swoole.phar');
$phar->buildFromDirectory(__DIR__.'/../', '/\.php$/');
$phar->compressFiles(Phar::GZ);
$phar->stopBuffering();
$phar->setStub($phar->createDefaultStub('lib_config.php'));

new Phar的参数是压缩包的名称。buildFromDirectory指定压缩的目录,第二个参数可通过正则来制定压缩文件的扩展名。

Phar::GZ表示使用gzip来压缩此文件。也支持bz2压缩。参数修改为 PHAR::BZ2即可。

setSub用来设置启动加载的文件。默认会自动加载并执行 lib_config.php。

执行此代码后,即生成一个swoole.phar文件。

使用phar压缩包

<?php
include 'swoole.phar';
include 'swoole.phar/code/page.php';

使用phar可以很方便的打包你的代码,集成部署到线上机器。

分类: PHP 标签:

PHP进程间通信IPC-消息队列的使用

2013年4月25日 1 条评论

Linux IPC消息队列是一个全内存设计,内核保证读写顺序和数据同步,并且性能非常强悍的先进现先出数据结构。它的特性如下:

  1. 每秒可读写超过50万次(4核/4G内存的机器)
  2. 支持消息多类型,抢队列时可根据需要获取特定的消息类型
  3. 每个消息长度最大支持65535个字节
  4. 队列长度受内存大小限制,最大不超过机器内存的50%,可以修改内核参数来调整

消息队列可以用在很多场景下,如异步任务处理,抢占式的数据分发,顺序缓存区等。使用方法也非常简单,Linux提供了4个库函数,msgget,msgsnd,msgrcv,msgctl,分别用于创建/获取消息队列、发送数据、接收数据、设置/获取消息队列。PHP内核包含了这个扩展,需要在./configure时加入–enable-sysvmsg来开启。具体可参考PHP手册。Swoole框架内提供了一个sysvmsg的封装,代码在http://code.google.com/p/swoole/source/browse/trunk/libs/class/swoole/queue/SysvQueue.class.php.

下面写一个简单的例子,采用单Proxy主进程+多Worker进程的模式,功能是做异步任务的处理。本代码没有提供进程管理、信号处理、队列过载保护,如果要用在生产环境,请自行实现。


<?php
$msg_key = 0x3000111; //系统消息队列的key
$worker_num = 2;   //启动的Worker进程数量
$worker_pid = array();

$queue = msg_get_queue($msg_key, 0666);
if($queue === false)
{
    die("create queue fail\n");
}
for($i = 0; $i < $worker_num; $i++)
{
    $pid = pcntl_fork();
    //主进程
    if($pid > 0)
    {
        $worker_pid[] = $pid;
        echo "create worker $i.pid = $pid\n";
        continue;
    }
    //子进程
    elseif($pid == 0)
    {
        proc_worker($i);
        exit;
    }
    else
    {
        echo "fork fail\n";
    }
}

proc_main();

function proc_main()
{
    global $queue;
    $bind = "udp://0.0.0.0:9999";
    //建立一个UDP服务器接收请求
    $socket = stream_socket_server($bind, $errno, $errstr, STREAM_SERVER_BIND);
    if (!$socket)
    {
        die("$errstr ($errno)");
    }
    stream_set_blocking($socket, 1);
    echo "stream_socket_server bind=$bind\n";
    while (1)
    {
        $errCode = 0;
        $peer = '';
        $pkt = stream_socket_recvfrom($socket, 8192, 0, $peer);

        if($pkt == false)
        {
            echo "udp error\n";
        }
        $ret = msg_send($queue, 1, $pkt, false, true, $errCode); //如果队列满了,这里会阻塞
        if($ret)
        {
            stream_socket_sendto($socket, "OK\n", 0, $peer);
        }
        else
        {
            stream_socket_sendto($socket, "ER\n", 0, $peer);
        }
    }
}

function proc_worker($id)
{
    global $queue;
    $msg_type = 0;
    $msg_pkt = '';
    $errCode = 0;
    while(1)
    {
        $ret = msg_receive($queue, 0, $msg_type, 8192, $msg_pkt, false, $errCode);
        if($ret)
        {
            //TODO 这里处理接收到的数据
            //.... Code ....//
            echo "[Worker $id] ".$msg_pkt;
        }
        else
        {
            echo "ERROR: queue errno={$errCode}\n";
        }
    }
}

运行结果:

create worker 0.pid = 18885
create worker 1.pid = 18886
stream_socket_server bind=udp://0.0.0.0:9999
[Worker 0] hello
[Worker 1] hello2
[Worker 0] hello1

客户端使用netcat来测试,netcat -u 127.0.0.1 9999。

使用ipcs -q来查看系统消息队列:

tianfenghan@VM_194_118_sles10_64:/data/nginx_log> ipcs -q

------ Message Queues --------
key msqid owner perms used-bytes messages
0x4000270f 0 tianfengha 666 0 0
0x03000111 32769 tianfengha 666 0 0

0×03000111就是刚才我们创建的消息队列。

分类: PHP 标签:

大型网站运营的技术经验分享

2013年3月28日 没有评论

一、日志系统

对于一个大型网站来说,代码库非常庞大,模块众多。部门协作的人数规模在百人以上,如何跟踪定位问题不像小网站那样容易。而且我们的服务器都是集群化的,动辄几千台。有一套可查询方便使用的日志系统至关重要。

对于日志的使用也有了一定的经验。我们的日志通常会非常详细的记录各种参数,环境变量,HOST等信息,在出现异常的情况下,必须要记录日志。我们使用了MySQL按时间分片的方式来记录日志,所有集群内节点均通过网络方式来写入到中心日志系统。

在管理端我们提供了很方便友好的工具,来查询定位日志,可按用户ID、类型、时间等几种索引方式查询。日志系统帮助我们定位到了很多问题。

二、PHP错误日志收集系统

PHP错误日志是所有PHP程序问题最直接的反馈渠道。通过分析PHP日志可以发现和分析出系统现有的Bug和潜在的问题。我们通过在节点部署监控工具,实时收集PHP错误日志,Fatal Error告警。解决了很多问题

三、数据统计中心

我们有一整套数据上报的系统,系统内各种接口调用、请求响应、错误返回,都会上报到数据统计中心。我们以报表的形式展现出来,可以很方便的看到每个模块,每个接口的可用性,成功率,数据规模。根据数据,我们还做了成功率告警,当接口成功率低于某个数值,比如99.99%时就会发送短信报警。还有历史数据对比报警,当发现今日数据与往期数据差距较大时,可能是系统出现了问题,会及时进行报警。

四、硬件系统报警

我们有一套工具,来监控每个服务器节点的CPU、硬盘、内存、网卡流量信息,以及其他系统关键参数的信息。并以图表方式提供展示,方便了解服务器运行情况。每次新版本,或新功能上线,都通过这些信息来感知访问量变化,以及机器的负载情况。

五、容灾工具

当一组Server中其中一台出现问题时,会及时发现,并踢掉。这个也可以做成自动容灾,不过控制不好会发生雪崩。

分类: PHP 标签: