XHProf 的安装配置与使用分析

目录

简介

XHProf,中文名为层次式性能分析器,是一个分层PHP性能分析工具。 它报告函数级别的请求次数和各种指标,包括阻塞时间,CPU时间和内存使用情况。 一个函数的开销,可细分成调用者和被调用者的开销。

安装配置

以 XHProf for PHP 7 为例,安装步骤如下:

1
2
3
4
5
6
7
8
9
git clone git@github.com:Yaoguais/phpng-xhprof.git ./xhprof
cd xhprof
/usr/local/Cellar/php71/7.1.5_17/bin/phpize
./configure --with-php-config=/usr/local/Cellar/php71/7.1.5_17/bin/php-config
make clean && make && make test && sudo make install

安装完毕后会显示扩展存放的路径如:

1
/usr/local/Cellar/php71/7.1.5_17/lib/php/extensions/no-debug-non-zts-20160303/phpng_xhprof.so

打开 php.ini,并在文件中加入代码如下:

1
2
3
[xhprof]
extension = /usr/local/Cellar/php71/7.1.5_17/lib/php/extensions/no-debug-non-zts-20160303/phpng_xhprof.so
xhprof.output_dir = /tmp/xhprof

编辑 php.ini 文件完毕后,重启 PHP-FPM 或 Apache(php7_module),通过 phpinfo() 函数或命令 php -m | grep xhprof 查看是否安装成功。

XHProf 函数与常量

函数

  • xhprof_enable — 启动 xhprof 性能分析器
  • xhprof_disable — 停止 xhprof 分析器
  • xhprof_sample_enable — 以采样模式启动 XHProf 性能分析
  • xhprof_sample_disable — 停止 xhprof 性能采样分析器

常量

  • XHPROF_FLAGS_NO_BUILTINS (integer):使得跳过所有内置(内部)函数。
  • XHPROF_FLAGS_CPU (integer):使输出的性能数据中添加 CPU 数据。
  • XHPROF_FLAGS_MEMORY (integer):使输出的性能数据中添加内存数据

XHProf 范例

学习 Swoole

Swoole 概述

Swoole 是基于 C 开发的一个 PHP 扩展。是一个 PHP的异步、并行、高性能网络通信引擎,提供了 PHP 语言的异步多线程服务器,异步 TCP/UDP 网络客户端,异步 MySQL,异步 Redis,数据库连接池,AsyncTask,消息队列,毫秒定时器,异步文件读写,异步 DNS 查询。 Swoole 内置了 Http/WebSocket 服务器端/客户端、Http2.0 服务器端。

PHP-CLI 模式

CLI 全称是 Command Line Interface,是命令行接口的意思。Swoole 大部分功能只能在 CLI 模式下运行。

进程和线程

进程是什么

进程是计算机中已运行程序的实体。用户下达运行程序的命令后,就会产生进程。进程需要资源才能完成工作,如 CPU 工作时间、存储器、文件和 IO 设备。每个 CPU 核心任何时间内仅能运行一个进程。

线程是什么

线程是进程的一个实体,是 CPU 调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源,但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。

进程和线程的区别

  • 创建进程的空间开销和时间开销要大于线程
  • 一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程,也就是主线程。
  • 资源分配给进程后,同一进程的所有线程共享该进程的所有资源
  • 线程的划分尺度小于进程,使得多线程程序的并发性高
  • 进程之间有着不同的地址空间,一个子进程挂了,不会影响其他子进程。线程之间共享内存,如果一个线程奔溃了,进而就会影响到其他线程和隶属进程了。

IO 模型

ApacheBench 参数讲解与基础使用

目录

认识 Apache Bench

Apache Bench 是 Apache 自带的一个命令式程序。它可以创建并发访问线程,同时模拟多个并发请求对某一个 URL 地址进行访问。它的测试目标是基于 URL 的。因此,Apache Bench 即可测试 Apache 的负载压力,也能测试 Nginx、LigntHttp、Tomcat、IIS 等其他 Web Server 的压力。

Apache Bench 命令对发出负载的计算机要求很低,既不会占用很高 CPU,也不会占用很多内存,但却会给目标服务器造成巨大的负载,其原理类似 CC 攻击。在测试使用也须注意,否则一次上太多的负载,可能造成目标服务器因资源耗完,严重时甚至导致死机。

Apache Bench 的命令参数

