曾 棕 根
(宁波职业技术学院电子信息工程学院 浙江 宁波 315800)
过去,在Windows操作系统上运行PHP网站通常采用WAMP架构,即Windows + Apache + MySQL + PHP黄金组合[1]。但该架构存在两个缺点,一个是性能较低,只能用于开发环境,无法成为Windows操作系统上的生产环境;另一个就是迁移困难,无法做到便捷地从开发机上部署到生产服务器上。
为了解决上述两个难题,本文研制了一款全新的WNMP架构,实现了Windows操作系统下PHP运行环境的便携式、高并发与高性能。
WNMP架构是指由Windows操作系统、Nginx服务器、MariaDB服务器和PHP模块组成的一套完全免费开源的PHP网站运行架构。
Nginx是一款新型的、高性能的HTTP和反向代理服务器,其特点是占有内存少、并发能力强[2]。
MariaDB是MySQL的一个分支,主要由开源社区在维护,采用GPL授权许可,它完全兼容MySQL。
PHP是一种通用开源脚本语言,具有面向对象特征,拥有丰富的开发框架,执行效率高,是当前开发动态网站的首选语言之一。在2014年的Qcon分享中有一个数据,全球排名前100万的网站中,81.3%使用的Web服务端脚本语言是PHP[3]。
WNMP架构与前端用户交互方式:用户从客户端浏览器发起PHP页面请求;再由Nginx转发给PHP去编译、运行;PHP到MariaDB数据库中存取数据,最后以HTML标记生成结果页面并转交给Nginx服务器;Nginx服务器再将结果页面返回给客户端浏览器;客户端浏览器则将结果页面解释出可视化结果给用户浏览。WNMP服务器间交互如图1所示。
图1 WNMP架构B/S交互示意图
为了使WNMP架构具备免安装和便携性的特征,把Nginx、PHP、MariaDB都放置在C盘的Win32_nmp/Win64_nmp文件夹下,里面包含这几个文件夹:nginx、php、mysql、tmp、xxfpm和startwnmp.bat、stopwnmp.bat两个批处理文件。
其中, startwnmp.bat负责启动nginx、mysql和xxfpm三项服务,而stopwnmp.bat则负责一次性停止这三项服务。tmp文件夹则用来保存WNMP架构运行过程中的全部临时数据。这种文件组织方式使WNMP架构运行过程中产生的所有数据都保留在Win32_nmp/Win64_nmp文件夹中,实现了免安装和随时将此架构通过冷拷贝Win32_nmp或Win64_nmp文件夹的方式将整个架构及网站数据随时进行完整迁移。
Nginx、PHP、MariaDB程序是从官方网站上下载编译好的二进制压缩包,直接解压缩后,放在Win32_nmp/Win64_nmp文件夹相应位置即可,下载位置及版本:
Nginx:从http://nginx.org/下载nginx-1.12.0.zip包;
PHP:从http://php.net/下载32位包php-7.0.21-nts-Win32-VC14-x86.zip或64位包php-7.0.21-nts-Win32-VC14-x64.zip;
需要注意的是,nginx二进制码不区分Windows 32/64位;PHP组件则需下载非线程安全版本。
WNMP架构由Nginx服务器、xxfpm服务器和MariaDB服务器组成,通过各自固定的端口进行通信,具体过程如下:
默认在80号端口工作的Nginx监控进程在接到来自客户端浏览器的php页面请求后,将此请求转发给一个Nginx工作进程。再由此工作进程转发给默认工作在9000号端口的xxfpm这个PHP Fast-CGI进程管理器。再由它交给一个PHP Fast-CGI工作进程,PHP Fast-CGI工作进程将此php页面编译、运行,并通过mysqli或PDO-mysql应用层AIP与libmysql或mysqlnd底层API去与默认工作在3306端口的MariaDB数据库服务器进行数据存取。最后PHP将程序运行结果以HTML格式交给Nginx服务器,再返回给客户端浏览器进行可视化解释,使终端用户看到php网页的运行效果。WNMP架构驱动模型如图2所示。
图2 WNMP架构驱动示意图
而xxfpm监控进程则通过下面这条启动参数来一次性启动a个PHP Fast-CGI进程,并运行在9000端口上:【xxfpm.exe " php-cgi.exe -c php.ini" -n a -i 127.0.0.1 -p 9000】
xxfpm有两个功能,一个是将Nginx传递过来的php网页请求转发给PHP Fast-CGI工作进程去执行;另一个是监控PHP Fast-CGI工作进程的数量,一旦发现Windows系统中PHP Fast-CGI工作进程的数量少于最初启动的a个,它会立即补充一个新的PHP Fast-CGI工作进程,实际上是对PHP Fast-CGI进行进程维持。因为PHP Fast-CGI的设计原理是,每个工作进程默认的生命周期是执行完500个requests网页请求后便自杀,并回收内存。如果xxfpm不对PHP Fast-CGI进行进程维持,那么WNMP架构在执行完【a×500】个requests网页请求后,所有的PHP Fast-CGI都将自杀殆尽。PHP这样的设计目的是为了避免某个PHP Fast-CGI工作进程开销内存过高而使操作系统内存耗尽。通过利用Sublime编辑器进行全文扫描,发现了PHP源码php-7.0.21-src.zip包的sapicgicgi_main.c的1727行和sapifpmfpmfpm_main.c的1585行都定义了最大请求执行量为500:
1727: int max_requests = 500;
1585: int max_requests = 500;
从而确定了WNMP架构运行一段时间就必需重启才能让网站正常访问的原因。虽然PHP自身设计了php-fpm服务来进行维持PHP Fast-CGI进程,但它是为Linux操作系统设计的,在Windows上并不适用,所以,WNMP架构包必须要采用自己编写的xxfpm服务进行维持,它调用了pthreadGC2.dll,并使用CreateProcess创建进程,使用Wait For Single Object等待进程结束[4]。只有xxfpm停止时,它所创建的所有的PHP Fast-CGI子进程才能被全部关闭。但当它所创建的某个PHP Fast-CGI进程关闭后,xxfpm会立即再创建一个,从而实现进程维持,保证了WNMP架构的稳定性。
对于单服务器上的WNMP架构的性能提升,主要从六个层面着手:提高计算能力、减少I/O延时、提高算法效率、充分发挥WNMP并行计算特征、缩短PHP编译时间和提高PHP与MariaDB的存取性能。
(1) 提高计算能力。CPU越多计算能力越强,WNMP架构也越快。另外,内存至少在16 GB以上,才更好发挥WNMP架构的并行计算性能。
(2) 减少I/O延时。应当尽可以利用缓存(内存)代替直接磁盘访问。在MariaDB配置文件my.ini中,提高MariaDB数据库InnoDB存储引擎的缓存,将缓存设置为总内存大小的50%~80 %能起到很好的效果[5],下面是32 GB内存下的优化实例:
innodb_buffer_pool_size=16G
(3) 提高算法效率。优化SQL慢查询、优化PHP代码算法,有效减少CPU计算次数,能大大提高WNMP架构响应性能,从而缩短用户等待时间。
(4) 充分发挥WNMP并行计算特征。要合理设置Nginx和PHP Fast-CGI并行工作进程的数量,才能充分发挥服务器的计算性能。Nginx工作进程的数量“一般等于CPU的总核数或总核数的两倍,例如两个四核CPU,则总核数为8”[6]。
而Nginx的配置文件nginx.conf中【worker_processes n;】的参数n表示要启动多少个Nginx工作进程,n设置应当设置为服务器的CPU总核数或其两倍即可。例如,该服务器CPU总核心是8核,那么,可设置【worker_processers 8;】或【worker_processers 16;】。
假定每个静态PHP Fast-CGI最多开销30 MB内存,那么,一次性启动的静态PHP Fast-CGI的工作进程数量一般设置为【内存总MB÷30 MB】,一般取2n个,如16、32、64、128,但最多不要超过128个。这里的内存总量应当要先减去Windows操作系统开销的4 GB内存,再减去数据库开销的缓存。WNMP架构在startwnmp.bat中指定了启动PHP Fast-CGI的数量:
【xxfpm.exe " php-cgi.exe -c php.ini" -n a -i 127.0.0.1 -p 9000】
【-n a】中的a就是指PHP Fast-CGI的启动数量。
(5) 缩短PHP编译时间。应当开启OPcache缓存功能,使PHP运行效率得到大幅提高,其工作原理是:OPcache通过将 PHP 脚本预编译的字节码存储到共享内存中来提升 PHP 的性能,存储预编译字节码的好处就是省去了每次加载和解析 PHP 脚本的开销[7]。直接在php.ini中开启opcache:
zend_extension = ″C:win64_nmp
phpextphp_opcache.dll″
opcache.enable = 1
(6) 提高PHP与MariaDB的存取性能。应当使用mysqlnd底层API代替libmysql。PHP要与MariaDB数据库服务器通信,以前采用Oracle公司开发的libmysql这个底层API库,该库有版权, PHP默认不启用这个库,另一方面libmysql与Zend引擎完全独立,无法利用Zend引擎的很多特性。为了解决这个问题,Zend公司开发了mysqlnd这个本地驱动库来替代libmysql库。一方面解决了版权问题;另一方面,mysqlnd与Zend引擎高度集成,因此提供更多高级特性,如内存管理受Zend引擎管理,使得内存消耗更少;有效利用Zend进行加速,使得PHP执行速度更快。最后,使用mysqlnd驱动后,无需在服务器上必须先编译安装MariaDB然后才能编译安装PHP了,有利于分布式PHP服务器的安装和部署。mysqlnd与libmysql工作原理[8]如图3所示。
图3 mysqlnd与libmysql工作原理
使用libmysql驱动, php的编译参数:
--with-mysqli=/usr/local/mysql/bin/mysql_config
--with-pdo-mysql=/usr/local/mysql
而使用mysqlnd驱动, php的编译参数改为:
--with-mysqli=mysqlnd
--with-pdo-mysql=mysqlnd
研制成功win32_nmp和win64_nmp两个版本的WNMP架构后,在同一台服务器上对传统的WAMP和按本文优化后的WNMP架构进行了PHP计算性能评估和WNMP架构并发性能评估。
测试服务器硬件配置:CPU为Intel®Xeon®CPU E3-1230 v3 @3.3 GHz,1块CPU共4核心;内存为32 GB,DDR 3,1 600 MHz,ECC。
WAMP架构测试服务器软件配置:OS为Windows 7 64位旗舰版;Apache为1个工作进程;PHP为1个工作进程;MariaDB为innodb缓存16 GB。
WNMP架构测试服务器软件配置:OS为Windows 7 64位旗舰版;Nginx为8个工作进程;PHP为128个工作进程;MariaDB为innodb缓存16 GB。
(1) PHP计算性能评估 本测试只运行纯CPU任务脚本的基准测试(不需要I/O操作的任务,例如访问文件,网络或数据库连接)。使用的基准测试脚本bench.php和micro_bench.php来源于php-7.0.21.tar.gz的Zend目录中。如图4所示。
从测试结果看, bench.php和micro_bench.php脚本在WNMP架构上较在WAMP架构上耗时平均下降了19.5%。
(2) WNMP架构并发性能评估 本测试采用Apache的ab测试,模拟100个用户同时向WAMP和WNMP架构发起PHP连接请求,共完成10 000个连接请求。
ab测试指令:ab -c 100 -n 10000 http://服务器IP/php页面。
请求的PHP页面说明:
insert.php:向MariaDB插入一条记录;
select.php:从MariaDB数据表中查询一条记录。
图5为并发性能评估对比图。
图5 WAMP/WNMP架构并发性能评估对比图
上述测试结果表明,在WNMP架构上,通过PHP存取MariaDB数据库性能平均提升了512.5%。
本文详细介绍了WNMP架构的文件组织方式、服务器间通信方式和性能优化方法,并对PHP性能和WNMP架构采用典型方法进行了压力的测试。结果表明,这款WNMP架构较传统的WAMP架构具有便携、稳定、高并发的特点,性能接近LNMP架构。该WNMP架构不仅可作为PHP网站的开发环境,也能作为Windows服务器操作系统上的PHP网站生产环境。下一步将研制出一款更高性能的LNMP架构。本文研制的WNMP架构可在QQ群263569269的群共享中下载。
[1] 曾棕根.LAMP(PHP)程序设计[M].北京:北京大学出版社,2012:1-2.
[2] Clément Nedulcu.学习Nginx HTTP Server[M].北京:清华大学出版社,2012:218-219.
[3] 徐汉彬.PHP7和HHVM的性能之争[EB/OL].http://www.aichengxu.com/php/1879060.htm.
[4] ChinaZ源码报导.Windows下的Nginx和php搭配php-cgi.exe自动关闭退出的完美解决方法[EB/OL].http://down.chinaz.com/server/201111/1334_1.htm.
[5] 简朝阳.MySQL性能调优与架构优化[M].北京:电子工业出版社,2009:212-213.
[6] 张宴.实战Nginx:取代Apache的高性能Web服务器[M].北京:电子工业出版社,2010:23-24.
[7] php官网.OPcache简介[EB/OL].http://php.net/manual/zh/intro.opcache.php.
[8] Yeho.PHP 5.3以上版本推荐使用mysqlnd驱动[EB/OL].https://blog.linuxeye.cn/395.html.