分类目录归档:PHP

一组PHP7在Nginx与Apache下的测试数据

概述

PHP7作为迄今为止性能最强的PHP版本,希望通过一些测试来了解它在Nginx与Apache下的表现。数据仅供参考。

由于个人水平有限,对测试的理解可能有所偏差,如有错误麻烦指出。

运行环境

测试环境一共分为两台机器,机器A用作PHP运行环境,机器B作为压测机。A与B的CPU均为12 x Xeon E5 + 16GB RAM

两台机器位于同一网段。

测试工具使用ab

为了便于测试,调整了内核参数:

测试使用的软件版本为:

  • Apache 2.2
  • Nginx 1.2.7
  • PHP 7.0.4

Apache在httpd-mpm.conf中的配置为:

由于Apache+modphp7Nginx+PHP-FPM二者的工作模式差别较大,想要均衡的配置出一个均衡的测试比较环境很困难,从简单出发,考虑到Apache在上述配置下最大会fork出512个进程处理请求,将PHP-FPM最大进程数也设成512个,即:

测试方法

测试主要针对三种操作进行:输出Hello WorldRedis KV读取操作输出phpinfo()

选择Redis KV读取操作的目的主要想测试下这一常用扩展的在Apache与Nginx作为WebServer环境下的表现。

测试分为2组,分别在25并发以及512并发下进行,测试时长为1min,同时记录服务器的load情况。

关注的数据为:

  • 最小响应时间
  • 平均响应时间
  • 平均每秒请求数
  • load
  • 相关进程数

测试结果

测试从性能指标以及系统负载两个方面进行观察。

性能指标

最小响应时间

min_response_time.png

针对最小响应时间,并发数高低对最小响应时间没有显著的影响。在测试初期系统负载较低,测试数据应该表现最佳的阶段,推断二者在此阶段差距不大。

平均每秒请求数

requests_per_sec.png

针对平均每秒请求数,Nginx+PHP-FPM的组合处理能力稍低于Apache+modphp7的组合,考虑到Nginx与PHP-FPM之间有网络通信(测试中采用了tcp socket)的开销,这个结果是可以接受的。高并发下考虑系统开销较大,请求处理能力会稍有下降。

平均响应时间

mean_response_time.png

针对平均响应时间,高并发下同样由于系统开销较大的缘故,平均的请求处理响应时间升高也是符合预期的。

系统负载

从性能指标上看,Nginx+PHP-FPM的组合与Apache+modphp7的组合在表现上没有巨大的差距,但是在系统负载上,Nginx+PHP-FPM的配置在高并发的情况下远优于Apache+modphp7的组合。 在512并发时,在测试Hello world时,系统load的表现就有巨大的差距:

load_hello_world.png

同样的情况还出现在512并发之下对Redis KV操作的测试case之下:

load_redis_kv.png

即便是在低并发(25并发)下,Redis KV此类需要操作网络资源的操作,Apache+modphp7在系统负载上的表现也差于Nginx+PHP-FPM

load_redis_kv_c25.png

对于这类需要操作I/O的操作来说,Apache+modphp7的组合表现差于使用了异步I/O的Nginx与PHP-FPM的组合是符合预期的。

在测试phpinfo()输出时,虽然二者在load上的区别不大,但是Nginx+PHP-FPM的组合在进程数这一指标上完全占优

processes_count_phpinfo.png

考虑到Nginx+PHP-FPM的组合无需fork出新的子进程处理新到的客户端请求,以及phpinfo()的执行时间较长这两个因素,同时fork子进程等操作属于消耗系统资源较大的操作,这个现象是符合预期的。

总结

使用php7时,在性能上Apache+modphp7的组合与Nginx+PHP-FPM的组合相差无几,但对于系统负载上来说,Nginx+PHP-FPM组合综合表现优于Apache+modephp7的组合,可以推断Nginx+PHP-FPM的组合对于构建高并发的服务更有优势。

综合考虑,Nginx+PHP-FPM表现较优。

TODO

  • 尝试阅读ab源码,了解其测试原理
  • 比对fpm在动静模式之间的区别

emoji,json_encode与MySQL

前言

之前有看到过对于emoji表情这类字符,想要存入MySQL之中,需要建立或者修改表的字符集为utf8mb4,而且对MySQL版本还有一定的要求(>=5.5.3),否则MySQL会提示类似:

的错误。

然而在PHP中其实有一种情况是可以“存储”emoji表情字符的,那就是将包含emoji的字符串通过PHP的json_encode方法处理后再进行存储操作。