参数说明:

  • -n requests [Number of requests to perform]

    执行请求的数量

  • -c concurrency [Number of multiple requests to make at a time]

    一次并发请求的数量

  • -t timelimit [Seconds to max. to spend on benchmarking]

    测试进行的最大秒数

  • -s timeout [Seconds to max. wait for each response,Default is 30 seconds]

    响应等待的最大秒数

  • -b windowsize [Size of TCP send/receive buffer, in bytes]

    TCP 发送/接受缓冲区的大小,以字节为单位

  • -p postfile [File containing data to POST. Remember also to set -T]

    包含了需要 POST 的数据的文件,需要设置 -T 参数

  • -u putfile [File containing data to PUT. Remember also to set -T]

    包含了需要 PUT 的数据的问题,需要设置 -T 参数

  • -T content-type [Content-type header to use for POST/PUT data, eg.’application/x-www-form-urlencoded’,Default is ‘text/plain’]

    POST/PUT 数据时需要使用的 Content-type 请求头部

  • -v verbosity [How much troubleshooting info to print]

    打印什么级别的故障排除信息

  • -w [Print out results in HTML tables]

    以 HTML 表格的格式打印出执行结果

  • -i [Use HEAD instead of GET]

    使用 HEAD 请求方法代替 GET 请求方法

  • -x attributes [String to insert as table attributes]

    字符串作为 table 元素的属性插入

  • -y attributes [String to insert as tr attributes]

    字符串作为 tr 元素的属性插入

  • -z attributes [String to insert as td or th attributes]

    字符串作为 td 元素的属性插入

  • -C attribute [Add cookie, eg. ‘Apache=1234’. (repeatable)]

    添加一个 Cookie

  • -H attribute [Add Arbitrary header line, eg. ‘Accept-Encoding: gzip’,Inserted after all normal header lines. (repeatable)]

    添加任意的 HTTP 头部属性

  • -A attribute [Add Basic WWW Authentication, the attributes,are a colon separated username and password.]

    添加基础的 HTTP WWW 认证

  • -P attribute [Add Basic Proxy Authentication, the attributes,are a colon separated username and password.]

    添加基础代理身份验证

  • -X proxy:port [Proxyserver and port number to use]

    代理服务器的端口号

  • -V [Print version number and exit]

    打印 ApacheBench 的版本号并退出 ApacheBench

  • -k [Use HTTP KeepAlive feature]

    使用 HTTP KeepAlive 长连接

  • -d [Do not show percentiles served table.]

    不要显示服务表的百分比

  • -S [Do not show confidence estimators and warnings.]

    不要显示信息评估和警告

  • -q [Do not show progress when doing more than 150 requests]

    当超过 150 个请求时,不显示进度

  • -l [Accept variable document length (use this for dynamic pages)]

    接受可变文档长度(用于动态页面)

  • -g filename [Output collected data to gnuplot format file.]

    将收集到的数据输出到格式为 gnuplot 的文件

  • -e filename [Output CSV file with percentages served]

    将执行过程百分比的数据输出到格式为 csv 的文件

  • -r [Don’t exit on socket receive errors.]

    当接收到套接字错误时不要退出执行

  • -m method [Method name]

    执行请求时所使用的 HTTP 请求方法

  • -h [Display usage information (this message)]

    显示 ApacheBench 使用参数信息

  • -I [Disable TLS Server Name Indication (SNI) extension]

    禁用 TLS 服务器名称指示扩展名

  • -Z ciphersuite [Specify SSL/TLS cipher suite (See openssl ciphers)]

    指定 SSL/TLS 密码套件

  • -f protocol [Specify SSL/TLS protocol(SSL3, TLS1, TLS1.1, TLS1.2 or ALL)]

    指定 SSL/TLS 协议

Apache Bench 的结果参数

  • Server Software

    服务器软件与版本

  • Server Hostname

    服务器域名或地址

  • Server Port

    服务器端口号

  • Document Path

    请求文件的路径

  • Document Length

    请求文件的大小

  • Concurrency Level

    每次并发数

  • Time taken for tests

    测试总时间

  • Complete requests

    处理请求成功的次数

  • Failed requests

    处理请求失败的次数

  • Total transferred

    测试过程传输字节数

  • HTML transferred

    HTML 内容传输字节数

  • Requests per second

    每秒处理的请求数 - 吞吐率 - 平均返回数据时间,相当于 Complete requests / Time taken for tests

  • Time per request

    用户等待响应的平均时间,相当于 Time taken for tests /(Complete requests / Concurrency Level)

  • Time per request (mean, across all concurrent requests)

    服务器处理并发请求的平均时间,相当于 Time taken for tests / Complete requests

  • Transfer rate

    请求在单位时间内从服务器获取的数据长度,相当于 Total transferred / Time taken for tests

  • Connection Times (ms)

    • Connect:连接服务器的时间
    • Processing:进程处理完成请求的时间
    • Waiting:客户端等待结果返回的时间
    • Total:页面完成渲染的时间
  • Percentage of the requests served within a certain time (ms)

    所有请求的平均速度,如在测试过程中进度到50%时平均响应时间为10148ms,到66%时
    平均响应时间为11054ms。

高性能的 PHP 日志系统

课程目的

  1. 了解为什么需要日志功能
  2. 学会使用高性能的 SeasLog
  3. 如何在实际的项目中使用 SeasLog

什么是日志系统

专门记录系统运行时的信息的系统

日志的分类:

  • 系统日志:硬件或系统内核奔溃了
  • 服务日志:MySQL 服务或 PHP-FPM 服务奔溃了
  • 安全日志:MySQL 或 Linux 服务器受到了入侵,需要记录相关信息
  • 应用日志:队列发送消息或邮件成功时记录相关信息

然后日志系统的功能不能影响到其他正常功能的使用。

为什么需要日志功能

  1. 了解系统运行情况
  2. 记录用户操作信息
  3. 收集程序各项数据

