0 前言
这篇文章算是对这本书的一点笔记 《高性能PHP应用开发》 目录大概都是搬过来的,但是有些技术是2011-11(出版日期)之前的,还有有些地方跟我的技术栈有所区别,就略过了。 比如Apache(我用的是nginx),比如memcached(我用的是redis),这些就略过了。
1. 基准测试技术
1.0 PHP技术栈
image.png
1.1 定义请求/响应声明周期
image.png
1.2 Apache Benchmark (ab工具)
- 查看测试结果
image.png - 常用选项
n: 要模拟的请求数;
c: 要模拟的并发请求数;
t: 执行模拟所需要的时间
1.3 Siege
Siege可以模拟托管文档的用户流量,与ab不同的是,Siege可以对文本文件中指定的URL列表运行负载测试。它还可以在执行其它请求之前让某个请求休眠,从而让你感觉某个用户在转移到Web应用程序的下一个文档之前正在读取该文档。
- 分析结果
image.png - 测试指标
image.png - 常用参数
-c 200 指定并发数200
-r 5 指定测试的次数5
-f urls.txt 制定url的文件
-i internet系统,随机发送url
-b 请求无需等待 delay=0
-t 5 持续测试5分钟 [H,M,S--时分秒,默认M]
-r和-t一般不同时使用
1.4 影响基准测试数字
- 地理位置和网络问题
- 响应大小
- 代码处理
- 浏览器行为
- Web服务配置
2.提高客户端下载和呈现性能
2.1 概述
- 第一个工具集(Firebug、YSlow、Page Speed)可提供以下信息来帮助我分析响应:
- Web服务器发送的响应的细节信息;
- 分析JavaScript中的前端逻辑;
- 浏览器将读取的资源的逐项列表;
- 浏览器获取和接受资源所花费的时间;
- 对如何优化响应的建议
- 第二个工具集(YUI Compressor、Closure Compiler和Smush.it)将帮助我们优化响应,简言之,我们可以使用这些工具压缩JavaScript、CSS文件以及网页所需的图像。
2.2 Firebug
好吧,我没用过,个人觉得Chrome自带的就够用了
image.png
还可以看时间花费细节:
image.png
2.3 YSlow
优化规则:
2.3.1 CSS优化
- 将CSS样式放置在HTML文档顶部;
- 避免某种CSS表达式;
- 精简CSS文件
2.3.2 图像优化
- 使用所要求的图像大小,而不是在HTML使用高度和宽度重新调整图像尺寸;
- 在可能的情况下创建子画面
2.3.3 JavaScript优化
- 将JavaScript放置在HTML底部;
- 精简JavaScript
- 将JavaScript做成外部文件
####### 2.3.4 服务器优化 - 服务器是否采用Gzip/bzip2压缩
- DNS查找是否有所减少
- 是否实现了Etag
2.4 YUI Compressor和Closure Compiler
这两个是JavaScript精简工具 现在不都是用gulp和webpack了吗?
2.5 图像压缩
使用Smush.it工具
3. PHP代码优化
3.1 PHP最佳实践
一些小tips
- ehco时用逗号连接比点连接更优;
- require优于require_once:require_once需要统计引入文件的引用次数
- for、foreach和while循环范围数组:foreach性能最好
- fread()、file_get_contents()、file()和readfile()。file_get_contents()、readfile()、fread()以字符串返回数据,而file()则将文件中的数据作为数组返回,每一行是数组的一个元素。四个方法都可以读取文件内容,但只有file_get_contents()将文件缓存到内存中,以便更快地进行读写操作,这种方式称为内存映射。
image.png
3.2 使用VLD、strace和Xdebug一探究竟
- 用VLD查看Opcode函数
pecl install channel://pecl.php.net/vld-0.14.0 安装
测试代码
<?php
echo hello , World!;
执行
php -dvld.active=1 test.php
结果
Finding entry points
Branch analysis from position: 0
Jump found. (Code = 62) Position 1 = -2
filename: /apps/www/test/test.php
function name: (null)
number of ops: 5
compiled vars: none
line #* E I O op fetch ext return operands
-------------------------------------------------------------------------------------
2 0 E > EXT_STMT
1 ECHO hello
2 EXT_STMT
3 ECHO World%21
3 4 > RETURN 1
branch: # 0; line: 2- 3; sop: 0; eop: 4; out1: -2
path #1: 0,
helloWorld!
- 使用strace进行C级跟踪(可以在Apache请求跟踪所调用的C函数,我玩的是nginx这部分跳过)
3.3 发现瓶颈
- Xdebug是面向PHP的调试器和概要分析工具,除了更多的调试信息外,Xdebug还可以为开发人员提供如下的信息:
- PHP脚本的内存消耗;
- 对某个函数执行的调用总数;
- 函数内部花费的总时间;
- 某个函数的完整栈跟踪;
- 举例
- 断点调试:
image.png
环境配置点这里 phpstrom+wampserver+xdebug配置 - 性能分析(可以用phpstorm分析xdebug生成的cachegrind.out日志):
image.png
分析结果:
image.png - 行为追踪【其实就是代替断点单步打日志】
示范代码:
xdebug_start_trace(/tmp/trace, XDEBUG_TRACE_APPEND ); //开始,日志写到/tmp/trace文件
class Node {
public $val; //值
public $d; //距离
public $p; //父节点
public $linkedNodes = [];//连接的顶点
public function __construct($val)
{
$this->val = $val;
}
}
/**
* Dijkstra
**/
class DijkstraAlgorithm
{
/**
* 生成图
* @param array $vertex
* @return array
*/
public function buildGraph($vertex)
{
$graph = [];
$nodes = array_keys($vertex);
foreach ($nodes as $node) {
$graph[$node] = new Node($node);
}
foreach ($vertex as $key => $item) {
foreach ($item as $value) {
if (isset($graph[$value])) {
$graph[$key]->linkedNodes[] = $graph[$value];
}
}
}
return $graph;
}
/**
* 初始化操作
* @param array $graphNodes
* @param Node $s
*/
public function init(&$graphNodes, &$s)
{
foreach ($graphNodes as $graphNode) {
$graphNode->d = 9999999;
}
$s->d = 0;
}
/**
* 松弛操作
* @param Node $u
* @param Node $v
* @param Array $w
* @return bool
*/
public function relax(&$u, &$v, $w)
{
$d = $u->d + $w[$u->val . _ . $v->val];
if($v->d > $d) {
$v->d = $d;
$v->p = $u;
return true;
}
return false;
}
/**
* 从队列取出最小距离的顶点
* @param array $queue
*/
public function extractMin(&$queue)
{
$result = [];
foreach ($queue as $item) {
$result[$item->d] = $item;
}
ksort($result);
$queue = array_values($result);
return array_shift($queue);
}
/**
* Dijkstra 算法
* 问题1:没考虑到负环路
* @param $graphNodes
* @param $s
*/
public function dijkstra(&$graphNodes, $s, $w)
{
$this->init($graphNodes, $s);
$gather = [];
$queue = $graphNodes;
while (!empty($queue)) {
$node = $this->extractMin($queue);
$gather[] = $node;
foreach ($node->linkedNodes as $linkedNode) {
if($this->relax($node, $linkedNode, $w)) {
if(!in_array($linkedNode, $queue)) {
array_push($queue, $linkedNode);
}
}
}
}
}
}
//顶点
$vertex = [
a => [b, c],
b => [d],
d => [e],
c => [e],
e => [],
];
//边的权重
$w = [
a_b => 1,
a_c => 3,
b_d => 2,
d_e => 2,
c_e => 4,
];
$g = new DijkstraAlgorithm();
$graphNodes = $g->buildGraph($vertex);
$g->dijkstra($graphNodes, $graphNodes[a], $w);
$w = [
a_b => 1,
a_c => 3,
b_d => 2,
d_e => 2,
c_e => 4,
];
xdebug_stop_trace();//结束行为追踪
centos的php.ini配置:
[xdebug]
zend_extension="/apps/install/php/lib/php/extensions/no-debug-non-zts-20151012/xdebug.so"
xdebug.remote_enable = 1
xdebug.trace_format = 1
xdebug.auto_trace = 0
xdebug.trace_output_dir = /tmp/
xdebug.trace_output_name = trace.%c.%p
xdebug.collect_params = 4
xdebug.collect_includes = On
xdebug.collect_return = On
xdebug.show_mem_delta = On
xdebug.var_display_max_depth = 2
分析结果
image.png
以上看起来不是很清晰,可以借助这个PHP进行分析:
用法:
usage:
php run-cli tracefile [sortkey] [elements]
Allowed sortkeys:
calls, time-inclusive, memory-inclusive, time-own, memory-own //后四个分别指包含子方法的耗时, 包含子方法的内存消耗,自己方法的耗时,自己方法体的内存消耗
示例:
php tracefile-analyser.php trace.log.xt time-inclusive 20 //获取最耗时的20个方法
结果:
Showing the 20 most costly calls sorted by time-inclusive.
Inclusive Own
function #calls time memory time memory
-----------------------------------------------------------------------------------------------------------------------------------------
1 5.8553 12017136 5.8553 12017136
commonservicesqmyxBuildingInfoService->getBuildingList 1 1.7179 3559752 0.0038 -457592
commonrepositoriesqmyxMyParamValueRepository->getParamOptionByScopeIdAndValueAndParamCode 45 1.3890 914592 0.0126 -275240
yiidbActiveQuery->one 46 1.3441 1012584 0.0062 704
yiidbQuery->one 46 1.2831 951440 0.0055 -323104
yiidbCommand->queryInternal 52 1.1350 1326664 0.0155 3504
yiidbCommand->queryOne 47 0.9962 1061744 0.0038 0
PDOStatement->execute 52 0.8573 634848 0.8573 634848
yiidbActiveQuery->createCommand 50 0.3639 268176 0.0090 -56976
yiidbQueryBuilder->build 50 0.2823 78424 0.0150 -133952
yiidbQueryBuilder->buildCondition 295 0.1507 32400 0.0082 10392
yiidbQueryBuilder->buildWhere 50 0.1488 27376 0.0025 224
yiiBaseYii::createObject 73 0.1318 5219896 0.0062 0
yiidbQueryBuilder->buildAndCondition 92 0.1316 22928 0.0060 -8896
yiidbCommand->queryAll 4 0.1239 247608 0.0002 0
yiidiContainer->get 75 0.1239 5219896 0.0051 0
yiidiContainer->build 75 0.1189 5219896 0.0083 18056
yiilogLogger->log 156 0.1131 739152 0.0470 -2074992
ReflectionClass->newInstanceArgs 75 0.1009 5022912 0.0037 99568
commonservicesprojectProjectService->filterDisabledProjectIdsByAppCode 1 0.0961 596344 0.0003 -4880
4. Opacode缓存
4.1 回顾路线图
image.png
4.2 PHP的生命周期
image.png
在此补充一下,php7新加了语法解析树。
应用了Opcode缓存的PHP生命周期:
image.png
4.2 Opcode缓存工具
- Alternative PHP Cache(APC)
- XCache
- eAccelerator
现在用opcache啦~~ 有兴趣看看鸟哥这篇文章 让PHP7达到最高性能的几个Tips
5. 变量缓存
image.png
缓存可以用memcached或者redis等中间件。
6. 选择正确的Web服务器
并发当然是选择nginx
7. 优化Web服务器和内容交付
大部分是针对Apache的优化,略过。 还有负载均衡、共享会话、分布式架构的一些问题(缓存一致性、缓存版本等等)、监控应用程序Ganglia、Cacti、Nagios。
8. 数据库优化
- MySQL的MyISAM的优缺点:
image.png - InnoDB的优缺点:
image.png - 选择存储引擎
1.当应用程序执行的大部分是读取操作时(95%)–MyISAM; 当事务性和一致性非常重要时—InnoDB; 当你有一个包含很多连接表的复杂模式时—InnoDB; 当不间断操作非常重要时—InnoDB(因为MyISAM没用防止数据崩溃的日志、版本控制和记录功能) - 了解MySQL如何使用内存
MySQL对内存来者不拒,你给它的内存越多,它的性能就越好,直到达到某个点。这个点就是MySQL的速度超过数据“工作集”的速度,超过这个点之后,无论你再为它分配多少内存,它的性能几乎都不会再有提高。
“工作集”就是经常使用的数据集,如果这个数据集可以轻松地放到内存中,性能的提高也基本上就到了极限,特别是当拥有一个好的索引集时。
- InnoDB与MyISAM内存使用的比较 MySQL配置文件提供了很多指令,可用于控制服务器的内存占用。分为下面几大类: 1.影响缓冲区和缓存大小的指令,适用所有存储引擎; 2.仅影响MyISAM存储引擎的指令; 3.仅影响InnoDB存储引擎的指令,通常以“innodb_”开头; 4.控制各种资源(如连接数量等)限制的指令; 定义属性(如字符集、路径等)的指令。
- 每服务器和每连接(线程)内存使用的比较 必须记住,当在配置文件中配置内存缓冲区和缓存的大小时,一些内存结构是按照每个 连接或每个连接来分配的。当连接到MySQL的连接数增加时,MySQL将使用更多的内 存,因此一定要从应用程序连接到数据库服务器打开的连接数减至最少,这一点非常重 要。MySQL可以使用的最大内存定义如下:
image.png - 查找配置文件
image.png - Mysqltuner.pl: 优化数据库服务器的内存
1). top监控进程
![image.png](https://upload-images.jianshu.io/upload_images/3596546-d9b99687b1416f6f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
2). iostat查看I/O性能
image.png
选项 说明
image.png
- 若 %iowait 的值过高,表示硬盘存在I/O瓶颈
- 若 %idle 的值高但系统响应慢时,有可能是CPU等待分配内存,此时应加大内存容量
- 若 %idle 的值持续低于1,则系统的CPU处理能力相对较低,表明系统中最需要解决的资源是 CPU
image.png
3).Mysqltuner.pl
当你看到较高的%util或%iowait值,那么应该查看驱动器的性能,或者看看打开的一些MySQL内存缓冲区以减少MySQL服务器命中磁盘所需的时间。但该系统没有显示I/O性能的任何问题,因此我们可以继续进行。 Mysqltuner.pl可以减轻配置和检查数据服务器的大量工作。
- 示例可能出现的问题:
image.png
- 附上MySQL的常用命令和设置
-- show variables like %max_connections%; 查看最大连接数
set global max_connections=1000 重新设置
mysql> show status like Threads%;
+-------------------+-------+
| Variable_name | Value |
+-------------------+-------+
| Threads_cached | 58 |
| Threads_connected | 57 | ###这个数值指的是打开的连接数
| Threads_created | 3676 |
| Threads_running | 4 | ###这个数值指的是激活的连接数,这个数值一般远低于connected数值
+-------------------+-------+
Threads_connected 跟show processlist结果相同,表示当前连接数。准确的来说,Threads_running是代表当前并发数
这是是查询数据库当前设置的最大连接数
mysql> show variables like %max_connections%;
+-----------------+-------+
| Variable_name | Value |
+-----------------+-------+
| max_connections | 1000 |
+-----------------+-------+
可以在/etc/my.cnf里面设置数据库的最大连接数
[mysqld]
max_connections = 1000
****命**令: show processlist;**
**如果是root帐号,你能看到所有用户的当前连接。如果是其它普通帐号,只能看到自己占用的连接。**
**show processlist;****只列出前100条,如果想全列出请使用****show full processlist;**
[MySQL](http://lib.csdn.net/base/14 "MySQL知识库")> show processlist;
**命令: show status;**
**命令:show status like %下面变量%;**
Aborted_clients 由于客户没有正确关闭连接已经死掉,已经放弃的连接数量。
Aborted_connects 尝试已经失败的MySQL服务器的连接的次数。
Connections 试图连接MySQL服务器的次数。
Created_tmp_tables 当执行语句时,已经被创造了的隐含临时表的数量。
Delayed_insert_threads 正在使用的延迟插入处理器线程的数量。
Delayed_writes 用INSERT DELAYED写入的行数。
Delayed_errors 用INSERT DELAYED写入的发生某些错误(可能重复键值)的行数。
Flush_commands 执行FLUSH命令的次数。
Handler_delete 请求从一张表中删除行的次数。
Handler_read_first 请求读入表中第一行的次数。
Handler_read_key 请求数字基于键读行。
Handler_read_next 请求读入基于一个键的一行的次数。
Handler_read_rnd 请求读入基于一个固定位置的一行的次数。
Handler_update 请求更新表中一行的次数。
Handler_write 请求向表中插入一行的次数。
Key_blocks_used 用于关键字缓存的块的数量。
Key_read_requests 请求从缓存读入一个键值的次数。
Key_reads 从磁盘物理读入一个键值的次数。
Key_write_requests 请求将一个关键字块写入缓存次数。
Key_writes 将一个键值块物理写入磁盘的次数。
Max_used_connections 同时使用的连接的最大数目。
Not_flushed_key_blocks 在键缓存中已经改变但是还没被清空到磁盘上的键块。
Not_flushed_delayed_rows 在INSERT DELAY队列中等待写入的行的数量。
Open_tables 打开表的数量。
Open_files 打开文件的数量。
Open_streams 打开流的数量(主要用于日志记载)
Opened_tables 已经打开的表的数量。
Questions 发往服务器的查询的数量。
Slow_queries 要花超过long_query_time时间的查询数量。
Threads_connected 当前打开的连接的数量。
Threads_running 不在睡眠的线程数量。
Uptime 服务器工作了多少秒。
My.ini配置 虚拟内存
[](http://www.pc51.net/data/MySQL/2007-01-04/2551.html)
innodb_buffer_pool_size=576M ->128M InnoDB引擎缓冲区
query_cache_size=100M ->32 查询缓存
tmp_table_size=102M ->32M 临时表大小
key_buffer_size=16m ->8M
**设置max_connections**
**命令:show variables like %max_connections%** (这个办法在debian+mysql Ver 12.22 Distrib 4.0.22, for pc-[Linux](http://lib.csdn.net/base/linux "Linux知识库") (i386)
里实验了)
设置办法是在my.cnf文件中,添加下面的最后红色的一行:
--------------------------------------------------------------------------------
[mysqld]
port=3306
#socket=MySQL
skip-locking
set-variable = key_buffer=16K
set-variable = max_allowed_packet=1M
set-variable = thread_stack=64K
set-variable = table_cache=4
set-variable = sort_buffer=64K
set-variable = net_buffer_length=2K
set-variable = max_connections=32000
(在院里的DELL机器mysql4.0里的语法不同
max_connecionts=2000
直接这么写就好了
)
--------------------------------------------------------------------------------
修改完毕后,重启MySQL即可。当然,为了确保设置正确,应该查看一下max_connections。
注意:
1、虽然这里写的32000。但实际MySQL服务器允许的最大连接数16384;
2、除max_connections外,上述其他配置应该根据你们系统自身需要进行配置,不必拘泥;
3、添加了最大允许连接数,对系统消耗增加不大。
4、如果你的mysql用的是my.ini作配置文件,设置类似,但设置的格式要稍作变通。
用mysqld --help 可以查看到max_connections 变量。
或者 mysql -uuser -p
**后mysql>show variables;
也会看到max_connections 。 **
下面是修改张老师 的redhat9的方法:
先是mysql -uw01f -p
mysql>show variables;
看到max_connections 为100
mysql>exit;
vi /etc/my.cnf
[mysqld]
set-variable=max_connections=250 #加入这些内容
:wq
/etc/init.d/mysqld restart
- 优化InnoDB 1.innodb_file_per_table:InnoDB为每个数据库创建一个文件并用这个文件来管理数据库的表。这意味着如果表的大小先增大然后又缩小,则很难恢复磁盘空间。设置该选项将使每个表使用一个独立的数据存储文件。如果你想在现有数据库上更改此设置,应该备份该数据库,然后删除它,再更改选项,然后重新启动服务器,最后从备份中重新加载该数据库; 2.innodb_buffer_pool_size:如果你只使用InnoDB表,可以把这一项设置为可用内存的70%左右,如果混用MyISAM,则可以小一些; 3.innodb_log_buffer_size=4M:4M可以满足绝大部分记录的需求,而且提供了一个合理的性能; 4.innodb_log_file_size=256M:推荐值,可以在恢复数据库的速度与保持较高的运行时性能之间取得良好的平衡; 5.innodb_flush_log_at_trx_commit=2:这一项控制将日志文件刷新到磁盘的频率,如果你可以容忍在发生崩溃时丢失一些记录,那么可以把它的值设置为2来减少磁盘写入的数量、加快性能以及减少驱动器上的I/O负担。
- 找到有问题的日志 查看慢查询日志,开启和如何查看就不说了,网上一搜铺天盖地的。
- 分析有问题的查询 explain神器