emoji不能直接存入UTF-8字符集的表中的原因

根据维基百科上对于字符编码的定义,我们可以将字符集字符编码看做同义词,后面二者将会不加区分的使用。

emoji不能直接存入UTF-8字符集的表中的原因,就得从emoji是什么说。

emoji简而言之就是若干组Unicode字符,随着iPhone、微博、微信等硬件、软件的支持、普及,emoji也算是越来越常见。

这类字符因为其码位值的原因(U+1F300..1F545U+1F600..1F641U+1F300-1F5FFU+1F600-1F64FU+1F300-1F5FFU+1F600-1F64F),是无法通过3字节长度的UTF-8编码表示的。

以啤酒(BEER MUG)符号举例,这个符号的Unicode的码位值为U+1F37A,那么转换成UTF-8编码则是:

0xF0 0x9F 0x8D 0xBA

明显可以看出需要通过4个字符进行表示,而在MySQL中,一般DBA给定的默认字符集都是UTF-8,而MySQL的文档中写道:

The utf8 character set is the same in MySQL 5.6 as before 5.6 and has exactly the same characteristics:

No support for supplementary characters (BMP characters only).

A maximum of three bytes per multibyte character.

也就是说直到5.6及以前的MySQL的UTF-8字符集最大只支持3个字符,那么emoji字符无法直接存入也是可以理解的了。

emoji转化后进行存储

从上一主题可以看到,不能直接存储emoji字符的话,那么是不是可以通过其他间接手段对emoji字符进行存储呢?

答案是肯定的,如果能够对emoji字符转化成为其编码的字符串之后进行存储,在展示时从字符串转化为原始字符即可,或者是将其转化成为一些特别的字符串进行处理,总而言之就是

的过程。

json_encode与emoji

在实际项目中,JSON是常见一种数据格式,很多结构化的数据可以通过JSON进行存储,而PHP的JSON处理极其简便,配合array简直不能更轻松,很多情况下,数组数据可以通过json_encode之后当做字符串存入MySQL中。

然而这与emoji有什么关系呢?

答案是json_encode会对作为数组中值的emoji字符进行转化,之后转化结果字符串可以方便的存入数据库中。

翻阅在使用的PHP 5.4.24的源码,阅读json扩展的源码ext\json\json.c,可以看到php_json_encode函数中的位于文件的629~631行:

跟进到json_escape_string函数,可以看到文件的422行:

即会将UTF-8编码的字符转化为UTF-16的编码。

之后423538行将转化结果添加到返回的字符串buf中(详情参见PHP源代码文件)。

实际操作一下:

emoji_json_encode_test

这样就解释了为什么经过json_encode处理之后的字符可以存入MySQL中,当然,这样处理之后的成本之一就是存储的字节数的增加了。

CI可能不应该使用持久连接Oracle数据库

问题背景

看过CI框架用法应该会看到,在配置CI框架连接数据库时,默认会开启持久连接,即类似这样的配置$db['test']['pconnect'] = TRUE;,使用MySQL时会调用mysql_pconnect方法实现这一个功能,而oci8扩展恰巧也有类似的方法oci_pconnect:

oci_pconnect

方法的用处文档上说的很清楚:

oci_pconnect() 创建一个到 Oracle 服务器的持久连接并登录。持久连接会被缓冲并在请求之间重复使用,可以降低每个页面加载的消耗。

那么按道理来说这样的功能应该是会提升处理能力的,但是问题在于,持久连接会增加Oracle的进程数,一旦进程数耗尽,那么新的连接请求可能会被拒绝,反而会使得处理能力下降。

今天遇到了这样的一个问题,当双机各自开启1024个php-fpm进程时,使用sqlplus连接数据库被拒绝,同时各种操作都被拒绝执行。

所用的机器是两台阿里云的8核心16GB ECS服务器,Oracle数据库为11G R2,PHP版本为5.4,CI版本为2.2

表现

首先是一次压测结束后,发现通过sqlplus连接不上Oracle数据库,即首先sqlplus /nolog之后使用conn命令无法连接成功,发现报错信息为:

max-proc-exceeded

居然没有可用的进程了!

那么目前需要做的就是释放这些进程,直接SHUTDOWN IMMEDIATE也该是最简单粗暴也能达到目的的方法,但是目前根本不能按照原先的sqlplus /nolog之后使用conn /as sysdba登录,解决的方式可以直接在shell中使用sqlplus / as sysdba即可完成登录。

分析

增加进程数