为什么使用 SeasLog

  1. 高性能;SeasLog 是采用 C 语言编写的,而且它自带缓冲池的功能,先写入内存,再一次性写入文件
  2. 无需配置
  3. 功能完善,使用简单,自带分类模块,统计功能。

如何安装 SeasLog 扩展

  • tar 解压
  • phpize
  • ./config || ./config –with-php-config=PHP-CLI-PATH
  • make && make install
  • 返回 seaslog.so 扩展安装地址
  • 打开 php.ini 文件,将安装地址写入 php.ini
  • 重启 PHP-FPM 和 Web Server

SeasLog 配置项

  • seaslog.default_basepath:默认 log 根目录
  • seaslog.default_logger:默认 logger 目录
  • seaslog.disting_type:是否以 type 分文件
  • seaslog.disting_by_hour:是否每小时划分一个文件
  • seaslog.use_buffer:是否启用 use_buffer
  • seaslog.buffer_size:buffer 中缓冲数量
  • seaslog.level:记录日志级别
  • seaslog.trace_error:自动记录错误
  • seaslog.trace_exception:自动记录异常信息

Seaslog 常用方法

  • 配置方法
    • setBasePath 设置存放路径
    • getBasePath 获取存放路径
    • setLogger 配置模块
    • getLastLogger 获取模块
  • 写日志方法
    • log
    • info
    • notice
    • debug
    • warning
    • error
  • 读日志方法
    • analyzerCount
    • analyzerDetail

Seaslog 集成与 PHP 框架中

查看框架代码,使用适配器模式

注意事项

  • 不要在虚拟主机中使用 SeasLog,它需要安装配置 PHP 扩展,确保对虚拟主机有足够的权限再操作
  • 不要在集群服务中使用 SeasLog,它职责更多是本地日志存储,集群服务需要支持网络日志存储的服务

LNMP 性能优化之 PHP 性能优化

目录

PHP 性能优化初探

使用 PHP 其语法不恰当

PHP 做一门弱类型动态语言,在上手容易和开发快速同时,也会导致一些新手写出不规范的代码。比如在递归当中连接数据库读取数据;一次性从文件中读取大量的数据,处理完后却不主动释放内存;在遍历和循环中重复计算某个变量等等;数组的键没有加引号导致先查找常量集,都会导致 PHP 程序性能下降。

使用 PHP 做其不擅长的事

PHP 作为一门 Web 后端脚本语言,好处是能够快速实现 Web Application 所需功能,而且容易部署。缺点就是相对于强类型静态语言如 Java/C/C++ 来说,PHP 的性能较差,在实现计算密集型的业务时没有任何优势。同时也由于 PHP 是同步阻塞的 IO 模型,在高并发请求的场景下容易遇到瓶颈,需要通过 PHP 相关扩展来解决相关技术难题。

使用 PHP 连接的服务不稳定

PHP 作为一门胶水语言,势必会连接各种各样服务。常见的服务如:MySQL、Redis、MongoDB 等数据库,C/C++、GO、Java 等语言编写的后端服务。倘若 PHP 所连接服务不稳定,势必也会对 PHP 造成一定的性能影响。

使用 PHP 但尚未排查出来的问题

在某些情况,某个 PHP 程序或某段 PHP 代码莫名其妙地出现相当耗时的情况,不知道是 PHP 本身出现了问题,还是所用的框架出现了问题,亦或是 PHP 周边甚至是硬件的问题。这个时候就需要通过工具进行排查。常用的工具有:PHP-Xhprof、PHP-XDebug。

PHP 性能问题简析

PHP 的底层是由 C 语言组成的。每次运行 PHP 的程序,都是需要经过 C 语言遍写的 Zend 引擎来解析 PHP 文件,将其编译成为 Opcodes 后再去执行。就这样一来一回就消耗了不少时间和硬件性能。

PHP 运行流程

  1. Scanning(Lexing),将 PHP 代码转换为语言片段(Tokens)。
  2. Parsing,将 Tokens 转换成简单而有意义的表达式(Expression)。
  3. Compilation,将表达式编译成 Opocdes。
  4. Execution,顺次执行 Opcodes,每次一条,从而实现 PHP 脚本的功能。

(*.php) -> scanner -> (Tokens) -> Parser -> (Expression) -> Compilation -> (Opcodes) -> Execution -> (Output)

PHP 开销和速度排序

在日常使用中,PHP 各项性能开销和运行速度如下:

  • 性能开销(从大到小)
    • 硬盘 I/O
    • 数据库 I/O
    • 内存 I/O
    • 网络 I/O
  • 运行速度(从快到慢)
    • 内存 I/O
    • 数据库 I/O
    • 硬盘 I/O
    • 网络 I/O

读写网络数据本质也是硬盘操作,而且网络都是有延迟的,这也带了隐性的时间浪费,事实上 Web Application 感觉运行速度低下的原因,一般也都是网络 I/O 和 硬盘 I/O 引起的问题。

PHP 语言级性能优化

尽可能地使用内置函数来完成任务

