首页
关于
Search
1
给你10个市场数据调研报告的免费下载网站!以后竞品数据就从这里找!
185 阅读
2
php接口优化 使用curl_multi_init批量请求
144 阅读
3
《从菜鸟到大师之路 ElasticSearch 篇》
107 阅读
4
2024年备考系统架构设计师
104 阅读
5
PHP 文件I/O
92 阅读
php
thinkphp
laravel
工具
开源
mysql
数据结构
总结
思维逻辑
令人感动的创富故事
读书笔记
前端
vue
js
css
书籍
开源之旅
架构
消息队列
docker
教程
代码片段
redis
服务器
nginx
linux
科普
java
c
ElasticSearch
测试
php进阶
php基础
登录
Search
标签搜索
php函数
php语法
性能优化
安全
错误和异常处理
问题
vue
Composer
Session
缓存
框架
Swoole
api
并发
异步
正则表达式
php-fpm
mysql 索引
开发规范
协程
dafenqi
累计撰写
786
篇文章
累计收到
33
条评论
首页
栏目
php
thinkphp
laravel
工具
开源
mysql
数据结构
总结
思维逻辑
令人感动的创富故事
读书笔记
前端
vue
js
css
书籍
开源之旅
架构
消息队列
docker
教程
代码片段
副业
redis
服务器
nginx
linux
科普
java
c
ElasticSearch
测试
php进阶
php基础
页面
关于
搜索到
786
篇与
的结果
2023-08-09
PHP编程中10个最常见的错误
PHP编程中10个最常见的错误错误1:foreach循环后留下悬挂指针在foreach循环中,如果我们需要更改迭代的元素或是为了提高效率,运用引用是一个好办法:$arr = array(1, 2, 3, 4); foreach ($arr as &$value) {$value = $value * 2; } // $arr is now array(2, 4, 6, 8)这里有个问题很多人会迷糊。循环结束后,$value并未销毁,$value其实是数组中最后一个元素的引用,这样在后续对$value的使用中,如果不知道这一点,会引发一些莫名奇妙的错误:)看看下面这段代码:$array = [1, 2, 3]; echo implode(',', $array), "\n"; foreach ($array as &$value) {} // by reference echo implode(',', $array), "\n"; foreach ($array as $value) {} // by value (i.e., copy) echo implode(',', $array), "\n";上面代码的运行结果如下:1,2,3 1,2,3 1,2,2你猜对了吗?为什么是这个结果呢?我们来分析下。第一个循环过后,$value是数组中最后一个元素的引用。第二个循环开始:第一步:复制$arr[0]到$value(注意此时$value是$arr[2]的引用),这时数组变成[1,2,1]第二步:复制$arr[1]到$value,这时数组变成[1,2,2]第三步:复制$arr[2]到$value,这时数组变成[1,2,2]综上,最终结果就是1,2,2避免这种错误最好的办法就是在循环后立即用unset函数销毁变量:$arr = array(1, 2, 3, 4); foreach ($arr as &$value) {$value = $value * 2; } unset($value); // $value no longer references $arr[3]错误2:对isset()函数行为的错误理解对于isset()函数,变量不存在时会返回false,变量值为null时也会返回false。这种行为很容易把人弄迷糊。。。看下面的代码:$data = fetchRecordFromStorage($storage, $identifier); if (!isset($data['keyShouldBeSet']) {// do something here if 'keyShouldBeSet' is not set }写这段代码的人本意可能是如果$data[‘keyShouldBeSet’]未设置,则执行对应逻辑。但问题在于即使$data[‘keyShouldBeSet’]已设置,但设置的值为null,还是会执行对应的逻辑,这就不符合代码的本意了。下面是另外一个例子:if ($_POST['active']) {$postData = extractSomething($_POST); } // ... if (!isset($postData)) {echo 'post not active'; }上 面的代码假设$_POST[‘active’]为真,那么$postData应该被设置,因此isset($postData)会返回true。反之,上 面代码假设isset($postData)返回false的唯一途径就是$_POST[‘active’]也返回false。真是这样吗?当然不是!即使$_POST[‘active’]返回true,$postData也有可能被设置为null,这时isset($postData)就会返回false。这就不符合代码的本意了。如果上面代码的本意仅是检测$_POST[‘active’]是否为真,下面这样实现会更好:if ($_POST['active']) {$postData = extractSomething($_POST); } // ... if ($_POST['active']) {echo 'post not active'; }判断一个变量是否真正被设置(区分未设置和设置值为null),array_key_exists()函数或许更好。重构上面的第一个例子,如下:$data = fetchRecordFromStorage($storage, $identifier); if (! array_key_exists('keyShouldBeSet', $data)) {// do this if 'keyShouldBeSet' isn't set }另外,结合get_defined_vars()函数,我们可以更加可靠的检测变量在当前作用域内是否被设置:if (array_key_exists('varShouldBeSet', get_defined_vars())) {// variable $varShouldBeSet exists in current scope }错误3:混淆返回值和返回引用考虑下面的代码:class Config {private $values = []; public function getValues() { return $this->values; } } $config = new Config(); $config->getValues()['test'] = 'test'; echo $config->getValues()['test'];运行上面的代码,将会输出下面的内容:PHP Notice: Undefined index: test in /path/to/my/script.php on line 21问题出在哪呢?问题就在于上面的代码混淆了返回值和返回引用。在PHP中,除非你显示的指定返回引用,否则对于数组PHP是值返回,也就是数组的拷贝。因此上面代码对返回数组赋值,实际是对拷贝数组进行赋值,非原数组赋值。// getValues() returns a COPY of the $values array, so this adds a 'test' element // to a COPY of the $values array, but not to the $values array itself. $config->getValues()['test'] = 'test'; // getValues() again returns ANOTHER COPY of the $values array, and THIS copy doesn't // contain a 'test' element (which is why we get the "undefined index" message). echo $config->getValues()['test'];下面是一种可能的解决办法,输出拷贝的数组,而不是原数组:$vals = $config->getValues(); $vals['test'] = 'test'; echo $vals['test'];如果你就是想要改变原数组,也就是要反回数组引用,那应该如何处理呢?办法就是显示指定返回引用即可:class Config {private $values = []; // return a REFERENCE to the actual $values array public function &getValues() { return $this->values; } } $config = new Config(); $config->getValues()['test'] = 'test'; echo $config->getValues()['test'];经过改造后,上面代码将会像你期望那样会输出test。我们再来看一个例子会让你更迷糊的例子:class Config {private $values; // using ArrayObject rather than array public function __construct() { $this->values = new ArrayObject(); } public function getValues() { return $this->values; } } $config = new Config(); $config->getValues()['test'] = 'test'; echo $config->getValues()['test'];如果你想的是会和上面一样输出“ Undefined index”错误,那你就错了。代码会正常输出“test”。原因在于PHP对于对象默认就是按引用返回的,而不是按值返回。综上所述,我们在使用函数返回值时,要弄清楚是值返回还是引用返回。PHP中对于对象,默认是引用返回,数组和内置基本类型默认均按值返回。这个要与其它语言区别开来(很多语言对于数组是引用传递)。像其它语言,比如java或C#,利用getter或setter来访问或设置类属性是一种更好的方案,当然PHP默认不支持,需要自己实现:class Config {private $values = []; public function setValue($key, $value) { $this->values[$key] = $value; } public function getValue($key) { return $this->values[$key]; } } $config = new Config(); $config->setValue('testKey', 'testValue'); echo $config->getValue('testKey'); // echos 'testValue'上面的代码给调用者可以访问或设置数组中的任意值而不用给与数组public访问权限。感觉怎么样:)错误4:在循环中执行sql查询在PHP编程中发现类似下面的代码并不少见:$models = []; foreach ($inputValues as $inputValue) {$models[] = $valueRepository->findByValue($inputValue); }当然上面的代码是没有什么错误的。问题在于我们在迭代过程中$valueRepository->findByValue()可能每次都执行了sql查询:$result = $connection->query("SELECT x,y FROM values WHERE value=" . $inputValue);如果迭代了10000次,那么你就分别执行了10000次sql查询。如果这样的脚本在多线程程序中被调用,那很可能你的系统就挂了。。。在编写代码过程中,你应该要清楚什么时候应该执行sql查询,尽可能一次sql查询取出所有数据。有一种业务场景,你很可能会犯上述错误。假设一个表单提交了一系列值(假设为IDs),然后为了取出所有ID对应的数据,代码将遍历IDs,分别对每个ID执行sql查询,代码如下所示:$data = []; foreach ($ids as $id) {$result = $connection->query("SELECT `x`, `y` FROM `values` WHERE `id` = " . $id); $data[] = $result->fetch_row(); }但同样的目的可以在一个sql中更加高效的完成,代码如下:$data = []; if (count($ids)) {$result = $connection->query("SELECT `x`, `y` FROM `values` WHERE `id` IN (" . implode(',', $ids)); while ($row = $result->fetch_row()) { $data[] = $row; } }错误5:内存使用低效和错觉一次sql查询获取多条记录比每次查询获取一条记录效率肯定要高,但如果你使用的是php中的mysql扩展,那么一次获取多条记录就很可能会导致内存溢出。我们可以写代码来实验下(测试环境: 512MB RAM、MySQL、php-cli):// connect to mysql $connection = new mysqli('localhost', 'username', 'password', 'database'); // create table of 400 columns $query = 'CREATE TABLE test(id INT NOT NULL PRIMARY KEY AUTO_INCREMENT'; for ($col = 0; $col < 400; $col++) {$query .= ", `col$col` CHAR(10) NOT NULL"; } $query .= ');'; $connection->query($query); // write 2 million rows for ($row = 0; $row < 2000000; $row++) {$query = "INSERT INTO `test` VALUES ($row"; for ($col = 0; $col < 400; $col++) { $query .= ', ' . mt_rand(1000000000, 9999999999); } $query .= ')'; $connection->query($query); }现在来看看资源消耗:// connect to mysql $connection = new mysqli('localhost', 'username', 'password', 'database'); echo "Before: " . memory_get_peak_usage() . "\n"; $res = $connection->query('SELECT x,y FROM test LIMIT 1'); echo "Limit 1: " . memory_get_peak_usage() . "\n"; $res = $connection->query('SELECT x,y FROM test LIMIT 10000'); echo "Limit 10000: " . memory_get_peak_usage() . "\n";输出结果如下:Before: 224704 Limit 1: 224704 Limit 10000: 224704根据内存使用量来看,貌似一切正常。为了更加确定,试着一次获取100000条记录,结果程序得到如下输出:PHP Warning: mysqli::query(): (HY000/2013): Lost connection to MySQL server during query in /root/test.php on line 11这是怎么回事呢?问 题出在php的mysql模块的工作方式,mysql模块实际上就是libmysqlclient的一个代理。在查询获取多条记录的同时,这些记录会直接 保存在内存中。由于这块内存不属于php的内存模块所管理,所以我们调用memory_get_peak_usage()函数所获得的值并非真实使用内存 值,于是便出现了上面的问题。我们可以使用mysqlnd来代替mysql,mysqlnd编译为php自身扩展,其内存使用由php内存管理模块所控制。如果我们用mysqlnd来实现上面的代码,则会更加真实的反应内存使用情况:Before: 232048 Limit 1: 324952 Limit 10000: 32572912更加糟糕的是,根据php的官方文档,mysql扩展存储查询数据使用的内存是mysqlnd的两倍,因此原来的代码使用的内存是上面显示的两倍左右。为了避免此类问题,可以考虑分几次完成查询,减小单次查询数据量:$totalNumberToFetch = 10000; $portionSize = 100; for ($i = 0; $i <= ceil($totalNumberToFetch / $portionSize); $i++) {$limitFrom = $portionSize * $i; $res = $connection->query( "SELECT `x`,`y` FROM `test` LIMIT $limitFrom, $portionSize"); }联系上面提到的错误4可以看出,在实际的编码过程中,要做到一种平衡,才能既满足功能要求,又能保证性能。错误6:忽略Unicode/UTF-8问题php编程中,在处理非ascii字符时,会遇到一些问题,要很小心的去对待,要不然就会错误遍地。举个简单的例子,strlen($name),如果$name包含非ascii字符,那结果就有些出乎意料。在此给出一些建议,尽量避免此类问题:如果你对unicode和utf-8不是很了解,那么你至少应该了解一些基础。推荐阅读这篇文章。最好使用mb_*函数来处理字符串,避免使用老的字符串处理函数。这里要确保PHP的“multibyte”扩展已开启。数据库和表最好使用unicode编码。知道jason_code()函数会转换非ascii字符,但serialize()函数不会。php代码源文件最好使用不含bom的utf-8格式。在此推荐一篇文章,更详细的介绍了此类问题: UTF-8 Primer for PHP and MySQL错误7:假定$_POST总是包含POST数据PHP中的$_POST并非总是包含表单POST提交过来的数据。假设我们通过 jQuery.ajax() 方法向服务器发送了POST请求:// js $.ajax({url: 'http://my.site/some/path', method: 'post', data: JSON.stringify({a: 'a', b: 'b'}), contentType: 'application/json' });注意代码中的 contentType: ‘application/json’ ,我们是以json数据格式来发送的数据。在服务端,我们仅输出$_POST数组:// php var_dump($_POST);你会很惊奇的发现,结果是下面所示:array(0) { }为什么是这样的结果呢?我们的json数据 {a: ‘a’, b: ‘b’} 哪去了呢?答案就是PHP仅仅解析Content-Type为 application/x-www-form-urlencoded 或 multipart/form-data的Http请求。之所以这样是因为历史原因,PHP最初实现$_POST时,最流行的就是上面两种类型。因此虽说现在有些类型(比如application/json)很流行,但PHP中还是没有去实现自动处理。因为$_POST是全局变量,所以更改$_POST会全局有效。因此对于Content-Type为 application/json的请求,我们需要手工去解析json数据,然后修改$_POST变量。// php $_POST = json_decode(file_get_contents('php://input'), true);此时,我们再去输出$_POST变量,则会得到我们期望的输出:array(2) { ["a"]=> string(1) "a" ["b"]=> string(1) "b" }错误8:认为PHP支持字符数据类型看看下面的代码,猜测下会输出什么:for ($c = 'a'; $c <= 'z'; $c++) {echo $c . "\n"; }如果你的回答是输出’a’到’z’,那么你会惊奇的发现你的回答是错误的。不错,上面的代码的确会输出’a’到’z’,但除此之外,还会输出’aa’到’yz’。我们来分析下为什么会是这样的结果。在PHP中不存在char数据类型,只有string类型。明白这点,那么对’z’进行递增操作,结果则为’aa’。对于字符串比较大小,学过C的应该都知道,’aa’是小于’z’的。这也就解释了为何会有上面的输出结果。如果我们想输出’a’到’z’,下面的实现是一种不错的办法:for ($i = ord('a'); $i <= ord('z'); $i++) {echo chr($i) . "\n"; }或者这样也是OK的:$letters = range('a', 'z'); for ($i = 0; $i < count($letters); $i++) {echo $letters[$i] . "\n"; }错误9:忽略编码标准虽说忽略编码标准不会导致错误或是bug,但遵循一定的编码标准还是很重要的。没有统一的编码标准会使你的项目出现很多问题。最明显的就是你的项目代码不具有一致性。更坏的地方在于,你的代码将更加难以调试、扩展和维护。这也就意味着你的团队效率会降低,包括做一些很多无意义的劳动。对于PHP开发者来说,是比较幸运的。因为有PHP编码标准推荐(PSR),由下面5个部分组成:PSR-0:自动加载标准PSR-1:基本编码标准PSR-2:编码风格指南PSR-3:日志接口标准PSR-4:自动加载PSR最初由PHP社区的几个大的团体所创建并遵循。Zend, Drupal, Symfony, Joomla及其它的平台都为此标准做过贡献并遵循这个标准。即使是PEAR,早些年也想让自己成为一个标准,但现在也加入了PSR阵营。在 某些情况下,使用什么编码标准是无关紧要的,只要你使用一种编码风格并一直坚持使用即可。但是遵循PSR标准不失为一个好办法,除非你有什么特殊的原因要 自己弄一套。现在越来越多的项目都开始使用PSR,大部分的PHP开发者也在使用PSR,因此使用PSR会让新加入你团队的成员更快的熟悉项目,写代码时 也会更加舒适。错误10:错误使用empty()函数一些PHP开发人员喜欢用empty()函数去对变量或表达式做布尔判断,但在某些情况下会让人很困惑。首先我们来看看PHP中的数组Array和数组对象ArrayObject。看上去好像没什么区别,都是一样的。真的这样吗?// PHP 5.0 or later: $array = []; var_dump(empty($array)); // outputs bool(true) $array = new ArrayObject(); var_dump(empty($array)); // outputs bool(false) // why don't these both produce the same output?让事情变得更复杂些,看看下面的代码:// Prior to PHP 5.0: $array = []; var_dump(empty($array)); // outputs bool(false) $array = new ArrayObject(); var_dump(empty($array)); // outputs bool(false)很不幸的是,上面这种方法很受欢迎。例如,在Zend Framework 2中,Zend\Db\TableGateway 在 TableGateway::select() 结果集上调用 current() 方法返回数据集时就是这么干的。开发人员很容易就会踩到这个坑。为了避免这些问题,检查一个数组是否为空最后的办法是用 count() 函数:// Note that this work in ALL versions of PHP (both pre and post 5.0): $array = []; var_dump(count($array)); // outputs int(0) $array = new ArrayObject(); var_dump(count($array)); // outputs int(0)在这顺便提一下,因为PHP中会将数值0认为是布尔值false,因此 count() 函数可以直接用在 if 条件语句的条件判断中来判断数组是否为空。另外,count() 函数对于数组来说复杂度为O(1),因此用 count() 函数是一个明智的选择。再来看一个用 empty() 函数很危险的例子。当在魔术方法 __get() 中结合使用 empty() 函数时,也是很危险的。我们来定义两个类,每个类都有一个 test 属性。首先我们定义 Regular 类,有一个 test 属性:class Regular {public $test = 'value'; }然后我们定义 Magic 类,并用 __get() 魔术方法来访问它的 test 属性:class Magic {private $values = ['test' => 'value']; public function __get($key) { if (isset($this->values[$key])) { return $this->values[$key]; } } }好了。我们现在来看看访问各个类的 test 属性会发生什么:$regular = new Regular(); var_dump($regular->test); // outputs string(4) "value" $magic = new Magic(); var_dump($magic->test); // outputs string(4) "value"到目前为止,都还是正常的,没有让我们感到迷糊。但在 test 属性上使用 empty() 函数会怎么样呢?var_dump(empty($regular->test)); // outputs bool(false) var_dump(empty($magic->test)); // outputs bool(true)结果是不是很意外?很不幸的是,如果一个类使用魔法 __get() 函数来访问类属性的值,没有简单的方法来检查属性值是否为空或是不存在。在类作用域外,你只能检查是否返回 null 值,但这并不一定意味着没有设置相应的键,因为键值可以被设置为 null 。相比之下,如果我们访问 Regular 类的一个不存在的属性,则会得到一个类似下面的Notice消息:Notice: Undefined property: Regular::$nonExistantTest in /path/to/test.php on line 10 Call Stack:0.0012 234704 1. {main}() /path/to/test.php:0因此,对于 empty() 函数,我们要小心的使用,要不然的话就会结果出乎意料,甚至潜在的误导你。
2023年08月09日
10 阅读
0 评论
0 点赞
2023-08-09
php-fpm 启动参数及重要配置详解
php-fpm 启动参数及重要配置详解约定几个目录/usr/local/php/sbin/php-fpm/usr/local/php/etc/php-fpm.conf/usr/local/php/etc/php.ini一,php-fpm的启动参数#测试php-fpm配置 /usr/local/php/sbin/php-fpm -t /usr/local/php/sbin/php-fpm -c /usr/local/php/etc/php.ini -y /usr/local/php/etc/php-fpm.conf -t #启动php-fpm /usr/local/php/sbin/php-fpm /usr/local/php/sbin/php-fpm -c /usr/local/php/etc/php.ini -y /usr/local/php/etc/php-fpm.conf #关闭php-fpm kill -INT `cat /usr/local/php/var/run/php-fpm.pid` #重启php-fpm kill -USR2 `cat /usr/local/php/var/run/php-fpm.pid`二,php-fpm.conf重要参数详解pid = run/php-fpm.pid #pid设置,默认在安装目录中的var/run/php-fpm.pid,建议开启 error_log = log/php-fpm.log #错误日志,默认在安装目录中的var/log/php-fpm.log log_level = notice #错误级别. 可用级别为: alert(必须立即处理), error(错误情况), warning(警告情况), notice(一般重要信息), debug(调试信息). 默认: notice. emergency_restart_threshold = 60 emergency_restart_interval = 60s #表示在emergency_restart_interval所设值内出现SIGSEGV或者SIGBUS错误的php-cgi进程数如果超过 emergency_restart_threshold个,php-fpm就会优雅重启。这两个选项一般保持默认值。 process_control_timeout = 0 #设置子进程接受主进程复用信号的超时时间. 可用单位: s(秒), m(分), h(小时), 或者 d(天) 默认单位: s(秒). 默认值: 0. daemonize = yes #后台执行fpm,默认值为yes,如果为了调试可以改为no。在FPM中,可以使用不同的设置来运行多个进程池。 这些设置可以针对每个进程池单独设置。 listen = 127.0.0.1:9000 #fpm监听端口,即nginx中php处理的地址,一般默认值即可。可用格式为: 'ip:port', 'port', '/path/to/unix/socket'. 每个进程池都需要设置. listen.backlog = -1 #backlog数,-1表示无限制,由操作系统决定,此行注释掉就行。backlog含义参考:http://www.3gyou.cc/?p=41 listen.allowed_clients = 127.0.0.1 #允许访问FastCGI进程的IP,设置any为不限制IP,如果要设置其他主机的nginx也能访问这台FPM进程,listen处要设置成本地可被访问的IP。默认值是any。每个地址是用逗号分隔. 如果没有设置或者为空,则允许任何服务器请求连接 listen.owner = www listen.group = www listen.mode = 0666 #unix socket设置选项,如果使用tcp方式访问,这里注释即可。 user = www group = www #启动进程的帐户和组 pm = dynamic #对于专用服务器,pm可以设置为static。 #如何控制子进程,选项有static和dynamic。如果选择static,则由pm.max_children指定固定的子进程数。如果选择dynamic,则由下开参数决定: pm.max_children #,子进程最大数 pm.start_servers #,启动时的进程数 pm.min_spare_servers #,保证空闲进程数最小值,如果空闲进程小于此值,则创建新的子进程 pm.max_spare_servers #,保证空闲进程数最大值,如果空闲进程大于此值,此进行清理 pm.max_requests = 1000 #设置每个子进程重生之前服务的请求数. 对于可能存在内存泄漏的第三方模块来说是非常有用的. 如果设置为 '0' 则一直接受请求. 等同于 PHP_FCGI_MAX_REQUESTS 环境变量. 默认值: 0. pm.status_path = /status #FPM状态页面的网址. 如果没有设置, 则无法访问状态页面. 默认值: none. munin监控会使用到 ping.path = /ping #FPM监控页面的ping网址. 如果没有设置, 则无法访问ping页面. 该页面用于外部检测FPM是否存活并且可以响应请求. 请注意必须以斜线开头 (/)。 ping.response = pong #用于定义ping请求的返回相应. 返回为 HTTP 200 的 text/plain 格式文本. 默认值: pong. request_terminate_timeout = 0 #设置单个请求的超时中止时间. 该选项可能会对php.ini设置中的'max_execution_time'因为某些特殊原因没有中止运行的脚本有用. 设置为 '0' 表示 'Off'.当经常出现502错误时可以尝试更改此选项。 request_slowlog_timeout = 10s #当一个请求该设置的超时时间后,就会将对应的PHP调用堆栈信息完整写入到慢日志中. 设置为 '0' 表示 'Off' slowlog = log/$pool.log.slow #慢请求的记录日志,配合request_slowlog_timeout使用 rlimit_files = 1024 #设置文件打开描述符的rlimit限制. 默认值: 系统定义值默认可打开句柄是1024,可使用 ulimit -n查看,ulimit -n 2048修改。 rlimit_core = 0 #设置核心rlimit最大限制值. 可用值: 'unlimited' 、0或者正整数. 默认值: 系统定义值. chroot = #启动时的Chroot目录. 所定义的目录需要是绝对路径. 如果没有设置, 则chroot不被使用. chdir = #设置启动目录,启动时会自动Chdir到该目录. 所定义的目录需要是绝对路径. 默认值: 当前目录,或者/目录(chroot时) catch_workers_output = yes #重定向运行过程中的stdout和stderr到主要的错误日志文件中. 如果没有设置, stdout 和 stderr 将会根据FastCGI的规则被重定向到 /dev/null . 默认值: 空.三,常见错误及解决办法整理1,request_terminate_timeout引起的资源问题request_terminate_timeout的值如果设置为0或者过长的时间,可能会引起file_get_contents的资源问题。如果file_get_contents请求的远程资源如果反应过慢,file_get_contents就会一直卡在那里不会超时。我们知道php.ini 里面max_execution_time 可以设置 PHP 脚本的最大执行时间,但是,在 php-cgi(php-fpm) 中,该参数不会起效。真正能够控制 PHP 脚本最大执行时间的是 php-fpm.conf 配置文件中的request_terminate_timeout参数。request_terminate_timeout默认值为 0 秒,也就是说,PHP 脚本会一直执行下去。这样,当所有的 php-cgi 进程都卡在 file_get_contents() 函数时,这台 Nginx+PHP 的 WebServer 已经无法再处理新的 PHP 请求了,Nginx 将给用户返回“502 Bad Gateway”。修改该参数,设置一个 PHP 脚本最大执行时间是必要的,但是,治标不治本。例如改成 30s,如果发生 file_get_contents() 获取网页内容较慢的情况,这就意味着 150 个 php-cgi 进程,每秒钟只能处理 5 个请求,WebServer 同样很难避免”502 Bad Gateway”。解决办法是request_terminate_timeout设置为10s或者一个合理的值,或者给file_get_contents加一个超时参数。$ctx = stream_context_create(array( 'http' => array( 'timeout' => 10 //设置一个超时时间,单位为秒 ) )); file_get_contents($str, 0, $ctx);2,max_requests参数配置不当,可能会引起间歇性502错误:pm.max_requests = 1000设置每个子进程重生之前服务的请求数. 对于可能存在内存泄漏的第三方模块来说是非常有用的. 如果设置为 ’0′ 则一直接受请求. 等同于 PHP_FCGI_MAX_REQUESTS 环境变量. 默认值: 0.这段配置的意思是,当一个 PHP-CGI 进程处理的请求数累积到 500 个后,自动重启该进程。但是为什么要重启进程呢?一般在项目中,我们多多少少都会用到一些 PHP 的第三方库,这些第三方库经常存在内存泄漏问题,如果不定期重启 PHP-CGI 进程,势必造成内存使用量不断增长。因此 PHP-FPM 作为 PHP-CGI 的管理器,提供了这么一项监控功能,对请求达到指定次数的 PHP-CGI 进程进行重启,保证内存使用量不增长。正是因为这个机制,在高并发的站点中,经常导致 502 错误,我猜测原因是 PHP-FPM 对从 NGINX 过来的请求队列没处理好。不过我目前用的还是 PHP 5.3.2,不知道在 PHP 5.3.3 中是否还存在这个问题。目前我们的解决方法是,把这个值尽量设置大些,尽可能减少 PHP-CGI 重新 SPAWN 的次数,同时也能提高总体性能。在我们自己实际的生产环境中发现,内存泄漏并不明显,因此我们将这个值设置得非常大(204800)。大家要根据自己的实际情况设置这个值,不能盲目地加大。话说回来,这套机制目的只为保证 PHP-CGI 不过分地占用内存,为何不通过检测内存的方式来处理呢?我非常认同高春辉所说的,通过设置进程的峰值内在占用量来重启 PHP-CGI 进程,会是更好的一个解决方案。3,php-fpm的慢日志,debug及异常排查神器:request_slowlog_timeout设置一个超时的参数,slowlog设置慢日志的存放位置tail -f /var/log/www.slow.log上面的命令即可看到执行过慢的php过程。大家可以看到经常出现的网络读取超过、Mysql查询过慢的问题,根据提示信息再排查问题就有很明确的方向了。
2023年08月09日
10 阅读
0 评论
0 点赞
2023-08-09
什么是 PHP Lambda(匿名函数) 和 Closures(闭包)
什么是 PHP Lambda(匿名函数) 和 Closures(闭包)?目录什么是 Lambda(匿名函数)?什么是Closures(闭包)?现实生活中的用法结论Lambda 和 Closures 是 PHP 在 5.3 版发布后相对较新的新增功能。两者都提供了一些新功能,并能够重构旧代码,使其更简洁、更直观。但是,我认为许多开发人员不知道 Lambda 和闭包,或者对它们的实际作用感到困惑。在这篇文章中,我将解释 Lambda 和闭包,给你一些示例代码来展示它们的用法,并给你一个它们在现代 PHP 中的实际示例,向你展示它们在现代 PHP 中的流行程度。什么是 Lambda?Lambda 是一个匿名函数,可以分配给变量或作为参数传递给另一个函数。如果你熟悉其他编程语言,如Javascript或Ruby,你会非常熟悉匿名函数。匿名函数匿名函数只是一个没有名称的函数。例如,要创建一个常规函数,您可以编写如下内容:// Regular functionfunction greeting(){return "Hello world";}然后,您可以像这样简单地调用此函数:echo greeting();// Returns "Hello world"匿名函数没有名称,因此您可以像这样定义它:// Anonymous functionfunction (){return "Hello world";}使用Lambdas由于函数没有名称,因此不能像常规函数一样调用它。相反,您必须将其分配给变量或将其作为参数传递给另一个函数。// Anonymous function// assigned to variable$greeting = function () {return "Hello world";}// Call functionecho $greeting();// Returns "Hello world"为了使用匿名函数,我们将其分配给一个变量,然后将该变量作为函数调用。您也可以将该函数传递给另一个函数,如下所示:// Pass Lambda to functionfunction shout($message){echo $message();}// Call functionshout(function () {return "Hello world";});为什么要使用 Lambda?Lambda 很有用,因为它们是一次性可以使用的函数。通常,您需要一个函数来完成工作,但是将其置于全局范围内甚至将其作为代码的一部分提供是没有意义的。与其使用一次函数然后闲置,不如改用 Lambda。当然,您已经在 PHP 中使用 create_function 函数已有一段时间了。这基本上做同样的工作。// Use create_function$greeting = create_function('echo "Hello World!";');// Call function$greeting();什么是闭包?闭包本质上与 Lambda 相同,除了它可以访问创建范围之外的变量。例如:// Create a user$user = "Philip";// Create a Closure$greeting = function () use ($user) {echo "Hello $user";};// Greet the user$greeting(); // Returns "Hello Philip"正如你在上面看到的,闭包能够访问$user变量。因为它是在闭包函数定义的 use 子句中声明的。如果要更改闭包中的$user变量,则不会影响原始变量。要更新原始变量,我们可以附加一个 & 符号。变量前的 & 符号表示这是一个引用,因此原始变量也会更新。例如:// Set counter$i = 0;// Increase counter within the scope// of the function$closure = function () use ($i) {$i++;};// Run the function$closure();// The global count hasn't changedecho $i; // Returns 0// Reset count$i = 0;// Increase counter within the scope// of the function but pass it as a reference$closure = function () use (&$i) {$i++;};// Run the function$closure();// The global count has increasedecho $i; // Returns 1当使用接受回调函数(如array_map、array_filter、array_reduce或array_walk)的 PHP 函数时,闭包也很有用。array_walk函数获取一个数组并通过回调函数运行它。// An array of names$users = ["John", "Jane", "Sally", "Philip"];// Pass the array to array_walkarray_walk($users, function ($name) {echo "Hello $name<br>";});// Returns// -> Hello John// -> Hello Jane// -> ..同样,您可以使用 use 子句访问闭包范围之外的变量:// Set a multiplier$multiplier = 3;// Create a list of numbers$numbers = [1, 2, 3, 4];// Use array_walk to iterate// through the list and multiplyarray_walk($numbers, function ($number) use ($multiplier) {echo $number * $multiplier;});在上面的例子中,创建一个函数来将两个数字相乘可能没有意义。如果你要创建函数来完成这样的工作,那么过一会儿回到代码,你可能会想为什么你费心创建一个全局可访问的函数只使用一次?通过使用 Closure 作为回调,我们可以使用该函数一次,然后忘记它。现实生活中的用法因此,我们已经确定 Lambda 和闭包是匿名函数,可以用作丢弃的功能位,这些功能不会污染全局命名空间,并且非常适合用作回调的一部分。使用这些类型函数的一个流行示例是在现代框架内路由请求。例如,Laravel允许您执行以下操作:Route::get("user/(:any)", function ($name) {return "Hello " . $name;});上面的代码只是匹配像 /user/philip 这样的 URL 并返回一个问候语。这是一个非常基本的例子,但它强调了如何在非常有用的情况下使用闭包。结论所以希望这是对 Lambda 和闭包的一个很好的解释。如果你是一个新手程序员,Lambdas和闭包似乎是两个非常深奥的计算机科学术语。但是,实际上它一点也不复杂。Lambda 和闭包都只是匿名函数,它们对一次性有用,或者在定义函数没有意义的情况下。Lambdas和Closures对PHP来说是相当新的,它们与其他语言的用法并不完全相同。如果你熟悉Javascript,你会看到匿名函数被大量使用。特别是,你会在jQuery中看到很多很好的例子。一旦你能够识别出模式,它就会使阅读代码变得容易得多,因为你不仅了解发生了什么,而且还理解为什么一开始就这样写它,以及开发人员试图通过她的决定实现什么。
2023年08月09日
12 阅读
0 评论
0 点赞
2023-08-09
5天学会一种 web 开发框架
5天学会一种 web 开发框架eb framework层出不穷,特别是ruby/python,各有10+个,php/java也是一大堆 根据我自己的经验写了一个to do list,按照这个清单,一条一条的学习,事半功倍,很快就能掌握 一共25条,即便很磨蹭,2小时也能搞定一条,25*2=50。只需要50小时就能掌握任意一种web框架各类web框架大同小异:现代web开发框架的6大元素,把握主线,就不会迷路建议把本文打印到一张A4纸,搞定一条打个勾web框架学习列表如何定义 url route如何组织 request handler 函数写一个最简单的request handler 函数如何从get/post请求中取出参数如何定义全局url 拦截函数如何获取/修改/存储 cookie,session数据如何修改/输出 http header 数据如何部部署app 程序服务器部署可以参考读python web 程序的9种部署方式如何配置开发环境如何配置静态文件访问如何访问数据库是否支持ORM支持orm如何维护表结构的变更如何定义/组织/初始化 数据表如何对接orm系统和现有的表结构掌握最基本的add/delete/按字段查询/count/slice/order by如何直接使用sql 访问数据库不支持orm (这样的web框架,不用也罢)如何使用模板系统如何组织/访问 模板文件的目录结构如何在模板中嵌入代码模板是否支持继承结构模板之间如何include如何自定义模板函数如何通过http get/post 获取远程数据如何parse json如何parse xml如何输出为 json如何处理状态码:404和50x如何处理文件上传可选的学习项目发送emaillog图片处理误区表单验证辅助函数,很多框架的表单验证部分实现的特别复杂,初学者完全不需要,手写代码处理就够用ORM中的hasone,manytomany,onetomany关系,概念很复杂,其实只是多写/少写一个查询字段的关系,学习成本太高,初学者完全不需要理会,直接跳过
2023年08月09日
16 阅读
0 评论
0 点赞
2023-08-09
PHP 应用性能分析 - Davey Shafik(全)
PHP 应用性能分析 - Davey Shafik(全)PHP 性能分析第一篇: Xhprof & Xhgui 介绍原文 http://news.oneapm.com/php-xhprof-xhgui/主题 XHProf 性能分析【前言】这是国外知名博主 Davey Shafik所撰写的 PHP 应用性能分析系列的第一篇,阅读第二篇可深入了解 xhgui,第三篇则关注于性能调优实践。什么是性能分析?性能分析是衡量应用程序在代码级别的相对性能。性能分析将捕捉的事件包括:CPU的使用,内存的使用,函数的调用时长和次数,以及调用图。性能分析的行为也会影响应用性能。影响的程度取决于基准测试。基准测试在外部执行,用于衡量应用真实性能。所谓真实性能,即终端用户所体验的应用表现。什么时候应该进行性能分析?在考虑是否进行性能分析时,你首先要想:应用是否存在性能问题?如果有,你要进一步考虑:这个问题有多大?如果你不这样做,将会陷入一个陷阱——过早优化,这可能会浪费你的时间。为了评断应用是否存在性能问题,你应该确定性能目标。例如,100个并发用户的响应时间小于1s。然后,你需要进行基准测试,看是否达到这个目标。一个常见的错误是,在开发环境进行基准测试。事实上,你必须在生产环境进行基准测试。(实际生产环境或模拟的生产环境,后者很容易在 SaaS 实现(见:OneAPM 性能在线分析)。用于基准测试的产品很多,包括 ab,siege 和 JMeter。我个人比较喜欢JMeter的功能集,但 ab 和 siege 更加易用。一旦你确定应用存在性能问题,就需要分析其性能,实施改进,然后再一次进行基准测试,查看问题是否解决。每一次变更之后,你都该进行基准测试查看效果。如果你做了很多变更,却发现应用性能有所下降,你就无法确定具体是哪一次变更导致了这个问题。下图是我定义的性能生命周期:性能下降的一般原因导致性能下降的一般原因中,有些相当出人意料。即便是像 PHP 这样的高级语言,代码的好坏也很少是问题的根源。在当今的硬件配置条件下,CPU 很少是性能限制的原因。常见的原因反而是:数据存储PostgreSQLMySQLOracleMSSQLMongoDBRiakCassandraMemcacheCouchDBRedis外部资源APIs文件系统网络接口外部流程糟糕的代码选择哪一种性能分析器?在 PHP 世界里,有两个截然不同的的性能分析器——主动和被动。主动 VS 被动性能分析主动分析器在开发过程中使用,由开发人员启用。主动分析器收集的信息比被动分析器多,对性能的影响更大。通常,主动分析器不能用在生产环境中。Xdebug 就是一种主动分析器。因为无法在生产环境中使用主动分析器,Facebook 推出了一个被动分析器——XHprof。XHprof 是为了在生产环境中使用而打造的。它对性能的影响最小,同时收集足够的信息用于诊断性能问题。XHprof 和 OneAPM 都是被动分析器。通常,Xdebug 收集的额外信息对于一般的性能问题分析并不必要。这意味着,被动分析器是用于不间断性能分析的更佳选择,即使是在开发环境中。Xhprof + XhguiXhprof 由 Facebook 开发的,包含一个基本的用户界面用于查看性能数据。此外,Paul Reinheimer 开发了 Xhgui 和一个增强的用户界面(UI)用于查看、比较和分析性能数据。安装安装 XHPROFXhprof 可通过 PECL 安装,步骤如下:$pecl install xhprof-beta该 pecl 命令将尝试自动更新你的 php.ini 设置。pecl 尝试更新的文件可以使用以下命令找到:$ pecl config-get php_ini它会在指定的文件(如果有的话)顶部增加新的配置行。你可能想把他们移到一个更合适的位置。一旦你编译了该扩展程序,您必须启用它。为此,您需要在 PHP INI 文件添加以下代码:[xhprof]extension=xhprof.so之后,结合 Xhgui 就能轻松地执行性能分析与检查。安装 XHGUI安装 Xhgui,必须直接从 git 获取。该项目可以在 github 上找到,地址为https://github.com/perftools/xhguiXhgui 要求:PHP 5.3+ext/mongocomposerMongoDB(若只需要收集数据,则可选可不选;若需要数据分析,则为必选)首先,克隆项目到任意位置。在基于 Debian 的 Linux 系统(例如 Ubuntu 等等),可能是 /var/www。在 Mac OS X 系统,可能是 /Library/WebServer/Documents。$cd /var/www$ git clone https://github.com/perftools/xhgui.git$ cd xhgui$ php install.php最后一个命令是运行 composer 以安装依赖并检查 xhgui 缓存目录的权限。如果失败,你可以手动运行 composer install。下一步,你可能需要创建配置文件。这一步很容易实现,可以使用在 /path/to/xhgui/config/config.default.php 下的默认配置文件。如果你在本地运行 mongodb ,没有身份验证,则可能不需要这样做。因为它将回退为默认值。而在多服务器环境中,你会需要一个所有服务器都能进行存储的远程 mongodb 服务器,并进行恰当的配置。为提高 MongoDB 的性能,你可以运行以下指令以添加索引:$ mongouse xhprofdb.results.ensureIndex( {'meta.SERVER.REQUEST_TIME': -1} )db.results.ensureIndex( {'profile.main().wt': -1} )db.results.ensureIndex( {'profile.main().mu': -1} )db.results.ensureIndex( {'profile.main().cpu': -1} )db.results.ensureIndex( {'meta.url':1} )其他配置如果你不想在生产环境中安装 mongo ,或无法让 Web 服务器访问 mongo 服务器,您可以将性能分析数据保存在磁盘中,再导入到本地MongoDB 供以后分析。为此,请在 config.php 中进行以下修改:<?php'save.handler' = 'file','save.handler.filename' => '/path/to/xhgui/xhprof-' .uniqid("", true). '.dat',?>改变文件中的 save.handler,然后取消批注 save.handler.filename ,为其赋一个恰当的值。注意:默认每天只保存一个分析文件。一旦分析数据的准备就绪,你就可以使用 xhgui 附带的脚本导入之:$ php /path/to/xhgui/external/import.php /path/to/file.dat在此之后的步骤都相同。运行 XhguiXhgui 是以 PHP 为基础的 Web 应用程序,你可以以 /path/to/xhgui/webroot为根文件,设置一个标准的虚拟主机。或者,你可以简单地使用 PHP 5.4+ cli-server 例如:$ cd /path/to/xhgui$ php -S 0:8080 -t webroot/这将使 XHGui 在所有网络接口都可通过 8080 端口进行通信。运行性能分析器运行分析器时,你需要在待分析的所有页面包含 external/header.php 脚本。为此,你可以在 PHP ini 文件设置 auto_prepend_file 。你既可以直接在公共 INI 文件进行设置,也可以限制到单一的虚拟主机。对于 Apache 服务器,添加以下代码:php_admin_value auto_prepend_file "/path/to/xhgui/external/header.php"对于 Nginx 服务器,在服务器配置中添加以下代码:fastcgi_param PHP_VALUE "auto_prepend_file=/path/to/xhgui/external/header.php";如果您使用 PHP 5.4 + cli-server(PHP - S),则 必须 通过命令行标记进行设置:$ php -S 0:8080 -dauto_prepend_file=/path/to/xhgui/external/header.php默认情况下,分析器运行时只分析(大约) 1% 的请求。这是由以下 external/header.php 代码控制的:<?php if (rand(0, 100) !== 42) { return;}?>如果你想分析每一个请求(例如,在开发阶段),你可以将这段代码注释掉。如果你想让分析 10% 的请求,你可以做如下改动:<?phpif (rand(0, 10) !== 4) {return;}?>这允许你对一小部分用户请求进行分析,而不过多影响单个用户或太多用户。如果你想在性能分析时进行手动控制,你可以这样做:<?phpif (!isset($_REQUEST['A9v3XUsnKX3aEiNsUDZzV']) && !isset($_COOKIE['A9v3XUsnKX3aEiNsUDZzV'])) {return;} else {// Remove trace of the special variable from REQUEST_URI$_SERVER['REQUEST_URI'] = str_replace(array('?A9v3XUsnKX3aEiNsUDZzV', '&A9v3XUsnKX3aEiNsUDZzV'), '', $_SERVER['REQUEST_URI']);setcookie('A9v3XUsnKX3aEiNsUDZzV', 1);}if (isset($_REQUEST['no-A9v3XUsnKX3aEiNsUDZzV'])) {setcookie('A9v3XUsnKX3aEiNsUDZzV', 0, time() - 86400);return;}?>这段代码会检查一个随机命名的 GET/POST/COOKIE 变量(在此例中为:A9v3XUsnKX3aEiNsUDZzV),同时创建一个同名的 Cookie ,用于分析该请求的整个过程,例如:表单提交后的重定向,Ajax 请求等等。此外,它允许一个名为 no-A9v3XUsnKX3aEiNsUDZzV 的 GET/POST 变量来删除 cookie ,停止分析。当然,我们欢迎大家尝试使用 OneAPM 来为您的PHP 和Java 应用做免费的性能分析。OneAPM 独有的探针能够深入到所有 PHP 和 Java 应用内部完成应用性能管理和监控,包括代码级别性能问题的可见性、性能瓶颈的快速识别与追溯、真实用户体验监控、服务器监控和端到端的应用性能管理。 OneAPM 可以追溯到性能表现差的 SQL 语句 Traces 记录、性能表现差的第三方 API、Web 服务、Cache 等等。在下一篇文章中,我们将深入研究 Xhgui ,以及用于展示、比较 xhprof 数据的用户界面( 本文系应用性能管理领军企业OneAPM 工程师编译整理 ) 。PHP 性能分析第二篇: Xhgui In-Depth原文 http://news.oneapm.com/profiling-php-part-2-xhgui-in-depth/主题 PHP 性能分析【前言】这是国外知名博主 Davey Shafik 撰写的 PHP 应用性能分析系列的第二篇,第一篇介绍Xhprof/Xhgui,第三篇则关注于性能调优实践。在第一篇中,我们初步介绍了 xhprof,以及如何安装和运行分析器。在本文,我们将介绍 Xhgui——用于审查并比较 xhprof 数据的用户界面(UI)。使用 XhguiXhgui 提供了许多协助性能评估的功能,既适用于单次运行,也能满足聚合环境——让你精确至具体问题、发现趋势。术语为了提高 Xhgui 的使用效率,你需要熟悉许多术语:1.调用次数函数调用的次数2.[包含] 实际执行时间 (wt)函数实际执行时间3.[包含] CPU 使用/CPU 用时 (cpu)运行该函数 CPU 所用时间4.[包含] 内存使用 (mu)目前该函数使用的内存量5.[包含] 内存使用量峰值 (pmu)函数使用的内存高峰6.专一实际执行时间 (ewt)7.专一 CPU 时间 (ecpu)8.专一内存使用量 (emu)9.专一内存使用量峰值 (epmu)术语2至5都是包含型的测量指标(尽管不总是明确指出),这些指标会计算函数及其子函数的调用。术语6至9是专一型的测量指标——它们只计算函数本身的资源调用。所有的测量数值都是调用该函数后的累计值。(例如,如果一个函数调用两次,第一次用时900毫秒,第二次,因为缓存的缘故,只耗时40毫秒,最终显示的时间就是940毫秒)。准备开始一旦在 HTTP 服务器上运行 Xhgui ,你首先会看到:在顶部,你会看到一个菜单,它包含:Recent — 近期大部分运行 (分页)Longest wall time — 根据实际执行时间从最慢的运行开始排序Most CPU — 从占用 CPU 时间最多的运行开始排序Most Memory — 从占用内存最多的运行开始排序Custom View — 执行 mongo DB 自定义查询Watch Functions — 应该出现在审查页面顶部的标记函数Waterfall — 从实验性视图查看并发请求的相互影响在本教程中,我选择分析用 Wordpress 搭建的网站性能。互联网上多于18%的网站都是基于 Wordpress 搭建的,这意味着,即便是对 Wordpress 很小的性能改进, 亦能产生巨大影响。查看一次运行的性能数据分析了几个页面的性能(或导入了文件)之后,你会看到它们罗列在 Xhgui :查看一次运行的性能数据,只需点击日期。通过单击适当的表头,你可以根据实际执行时间 (wt) , CPU 时间 (CPU) 、内存使用量 (mu) 或 内存使用量峰值 (pmu) 查看这些运行情况。从而轻易找出最慢的页面。单个性能页面展示了相当多的信息。在左侧可以看到运行的总体情况,以及运行时的环境数据,包括 GET (或 POST) 数据和服务器数据:在右侧,展示了 watch function 列表:该表详细列出了函数名称,调用次数 ,专一实际执行时间 (ewt), 专一内存使用量(emu)、和专一内存使用峰值(epmu)。此外,你可能会注意到页面顶部的两个按钮,“View Callgraph(查看调用图)” 和 “Compare this run(对比此次运行)”。接下来,我们看到两个图。图一展示了专一实际执行时间最长的六个函数,该时间是用在函数本身的时间(不包含任何子函数调用所占的时间)。图二展示内存使用量最大的六个函数。这些图通常能将你指向性能瓶颈。函数的细节在下方列出。如果将鼠标滑过图中的圆柱,这些信息也将出现在提示框中。最后,我们看到性能分析器收集到的大宗信息——函数列表:该表包含一个浮动的标题栏(即便鼠标向下滚动,该栏目也会保持在屏幕顶端),包含函数名,调用次数,和前面提到的专一和包含的测量值。默认情况下,该表按专一实际执行时间排序,时间最长者排在首位。通常你不会想改变这一次序,因为这让你快速找出运行最慢的函数,除非你想看内存使用量。当你想查看一个函数的运行情况时,点击该函数,会跳转到其详细页面。该页面首先会递归展示函数本身的细节。接下来, “Parent Functions(父函数)” 部分列出所有直接调用该函数的函数。最后,“Child Functions(子函数)”列出该函数直接调用的其他函数。父函数按照专一实际运行时间,列出标准列表数据。你需要确定:是函数本身运行缓慢,还是调用它的次数太多导致累积的实际执行时间太长。通过检查该函数的调用计数,然后回顾其父函数列表。如果你觉得函数调用次数没问题,你就要看看子函数运行情况。此处才是函数运行消耗时间的部分。子函数只显示包含测量值;这是因为你想很快找到耗时最长的代码路径。你可以点击每个子函数,下钻到相同的细节视图,并进行相同的分析。比较性能数据Xhgui 最好的特性在于比较两个不同的运行。这使你:修改系统 (如启用 opcache , mysql 查询缓存) 并比较结果修改代码(代码或 SQL 优化)并比较结果将异常的运行与“正常”运行比较比较两个运行时,你必须首先选择一个基础运行。点击其日期就能看到该运行的详细信息页。接下来,单击右上角的“Compare this run” 按钮:接着会跳转到同一 URL 下的运行列表,你可以选择一个进行比较:点击你想进行比较的运行的 "Compare" 按钮,将跳转到比较页面。比较视图只显示两个运行之间的差异。在页面顶部显示比较中的两个运行,以及一些辅助修改排序的按钮。接下来是概览:尽管这个表的所有信息都有用,但特别值得注意的两个差别是 "函数调用次数" 和 "专一实际运行时间" 。函数调用次数的差别暗示着两次运行的重要差异:不同的代码路径或缓存。第一个差别可能是有意的优化导致的,但若这并非你的目的,比较这两个运行很可能不会有太大的价值。另一方面,缓存是有益且有效的提高性能的方式。这种比较很容易验证缓存是否发生。包含实际执行时间的百分比差展示了性能调优的实际成果。理想情况下,我们将看到一个较小的百分比——这是第二运行时间比上第一次运行时间的占比。在截图中,第二次运行只花了第一次运行79%的时间,这意味着性能提升了21%。最后,我们看到功能细节:请记住,该视图只展示差别。差别通过绿色的负数和红色的正数表示。(负数表明调用次数更少,实际执行时间更短,CPU 耗时更短或内存消耗更少)如果没有差异,则显示为灰色的0。与其他表一样,您可以在任意列进行排序,默认的顺序方式是函数的调用次序。在这里你可以验证,你做的改变是否确有效果,是否为预期效果。你也可以在性能下降时使用该视图追踪原因。性能提高的一个好例子是:基于一个条件只调用一个函数——例如,您可能不需要过滤数据,如果之前已经做了。当你做出这种改变时,你会预期过滤函数的调用次数减少,从而性能提升。这两件事都可以在此处得到验证,以及其他意想不到的原因——你的条件比过滤本身需要更长的时间?如果真是如此,这将对性能产生负面影响。在此处,我们可以看到, NOOP Translations::translate 和 apply filter 的调用次数都减少了,但是 apply_filter 的专一内存使用量增加了133560个字节!发现趋势对我来说,Xhgui 最强大的功能是查看趋势。因为 xhprof 是被动分析器,可以在所有环境中启用 (dev、qa、阶段性、生产),可以持续地对流量取样分析。审查给定 URL 的所有数据,只需在运行列表点击它:这将跳转到该 URL 运行页面。该页面显示两个重要图表。第一个显示实际运行时间和 CPU 时间,第二个显示内存使用情况和峰值内存使用。这些图表列表中运行的数据,包括每次运行的 URL,时间,实际运行时间、CPU时间、内存使用和峰值。这些图是查看趋势和异常值的关键所在。但是该如何处理这些信息呢?对于数据异常者,首先你可以将鼠标悬浮在它上面验明正身,接着,你可以看一下它的单次运行。或用其他正常运行与其比较,从而发现不同。对于趋势,最好的选择是审查趋势开始的时间——你在此时添加缓存了吗?随着缓存变得更加完整,整体趋势应该向下。或者你的缓存失效,你将看到一个上升趋势,此时缓存正在重建。默认情况下,这些图表显示最近100次运行,你可以点击下一页去查看更久远时间的运行。另外,你可以点击搜索按钮来定制显示的界面:单击该按钮将显示搜索表单:你可以搜索具体日期之间的运行。也可以查看最近30分钟、1小时、2小时、12小时、24小时、1周、2周或30天内的运行——更小的时间间隔适合评估性能调优的结果。最后,你可以使用 PHPs DateTimeIntervalInterval 规范格式 指定自定义时间区间——例如,最近2天可使用 P2D,最近15分钟可使用 PT15M。Watch FunctionsWatch functions 允许你通过正则表达式识别特定的函数,或函数组,并显示在单个运行页面(见前文)。因为可以使用正则表达式,我们可以轻易地查看一个模块或扩展中的功能。For example, to watch all MySQL activity, simply add one of the following:例如,查看所有 MySQL 活动,只需添加如下列表的任意一项:mysql_(.*)for ext/mysqlmysqli(.*)for ext/mysqlipdo(.*)for PDO (适用于所有PDO-based数据库交互)如果你使用诸如 Propel 的 ORM,你可能使用 (. )Query::(. ) 追踪所有 Query 类。调用图(Callgraphs)Xhprof 的最后一部分是调用图 ,该图展示运行的代码执行路径。点击单一运行页面顶部的“View Callgraph”按钮即可查看调用图。在调用图中,拖拽结点可以更好地查看数据。鼠标悬浮在每个点击上,会显示其包含实际执行时间,同时允许你进入该函数的详情页。更直观地查看图,请点击: 体验免费使用OneAPM在线PHP应用性能分析SaaS服务!下一章节在第三部分也即最后一部分,我们会使用 xhprof 数据来优化代码。我们也会简单地介绍其他优化代码的工具。 (本文系应用性能管理领军企业OneAPM 工程师编译整理)PHP 性能分析第三篇: 性能调优实战原文 http://www.ituring.com.cn/article/205844主题 PHP 性能分析 数据库注意:本文是我们的 PHP 性能分析系列的第三篇,点此阅读 PHP 性能分析第一篇: XHProf & XHGui 介绍 ,或 PHP 性能分析第二篇: 深入研究 XHGui 。在本系列的 第一篇 中,我们介绍了 XHProf 。而在 第二篇 中,我们深入研究了 XHGui UI, 现在最后一篇,让我们把 XHProf /XHGui 的知识用到工作中!性能调优不用运行的代码才是绝好的代码。其他只是好的代码。所以,性能调优时,最好的选择是首先确保运行尽可能少的代码。OpCode 缓存首先,最快且最简单的选择是启用 OpCode 缓存。OpCode 缓存的更多信息可以在这里 找到。在上图,我们看到启用 Zend OpCache 后发生的情况。最后一行是我们的基准,也即没有启用缓存的情况。在中间行,我们看到较小的性能提升,以及内存使用量的大幅减少。小的性能提升(很可能)来自 Zend OpCache 优化,而非 OpCode 缓存。第一行是优化和 OpCode 缓存后结果,我们看到很大的性能提升。现在,我们看看 APC 之前和之后的变化。如上图所示,跟 Zend OpCache 相比,随着缓存的建立,我们看到初始(中间行)请求的性能下降,在消耗时长与内存使用量方面的表现都明显下降。接着,随之 opcode 缓存的建立,我们看到类似的性能提升。内容缓存第二件我们能做的事是缓存内容——这对 WordPress 而言小菜一碟。它提供了许多安装简便的插件来实现内容缓存,包括 WP Super Cache。WP Super Cache 会创建网站的静态版本。该版本会在出现诸如评论事件时依照网站设置自动过期。(例如,在非常高负载情况下,您可能会想禁止任何原因造成的缓存过期)。内容缓存只能在几乎没有写操作时有效运行,写操作会使缓存失效,而读操作不会。你也应该缓存应用从第三方 API 处收到的内容,从而减少由于 API 可用性导致的延迟与依赖。 WordPress 有两个缓存插件,可以大大提高网站的性能: W3 Total Cache 和 WP Super Cache 。这两个插件都会创建网站的静态 HTML 副本,而不是每次收到请求时再生成页面,从而压缩响应时间。如果你正在开发自己的应用程序,大多数框架都有缓存模块:Zend Framework 2: Zend\CacheSymfony 2: Multiple optionsLaravel 4: Laravel CacheThinkPHP 3.2.3: ThinkPHP Cache查询缓存另一个缓存选项是查询缓存。针对 MySQL,有一个通用的查询缓存帮助极大。对于其他数据库,将查询结果集缓存在 Memcached 或者 cassandra 这样的内存缓存,也非常有效。跟内容缓存一样,查询缓存在包含大量读取操作的场景是最有效的。由于少量的数据改动就会使大块的缓存区无效,尤其不能在这种情况下依赖 MySQL 查询缓存来提高性能。查询缓存或许在生成内容缓存时对性能有提升。如下图所示,当我们开启查询缓存后,实际运行时间减少了 40% ,尽管内存使用量没有明显改变。现有三种类型的缓存选项,由 query_cache_type 控制设置。设置值为 0 或 OFF 将禁用缓存设置值为 1 或 ON 将缓存除了以 SELECT SQL_NO_CACHE 开头之外的所有选择设置值为 2 或 DEMAND 只会缓存以 SELECT SQL_CACHE 开头的选择此外,你应该将 query_cache_size 设置为非零值。将它设置为零将禁用缓存,不管 query_cache_type 是否设置。想得到设置缓存的帮助,与许多其他性能相关的设置,请查看 mysql-tuning-primer脚本。MySQL 查询缓存的主要问题是,它是全局的。对缓存结果集构成的表格的任何更改都将导致缓存失效。在写入操作频繁的应用程序中,这将使缓存几乎无效。然而,你还有许多其他选择,可以根据你的需求和数据集建立更多的智能缓存,例如 Memcached , riak , cassandra 或 redis查询优化如前所述 ,数据库查询常常是程序执行缓慢的原因,查询优化往往能比代码优化带来更多切身的好处。查询优化有助于生成内容缓存时提高性能,而且,在无法缓存这种最坏的情况下也有益处。除了分析, MySQL 还有一个帮助识别慢查询的选择——慢查询日志。慢查询日志会记录所有耗时超过指定时间的查询,以及不使用索引的查询(后者为可选项)。您可以在 my.cnf 中使用以下配置启用日志。[mysqld]log_slow_queries =/var/log/mysql/mysql-slow.log long_query_time =1log-queries-not-using-indexes任何查询如果慢于 long_query_time (以秒为单位),该查询就会记录到日志文件log_slow_queries 中。默认值是10秒,最低1秒。此外, log-queries-not-using-indexes 选项可以将任何不使用索引的查询捕获到日志中。之后我们可以用与 MySQL 捆绑在一起的 mysqldumpslow 命令检查日志。在 WordPress 安装时使用这些选项 ,主页加载完成并运行后得到如下数据:$ mysqldumpslow -g "wp_" /var/log/mysql/mysql-slow.logReading mysql slow query log from /var/log/mysql/mysql-slow.logCount: 1 Time=0.00s (0s) Lock=0.00s (0s) Rows=358.0(358), user[user]@[host] SELECT option\_name, option\_value FROM wp_options WHERE autoload ='S'Count: 1 Time=0.00s (0s) Lock=0.00s (0s) Rows=41.0(41), user[user]@[host] SELECT user\_id, meta_key, meta_value FROM wp_usermeta WHERE user_id IN (N)首先,注意所有字符串值都以 S 表示,数字则以 N 表示。你可以添加 -a 标志来显示这些值。 接下来,请注意,这两个查询均耗时 0.00 s,这意味着他们的耗时在 1 秒的阈值以下,且没有使用索引。在 MySQL 控制台 使用 EXPLAIN,可以检查性能下降的原因:mysql> EXPLAIN SELECT option_name, option_value FROM wp_options WHERE autoload = 'S'\G*************************** 1. row ***************************id: 1select_type: SIMPLEtable: wp_optionstype: ALLpossible_keys: NULLkey: NULLkey_len: NULLref: NULLrows: 433Extra: Using where此处,我们看到 possible_keys 是 NULL,从而确认未使用索引。EXPLAIN是对优化 MySQL 查询非常强大的工具,更多信息可以在 这里 找到。PostgreSQL 同样也包括一个 EXPLAIN (该 EXPLAIN 与 MySQL 的差别很大),而 MongoDB 有 $explain 元 操作符 。代码优化通常只有当你不再受到 PHP 本身限制(通过使用 OpCode 缓存),缓存了尽可能多的内容,优化了查询之后,才可以开始调整代码。代码和查询优化带来足够的性能提升才能创建其他缓存;代码在最糟糕的环境(没有缓存)下性能越高,应用就越稳定,重建缓存的速度也就越快。让我们看看如何(潜在地)优化我们的 WordPress 安装。首先,让我们看看最慢的函数:令我惊讶的是,列表中的第一项 不是 MySQL (事实上 mysql_query() 是第四),而是 apply_filter() 函数。WordPress 代码库的特点是,通过基于事件的过滤系统执行多种数据转换,执行次序按照数据经内核、插件添加或回调的顺序。apply_filter()函数是这些回调应用的地方。首先,你可能会注意到,函数被调用 4194 次。如果我们点击查看更多细节,就可以按照“调用次数”降序排列“父函数”,从而发现 translate() 调用了 apply_filter() 函数 778 次。这很有趣,因为实际上我不使用任何翻译。我(并怀疑大多数用户)在使用 WordPress 软件时都设置为本土语言:英语。因此,让我们点击查看细节,进一步查看该 translate() 函数在做什么。在这里,我们看到两间有趣的事。首先,在父函数中,有一个被调用了773次:__()。查看该函数的源代码后,我们发现它是 translate() 的包装器。<?php/*** Retrieves the translation of $text. If there is no translation, or* the domain isn't loaded, the original text is returned.** @see translate() An alias of translate()* @since 2.1.0** @param string $text Text to translate* @param string $domain Optional. Domain to retrieve the translated text* @return string Translated text*/function __( $text, $domain = 'default' ) {return translate( $text, $domain );}?>根据经验法则,函数调用代价昂贵,应该尽量避免。现在我们总是调用 __() 而不是translate() ,我们应该把别名改为 translate() 来保持向后兼容性,而 __() 则不再调用非必要的函数。然而,实际上,这种改变不会带来多大的差异,只是微观的优化罢了——但它的确提高了代码可读性,简化了调用图。继续前进,让我们看看子函数:现在,深入该函数,我们看到有 3 个 函数或方法被调用,每个 778 次:get_translations_for_domain()NOOP_Translations::translate()apply_filters()按照包容性实际运行时间降序排列,我们看到 apply_filter() 是目前为止耗时最长的调用。查看代码:<?phpfunction translate( $text, $domain = 'default' ) {$translations = get_translations_for_domain( $domain );return apply_filters( 'gettext', $translations->translate( $text ), $text, $domain );}?>这段代码的作用是检索一个翻译对象,然后将 $translations->translate() 的结果传给apply_filter() 。我们发现 $translations 是 NOOP_Translations 类的一个实例。仅根据名称( NOOP ),再经代码中的注释证实,我们发现翻译器实际上没有任何动作!<?php/*** Provides the same interface as Translations, but doesn't do anything*/class NOOP_Translations {?>因此,也许我们完全可以避免这种代码!通过在代码上进行小规模调试,我们看到当前使用的是默认的域,我们可以修改代码以忽略翻译器:<?phpfunction translate( $text, $domain = 'default' ) {if ($domain == 'default') { return apply_filters( 'gettext', $text, $text, $domain );} $translations = get_translations_for_domain( $domain ); return apply_filters( 'gettext', $translations->translate( $text ), $text, $domain );}?>接下来,我们再次分析,确保要运行 至少两次 ——确保所有缓存都建立,才是公平的对比!这次运行的确更快!但是,快多少?为什么?使用 XHGui 的比较运行这一特性就能找到答案。回到我们最初的运行,点击右上角的 “比较此处运行” 按钮,并从列表中选择新的运行。我们发现,函数调用的次数减少了3% ,包容性实际运行时间减少 9% ,包容性CPU时间减少12%!之后,可以按调用次数降序排列细节页,这证实(如同我们的预期)get_translations_for_domain() 和 NOOP_Translations::translate() 函数的调用次数减少。同样,可以确认没有预料之外的变化发生。30 分钟的工作带来9 - 12% 的性能提升,这非常可喜。这就意味着真实世界的性能收益,即便是在应用了 opcache 之后。现在我们可以对其函数重复这个过程,直到找不到更多优化点。注意:此更改已提交到 WordPress.org 并已获更新。你可以在 WordPress Bug Tracker 跟踪讨论,查看实践过程。此更新计划包含在 WordPress 4.1 版本中。其他工具除了出色的 XHProf/XHGui,还有一些很好的工具。New Relic & OneAPMNew Relic 与 OneAPM 均提供前后端性能分析;洞察后台堆栈讯息,包括 SQL 查询与代码分析,前端 DOM 与 CSS 呈现,以及 Javascript 语句。 OneAPM 更多功能请移步 ( OneAPM 在线DEMO ) uprofileruprofiler 是目前还未发布的 Facebook XHProf 分支,该分支计划删除 Facebook 所需的 CLA。目前,两者具备相同的特性,只有一些部分重命名了。XHProf.ioXHProf.io 是 XHProf 的另一种用户界面。XHProf.io 在配置文件存储使用 MySQL ,用户友好性方面不及 XHGui。Xdebug在 XHProf 出现之前, Xdebug 早已存在——Xdebug 是一种主动的性能分析器,这意味着它不应该用于生产环境,但可以深入了解代码。然而,它必须与另一个工具配合使用以读取分析器的输出 , 比如 KCachegrind。但是 KCachegrind 很难安装在非 linux 机器上。另一个选择是 Webgrind 。Webgrind 无法提供 KCachegrind 的那些特性,但它是一个 PHP Web 应用程序,在任何环境都易于安装。若搭配 KCachegrind ,你可以轻易探索并发现性能问题。(事实上,这是我最喜欢的剖析工具!)结语分析和性能调优是非常复杂的工程。有了对的工具,并理解如何善用这些工具,我们可以很大程度地提高代码质量——即使是对我们不熟悉的代码库。花时间去探索和学习这些工具是绝对值得的。注意:本文是我们的 PHP 性能分析系列的第三篇,阅读 PHP 性能分析第一篇: XHProf & XHGui 介绍 ,和 PHP 性能分析第二篇: 深入研究 XHGui 。( 本文系 应用性能管理 领军企业 OneAPM 工程师编译整理 )
2023年08月09日
10 阅读
0 评论
0 点赞
1
...
108
109
110
...
158