登录完成之后进行SHUTDOWN IMMEDIATE以及STARTUP操作,重启之后修改可用进程数:

关于这几个数值的关系,参考了这篇博文,博文中作者提到这三者可以按照如下关系配置:

使用spfile作为scope的参数原因是这几个参数并不能修改后立即生效,需要重启数据库之后才能生效。

修改完成后重启数据库,重新进行压测,ps -ef | grep oracle | wc -l发现进程数果然上来了,但是在压测结束后,发现再次出现了这一问题。那么这一问题并不单纯是进程不足的问题。

测试环境检查

Oracle进程数增加却被完全消耗,这个情况确实让人觉得奇怪。

检查CI的配置时,发现数据库配置文件中使用了pconnect属性,查阅oci_pconnect的文档,注意到如下一句话:

一个典型的 PHP 应用程序对于每个 Apache 子进程(或者 PHP FastCGI/CGI 进程)会有一个打开的持久连接到 Oracle 服务器

那么,目前一共有两台机器,每台机器开启了1024个php-fpm的进程,总计2048个,那么Oracle的进程数仅仅为1000个,自然是不够用的。

持久连接虽然减少了连接的成本,然后却使得没有进程可用,显然问题出在CI的持久连接上。

解决

解决的方式自然是关闭CI的持久连接配置,在设定$db['test']['pconnect'] = FALSE;之后,通过ab进行压力测试,在10000并发的情况下,所用的Oracle进程数长期维持在30个左右,基本上解决了这一问题。

结论

CI在使用持久连接时,可能需要考虑Oracle的可用进程数(设为X)以及php-fpm的个数(设为Y)之间的关系,当X>Y时,个人认为是可以考虑使用持久连接的。

否则,个人认为可以牺牲一部分性能来建立连接,使得Oracle维持有足够的可用进程数。

CI连接Oracle 11G数据库

CI连接Oracle 11G数据库

CI框架算是个人最喜欢的PHP框架之一,易用性上没的说,还有完备的中文文档,不过大多数时候是搭配MySQL一起使用。

不过最近接触的一个项目使用的是Oracle 11G数据库,开发前给大家搭环境的时候发现连接有一些问题,主要来说是安装配置上的一些问题。

环境

  • CodeIgniter 2.2.0
  • Oracle 11G R2
  • CentOS 6.4
  • PHP 5.2

扩展安装

首先CI本身是能支持Oracle数据库的,在DB Driver的代码中可以明确地看到,下面需要的就是安装oci8扩展了。

oci8扩展在安装上和其他的PHP扩展没有太多的区别,稍微有点区别的是需要下载安装一个Instant Client,Windows下的下载安装倒也还算顺利,然后Linux下的下载真是让人哭笑不得了,因为页面上的js错误,点击我同意按钮之后是不会出现熟悉的下载功能的,即各个链接仍然连接到本页,不过没有关系,看了下页面源码,还是找出了rpm包的实际下载链接(当然这个也是要注册Oracle的账户才能下载的)。

http://download.oracle.com/otn/linux/instantclient/112010/oracle-instantclient11.2-basic-11.2.0.1.0-1.x86_64.rpm

同时还需要安装devel包否则在编译扩展时会出现找不到头文件的情况。

http://download.oracle.com/otn/linux/instantclient/112010/oracle-instantclient11.2-devel-11.2.0.1.0-1.x86_64.rpm

之后就是常规的phpize && configure && make && make install了。

多说一句,如果通过rpm包安装了sqlplus之后不能使用,出现诸如:

sqlplus64: error while loading shared libraries: libsqlplus.so: cannot open shared object file: No such file or directory

的问题时,可以考虑通过:

export LD_LIBRARY_PATH=/usr/lib/oracle/11.2/client64/lib:$LD_LIBRARY_PATH

的方式来解决。

CI配置

下面来看在autoload配置文件中已经配置了autoload database配置的情况下CI的配置。

网上对于CI的配置主要区别在hostname这一个项目,有写成tnsnames.ora样式的,这个自己没实验成功,最后读了一下CI连接部分的代码,确定了连接中hostname配置应该是:

//数据库IP:数据库端口/数据库名称

最终连接成功的配置如下:

$db['default']['hostname'] = "//192.168.1.200:1521/db200";
$db['default']['username'] = 'learn';
$db['default']['password'] = '123456';
$db['default']['database'] = '';
$db['default']['dbdriver'] = 'oci8';

db200是dbca安装数据库时指定的名称。

环境搭好之后开发自然是要开始了。

以上。