能使用 PHP 内置方法解决的问题,就不要自己手写代码,一是手写代码一般冗余较多,可读性不佳。二是手写代码需要解析编译为底层代码再执行,没有 PHP 内置函数的性能高。

for & range() 实现同一功能

1
2
3
4
5
6
7
<?php
for ($i = 0; $i <1000; $i++) {
$array1[$i] = $i+1000;
}
range(1000,1999);

以 foreach、in_array 和 array_merge 实现同一功能对比说明:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
arrayMerged = [];
foreach ($array1 as $value) {
$arrayMerged[] = $value;
}
foreach ($array2 as $value) {
if(!in_array($value, $arrayMerged)){
$arrayMerged[] = $value;
}
}
array_merge($array1,$array2);

以 foreach 和 array_column() 实现同一功能对比说明:

1
2
3
4
5
6
7
8
9
10
<?php
$usernames = [];
foreach ($array as $key => $value) {
if(isset($value['username']) && !empty($value['username'])){
$usernames[$value['id']] = $value;
}
}
array_column($array, 'username','id');

以 foreach 和 array_filter() 实现同一功能对比说明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
$arr = ['a' => 1, 'b' => 2, 'c' => 3, 'd' => 4];
foreach ($arr as $key => $value) {
if($key === 'b'){
$result[$key] = $value;
}
if($value === 4){
$result[$key] = $value;
}
}
array_filter($arr, function($v, $k) {
return $k == 'b' || $v == 4;
}, ARRAY_FILTER_USE_BOTH)

尽可能地使用高性能的内置函数来完成任务

在 PHP 内置的函数之间,实现同一个功能时,也会存在着性能的差距。这是因为 PHP 内置函数的时间复杂度各不相同,了解各个 PHP 内置函数的时间复杂度,也有利于从代码层优化 PHP 项目。

以 isset() 和 array_key_exists() 对比说明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?php
function current_unix_time(){
list($usec,$sec) = explode(" ", microtime());
return ((float)$usec + (float)$sec);
}
$time = -current_unix_time();
$i = 0;
$array = range(1, 1000000);
while ($i < 1000000) {
if(array_key_exists($i, $array)){
}
$i++;
}
$time += current_unix_time();
echo $time."\n";
$time2 = -current_unix_time();
$i = 0;
$array = range(1, 1000000);
while ($i < 1000000) {
if(isset($array[$i])){
}
$i++;
}
$time2 += current_unix_time();
echo $time2;
// array_key_exists:0.080096006393433
// isset:0.041244029998779

尽可能避免使用魔法方法来完成任务

TODO

尽可能避免使用错误抑制符来完成任务

TODO

尽可能避免使用正则表达式来完成任务

正则表达式在 PHP 里算是一把双刃剑;它的优势就是使用起来简单。它的劣势就是性能低下。因为正则表达式需要回溯,而回溯的性能开销比较大,需要去优化。但优化正则表达式是需要一定的技术水平的。在常见的业务场景中,不妨使用字符串处理函数或 filter_var() 函数来实现功能。

尽可能避免在遍历或循环内做各种运算

使用 for、while 循环的时候,循环内的计算式将会被重复计算,这样就造成了一些可避免的不必要性能开销。

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
// 修改前
for($i = 0; $i < strlen("hello world"); $i++){
//do something
}
// 修改后
$str = "hello world";
$strlen = strlen($str);
for($i = 0; $i < $strlen; $i++){
//do something
}

尽可能避免在计算密集型的业务中使用

什么是计算密集型的业务?一般来说是大批量的日志分析、大批量的数据运算。PHP 的语言特性决定了 PHP 不适合做大数据量的运算。因为 PHP 是由 C 语言编写的,
PHP 的代码最终还是要转换成 C 语言去执行,这就需要一部分的性能开销。再加上 PHP 自身环境和语言特性缘故,PHP 做运算相比于 C 语言来说,不仅性能开销大,运算速度也慢,所以 PHP 不是适合做计算密集型的业务。PHP 的语言特性决定了它比较适合衔接后端服务或渲染页面。

PHP 周边级性能优化

Server 服务器

从服务器方面进行优化,可以选择将服务器不安装其他后端服务软件,仅仅安装 PHP 以及其必要扩展。使单机的性能全部向 PHP 倾斜。同时也对 PHP 的相关参数进行优化,将 PHP 单机服务器性能最大化。在大数据、高并发的场景下,可以尝试将 PHP 服务器集群化,通过负载均衡,将网络请求分配至不同的 PHP 单机服务器处理。

Network 网络

从网络方面进行优化时的需要注意的两点:

  • 对接接口是否稳定
  • 连接网络是否稳定

如何优化网络:

  • 使用更好的网卡:使用千兆以上的网卡
  • 选择更好的套餐:更多的流量,更高的带宽
  • 设置超时时间:
    • 连接超时:200 ms
    • 读取超时:800 ms
    • 写入超时:500 ms
    • 可以根据实际情况进行自由调整
  • 将串行的网络连接并行化
    • 使用 curlmulti*() 系列函数
    • 使用 Swoole 扩展或 Wokerman
  • 压缩网络请求数据
    • 启用 Server 的 Gzip 相关功能,最好是网络传输的数据大于 100 KB 的时候再使用,否则压缩包比源数据还大就徒劳了。
    • 压缩优势是减少客户端获取数据速度;劣势是由于 Server 需要压缩数据,会产生额外的 CPU 开销;而 Client 需要解压数据,客户端也会产生额外的 CPU 开销。

Database 数据库

数据库可以进行优化的层面:

  • 服务器硬件
  • 数据库配置
  • 数据库表结构
  • SQL 语句和索引

以 MySQL 为例,在服务器硬件方面,可以选择多核 CPU,增加内存,使用 SSD 和选择适合的 RAID 方案,选择千兆以上的网卡;在数据库配置方面,可以根据监控日志和性能压测数据进行调优;数据表结构方面可以根据需要存储的数据选择最优的数据类型,设计时可以根据数据库表的范式来设计减少冗余数据,也可以反范式设计提高查询性能;如果数据库表过大,可以采取垂直拆分和水平拆分。而 SQL 语句和索引方面,可以通过开启 mysqldumpslow 和 pt-query-digest 来查看慢查询。使用 EXPLAIN 分析 SQL 的执行计划;使用普通索引或复合索引增加查询数据;了解 MySQL 执行的细节写出最优解的 SQL 语句。

Cache 缓存

TODO

File 文件

TODO

PHP 瓶颈级性能优化

Opcode 优化

  • 启用 Zend Opcache,以 PHP 7 为例,在配置文件 php.ini 加入以下代码即可:
    1
    2
    3
    zend_extension=opcache.so
    opcache.enable=1
    opcache.enable_cli=1"

Runtime 优化

PHP 扩展编写

PHP 性能分析工具

ApacheBench 的参数讲解与基础使用

ApacheBench 参数讲解与基础使用:https://github.com/luisedware/Archives/issues/2

Vld Opcache 的参数讲解与基础使用

TODO

XHprof 的安装配置与使用分析

TODO

PHP 的性能优化

PHP 性能优化初探

什么情况下,会遇到了 PHP 性能问题?

  • PHP 语法使用得不恰当
  • 使用 PHP 语言做了它不擅长做的事情
  • 使用 PHP 语言连接的服务不给力
  • PHP 自身的短板
  • 尚未排查出的问题

PHP 性能问题简析

PHP 的性能问题,占整体项目性能问题的比例一般是三成,但也不仅仅局限于对 PHP 的性能优化。

PHP 的性能问题的解决方向,优化难度是从简单到困难

  • PHP 语言级的性能优化
  • PHP 周边问题的性能优化
  • PHP 底层级的性能优化

压力测试工具简介

Apache Benchmark(ab)

简介

ApacheBench 是 Apache 服务器自带的一个web压力测试工具,缩写为 ab ,旨在高压高并发下体现出各种问题和瓶颈.
ab 命令会模拟多个用户同时访问一个url (伪装一个并发访问的环境).
此命令对本地cpu要求不高,内存要求也不多,但是会对访问目标服务器造成严重的负载甚至资源耗尽而宿机,很类似与cc攻击.

如何使用

./ab -n1000 -c100 http://www.baidu.com/,其中 -n 是请求书,-c 是并发数,后面的是压测地址。

压测呈现的数据,比较重要的三个

  • Requests per second: 每秒处理的请求
  • Time per request: 每处理一个请求所消耗的时间
  • Time taken for test: 完成这次压测所消耗的时间

PHP 语言级的性能优化

优化点:产生额外开销的错误抑制符 @

  • 情况描述:PHP 提供的错误抑制符只是为了方便懒人
  • @ 的实际逻辑:在代码开始前、结束后,增加 Opcode,忽略报错
  • vld - PHP Opcode 查看扩展
  • 举例说明:file_get_contents()

    1
    php -dvld.active=1 -dvld.execute=0 XXX.php 只查看不执行
  • 好的建议:尽量不要使用 @ 错误抑制符

优化点:合理使用内存

  • 情况描述:PHP 有内存回收机制保底,但也请小心使用内存
  • 好的建议:利用 unset 及时释放不使用的内存

优化点:减少计算密集型业务

  • 情况描述:PHP 不适合密集型运算的场景
  • 为什么:PHP 语言特性决定了 PHP 不适合做大数据量运算
  • PHP 使用场景:适合衔接 Webserver 与后端服务、UI 呈现

优化点:务必使用带引号字符串做键值

  • 情况描述:PHP 会将没有引号的键值当做常量,产生查找常量的开销
  • 好的建议:严格使用引号作为键值

PHP 周边问题的性能优化

  • Linux 环境
  • 网络的性能
  • 缓存的性能
  • 数据库的性能
  • 服务器硬盘的性能

php => 网络 =》 DB 的场景,找出问题的和核心,并且找到大头去优化。

减少文件类操作:

  • PHP 常见的开销场景
    • 读写硬盘
    • 读写数据库
    • 读写内存
    • 读写网络数据
  • PHP 常见的开销场景的开销次序
    • 读写内存 小于 读写数据库
    • 读写数据库 小于 读写硬盘
    • 读写硬盘 小于 读写网络数据(网络延迟)
    • 读写数据库、读写硬盘、读写网络数据都是基于文件类操作
  • 优化网络请求
    • 问题
      • API 的不确定因素
      • 网络稳定性
    • 如何优化
      • 1.设置超时时间
        • 连接超时 200ms
        • 读超时 800ms
        • 写超时 500ms
      • 2.将串行请求并行化
        • 使用 curlmulti*()
        • 使用 Swoole 扩展

压缩 PHP 接口输出

  • 如何压缩
    • 使用 Gzip 即可
  • 压缩输出的利与弊
    • 利于我们的数据输出, Client 端能更快获取数据
    • 额外的 CPU 开销(如果传输数据大于 100 KB,能取得不错的效果)

缓存重复计算内容

  • 什么情况下做输出内容的缓存
    • 多次请求,内容不变情况

重叠时间窗口思想:

- 串行
- 重叠时间窗口思想

如果后一个请求不强依赖前一个请求的返回结果,可以使用将网络请求并行化。

旁路方案

如果后一个请求不强依赖前一个请求的返回结果,可以使用旁路方案。

PHP 性能问题的具体分析

工具:XHPorf(源自 Fackbook 的 PHP 性能分析工具)
实践:通过分析 Wordpress 程序,做优化

PHP 性能瓶颈解决方法

Opcode Cache: PHP 扩展 Yac
扩展实现:通过 PHP 扩展代替原 PHP 代码中高频逻辑
Runtime 优化:HHVM,PHP-JIT

设计模式之抽象工厂模式

1.抽象工厂模式

目录

1.1.模式定义

1.2.模式分析

1.3.模式结构

1.4.模式实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
<?php
/**
* Abstract Product
*/
abstract class Storage
{
abstract function use (): void;
}
/**
* ConcreteProduct
*/
class QiNiuStorage extends Storage
{
public function use (): void
{
}
}
/**
* ConcreteProduct
*/
class YouPaiStorage extends Storage
{
public function use (): void
{
}
}
/**
* Abstract Product
*/
abstract class Live
{
abstract function use (): void;
}
/**
* ConcreteProduct
*/
class QiNiuLive extends Live
{
public function use (): void
{
}
}
/**
* ConcreteProduct
*/
class YouPaiLive extends Live
{
public function use (): void
{
}
}
/**
* Abstract Product
*/
abstract class Cdn
{
abstract function use (): void;
}
/**
* ConcreteProduct
*/
class QiNiuCdn extends Cdn
{
public function use (): void
{
}
}
/**
* ConcreteProduct
*/
class YouPaiCdn extends Cdn
{
public function use (): void
{
}
}
/**
* Abstract Factory
*/
abstract class Factory
{
abstract public function createStorage(): Storage;
abstract public function createLive(): Live;
abstract public function createCdb(): Cdn;
}
/**
* Concrete Factory
*/
class QiNiuFactory extends Factory
{
public function createStorage(): Storage
{
return new QiNiuStorage();
}
public function createLive(): Live
{
return new QiNiuLive();
}
public function createCdb(): Cdn
{
return new QiNiuCdn();
}
}
/**
* Concrete Factory
*/
class YouPaiFactory extends Factory
{
public function createStorage(): Storage
{
return new YouPaiStorage();
}
public function createCdb(): Cdn
{
return new YouPaiCdn();
}
public function createLive(): Live
{
return new YouPaiLive();
}
}
$qiNiuFactory = new QiNiuFactory();
$qiNiuStorage = $qiNiuFactory->createStorage();
$qiNiuLive = $qiNiuFactory->createLive();
$qiNiuCdn = $qiNiuFactory->createCdb();
print_r($qiNiuStorage);
print_r($qiNiuLive);
print_r($qiNiuCdn);

设计模式之工厂方法模式

1.工厂方法模式

目录

1.1.模式定义

工厂方法模式定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。编写创建者类时,不需要知道实际创建的产品是哪一个。选择了使用哪个子类,自然就决定了实际创建的产品是什么。工厂方法模式让类把实例化推迟到子类。

1.2.模式分析

工厂方法模式的分析描述

工厂方法模式是简单工厂模式的进一步抽象和推广。由于使用了面向对象的多态性,工厂方法模式保持了简单工厂模式的优点,而且克服了它的缺点。在工厂方法模式中,核心的工厂类不再负责所有产品的创建,而是将具体创建工作交给子类去做。这个核心类仅仅负责给出具体工厂必须实现的接口,而不负责哪一个产品类被实例化这种细节,这使得工厂方法模式可以允许系统在不修改工厂角色的情况下引进新产品。

工厂方法模式的优点

  • 在工厂方法模式中,工厂方法用来创建客户所需要的产品,同时还向客户隐藏了哪种具体产品类将被实例化这一细节,用户只需要关心所需产品对应的工厂,无须关心创建细节,甚至无须知道具体产品类的类名。
  • 基于工厂角色和产品角色的多态性设计是工厂方法模式的关键。它能够使工厂可以自主确定创建何种产品对象,而如何创建这个对象的细节则完全封装在具体工厂内部。工厂方法模式之所以又被称为多态工厂模式,是因为所有的具体工厂类都具有同一抽象父类。
  • 使用工厂方法模式的另一个优点是在系统中加入新产品时,无须修改抽象工厂和抽象产品提供的接口,无须修改客户端,也无须修改其他的具体工厂和具体产品,而只要添加一个具体工厂和具体产品就可以了。这样,系统的可扩展性也就变得非常好,完全符合“开闭原则”。

工厂方法模式的缺点

  • 在添加新产品时,需要编写新的具体产品类,而且还要提供与之对应的具体工厂类,系统中类的个数将成对增加,在一定程度上增加了系统的复杂度,有更多的类需要编译和运行,会给系统带来一些额外的开销。
  • 由于考虑到系统的可扩展性,需要引入抽象层,在客户端代码中均使用抽象层进行定义,增加了系统的抽象性和理解难度,且在实现时可能需要用到DOM、反射等技术,增加了系统的实现难度。

在以下场景中可以使用工厂方法模式:

1.3.模式结构

工厂方法模式包含如下角色:

  • Product:抽象产品
  • ConcreteProduct:具体产品
  • Factory:抽象工厂
  • ConcreteFactory:具体工厂

UML 类图

1.4.模式实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
<?php
<?php
/**
* Abstract Product
*/
abstract class Storage
{
abstract public function upload();
abstract public function download();
}
/**
* Abstract Factory
*/
abstract class Factory
{
abstract public function createStorage();
}
/**
* Concrete Product
*/
class QiNiuFactory extends Factory
{
public function createStorage()
{
return new QiNiuStorage();
}
}
/**
* Concrete Product
*/
class QingYunFactory extends Factory
{
public function createStorage()
{
return new QingYunStorage();
}
}
/**
* Concrete Product
*/
class QiNiuStorage extends Storage
{
public function upload()
{
}
public function download()
{
}
}
/**
* Concrete Product
*/
class QingYunStorage extends Storage
{
public function upload()
{
}
public function download()
{
}
}
$concreteFactory = new QingYunFactory();
$concreteStorage = $concreteFactory->createStorage();
print_r($concreteStorage);

设计模式之简单工厂模式

1.简单工厂模式

目录

1.1.模式定义

简单工厂模式(Simple Factory Pattern),又称为静态工厂方法模式(Static Factory Method Pattern),它属于类-创建型模式。在简单工厂模式中,可以根据参数的不同返回不同类的实例。简单工厂模式专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。

1.2.模式分析

简单工厂模式的分析描述

  • 将对象的创建和对象本身业务处理分离可以降低系统的耦合度,使得两者修改起来都相对容易。
  • 在调用工厂类的工厂方法时,由于工厂方法是静态方法,使用起来很方便,可通过类名直接调用,而且只需要传入一个简单的参数即可,在实际开发中,还可以在调用时将所传入的参数保存在XML等格式的配置文件中,修改参数时无须修改任何源代码。
  • 简单工厂模式最大的问题在于工厂类的职责相对过重,增加新的产品需要修改工厂类的判断逻辑,这一点与开闭原则是相违背的。
  • 简单工厂模式的要点在于:当你需要什么,只需要传入一个正确的参数,就可以获取你所需要的对象,而无须知道其创建细节。

简单工厂模式的优点

  • 工厂类含有必要的判断逻辑,可以决定在什么时候创建哪一个产品类的实例,客户端可以免除直接创建产品对象的责任,而仅仅“消费”产品;简单工厂模式通过这种做法实现了对责任的分割,它提供了专门的工厂类用于创建对象。
  • 客户端无须知道所创建的具体产品类的类名,只需要知道具体产品类所对应的参数即可,对于一些复杂的类名,通过简单工厂模式可以减少使用者的记忆量。
  • 通过引入配置文件,可以在不修改任何客户端代码的情况下更换和增加新的具体产品类,在一定程度上提高了系统的灵活性。

简单工厂模式的缺点

  • 由于工厂类集中了所有产品创建逻辑,一旦不能正常工作,整个系统都要受到影响。
    使用简单工厂模式将会增加系统中类的个数,在一定程序上增加了系统的复杂度和理解难度。
  • 系统扩展困难,一旦添加新产品就不得不修改工厂逻辑,在产品类型较多时,有可能造成工厂逻辑过于复杂,不利于系统的扩展和维护。
  • 简单工厂模式由于使用了静态工厂方法,造成工厂角色无法形成基于继承的等级结构。

在以下场景中可以使用简单工厂模式:

  • 工厂类负责创建的对象比较少;由于创建的对象较少,不会造成工厂方法中的业务逻辑太过复杂。
  • 客户端只知道传入工厂类的参数,对于如何创建对象不关心;客户端既不需要关心创建细节,甚至连类名都不需要记住,只需要知道类型所对应的参数。

1.3.模式结构

简单工厂模式包含如下角色:

  • Factory:工厂角色
    • 工厂角色负责实现创建所有实例的内部逻辑
  • Product:抽象产品角色
    • 抽象产品角色是所创建的所有对象的父类,负责描述所有实例所共有的公共接口
  • ConcreteProduct:具体产品角色
    • 具体产品角色是创建目标,所有创建的对象都充当这个角色的某个具体类的实例

UML 类图

1.4.模式实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
<?php
/**
* Abstract Product
*/
abstract class Storage
{
abstract public function upload();
abstract public function download();
}
/**
* Concrete Product
*/
class QiNiuStorage extends Storage
{
public function __construct(){
}
public function upload(){
}
public function download(){
}
}
/**
* Concrete Product
*/
class QingYunStorage extends Storage
{
public function __construct(){
}
public function upload(){
}
public function download(){
}
}
/**
* Simple Factory(Static Factory)
*/
class SimpleStorageFactory
{
public static function createStorage(string $name) : Storage
{
if ($name === 'qiniu') {
return new QiNiuStorage();
}elseif($name === 'qingyun'){
return new QingYunStorage();
}
}
}
$concreteStorage = SimpleStorageFactory::createStorage("qiniu");
print_r($concreteStorage);

使用 Laravel 5.3 + Vue.js 2.1 开发知乎

项目配置

创建 Laravel 项目,并使用 PHPStorm 打开项目,将 app 目录设置为 Sources Root

1
laravel new zhihu

打开用户表迁移文件,新增字段代码如下:

1
2

发送邮件

安装 Laravel-SendCloud

1
composer require naux/sendcloud

添加服务提供者

1
Naux\Mail\SendCloudServiceProvider::class,

修改 .env 驱动和配置密钥

1
2
3
MAIL_DRIVER=sendcloud
SEND_CLOUD_USER= # 创建的 api_user
SEND_CLOUD_KEY= # 分配的 api_key

快速生成用户验证界面

1
php artisan make:auth

快速生成控制器,并修改代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
php artisan make:controller EmailController
<?php
namespace App\Http\Controllers;
use App\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class EmailController extends Controller
{
public function verify($token)
{
$user = User::where('confirmationToken', $token)->first();
if (is_null($user)) {
return redirect('/');
}
$user->isActive = 1;
$user->confirmationToken = str_random(40);
$user->save();
Auth::login($user);
return redirect('/home');
}
}

修改注册控制器代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<?php
/**
* Create a new user instance after a valid registration.
*
* @param array $data
* @return User
*/
protected function create(array $data)
{
$user = User::create([
'name' => $data['name'],
'email' => $data['email'],
'avatar' => '/images/avatars/default.png',
'confirmationToken' => str_random(40),
'password' => bcrypt($data['password']),
]);
$this->sendVerifyEmailTo($user);
return $user;
}
/**
* 验证用户邮箱
*
* @param $user
*
* @return void
*/
private function sendVerifyEmailTo($user)
{
$data = [
'url' => route('email.verify', ['token' => $user->confirmationToken]),
'name' => $user->name,
];
$template = new SendCloudTemplate('zhihu_app_register', $data);
Mail::raw($template, function($message) use($user) {
$message->from('luis@mail.cowcat.cc', 'Laravel');
$message->to($user->email);
});
}

新增路由

1
Route::get('email/verify/{token}', ['as' => 'email.verify', 'uses' => 'EmailController@verify']);

Flash 消息提示

安装 Laracasts Flash

1
composer require laracasts/flash

添加服务提供者

1
Laracasts\Flash\FlashServiceProvider::class,

本地化

安装 caouecs/Laravel-lang

1
composer require caouecs/laravel-lang:~3.0

安装完毕之后,把对应的文件复制粘贴到 resource/lang 目录下

富文本编辑器

安装 overtrue/laravel-ueditor

1
composer require "overtrue/laravel-ueditor:~1.0"

添加服务提供者

1
Overtrue\LaravelUEditor\UEditorServiceProvider::class,

发布配置文件与资源

1
php artisan vendor:publish

模板引入编辑器

1
@include('vendor.ueditor.assets')

编辑器的初始化

1
2
3
4
5
6
7
8
9
10
<!-- 实例化编辑器 -->
<script type="text/javascript">
var ue = UE.getEditor('container');
ue.ready(function() {
ue.execCommand('serverparam', '_token', '{{ csrf_token() }}'); // 设置 CSRF token.
});
</script>
<!-- 编辑器容器 -->
<script id="container" name="content" type="text/plain"></script>

简化富文本编辑器

1.clone 代码

1
git clone https://github.com/JellyBool/simple-ueditor.git

2.用此项目的 ueditor 目录替换原来的 ueditor 目录

3.实例化编辑器的时候配置 toolbar ,主要是 toolbar 的配置

1
2
3
4
5
6
7
8
9
10
11
var ue = UE.getEditor('editor', {
toolbars: [
['bold', 'italic', 'underline', 'strikethrough', 'blockquote', 'insertunorderedlist', 'insertorderedlist', 'justifyleft','justifycenter', 'justifyright', 'link', 'insertimage', 'fullscreen']
],
elementPathEnabled: false,
enableContextMenu: false,
autoClearEmptyNode:true,
wordCount:false,
imagePopup:false,
autotypeset:{ indent: true,imageBlockLine: 'center' }
});

使用 select 2 组件

1
https://select2.github.io/

使用 Laravel-Repository