Nginx 黑魔法:使用 NGX-PHP 模块低成本实现高性能应用
本篇文章分享一个和 Nginx 以及 PHP 有关的“黑魔法”:NGX-PHP 模块。通过这个方式,我们可以低成本的实现高性能应用,以及适合在服务器资源有限的情况下,同时体验到 Nginx 的高效以及 PHP 的灵活。
如果你对 PHP 的印象还停留在“慢”,那么或许这篇文章可以帮助你打开新世界。
写在前面
提到 “NGX 和 PHP”,使用过 Nginx 和 PHP 的同学第一反映可能是 Nginx + PHP-FPM 这种架构。不过,这篇文章中,我们要提到的技术架构更简单高效一些:直接使用 Nginx 和三方模块(NGX-PHP),调用 PHP Embedded 库,来实现原本需要跨进程实现的功能,从而明显提升应用性能。
之所以能够这样玩,需要感谢下面两个项目的相关实现:
- PHP 提供了一种有趣的调用方式:让其他的程序能够通过支持 C Bindings 的符号绑定的方式来调用它的核心引擎,Zend。这种接口调用方式,被称作 PHP SAPI 或者 PHP-Embeded,项目地址:https://github.com/php/php-class="lazy" data-src/tree/master/sapi/embed
- 2016 年,有一位来自搜狐的工程师 rryqszq4,开始在 GitHub 上尝试开源一个项目,把 “Nginx” 和 “PHP-Embeded Library” 桥接到一起,这个项目经过多年发展,陆续支持了 PHP5、PHP7,以及最新的 PHP8。项目地址:https://github.com/rryqszq4/ngx-php
在 Techem Power 的测试中,自 2020 年开始,“NGX-PHP” 这个技术选型出现之后,便取得了不错的成绩,比如:2020 年的Round 19,以及 2022 年的Round 21。
在最近的 2022 年测试中,框架开销非常低,位于排行榜第五和第六名。
如果用我们熟悉的 Node.js + MongoDB 作为基准,那么这套方案开销比它少 300%:跑的快,吃草少。换个角度来看,这个方案非常贴合 “Nginx” 和 “PHP” 的特性:快糙猛。
好了,关于这个项目的概况就介绍到这里,我们先来使用 Docker 快速、实际的感受下它的性能。
快速体验
执行下面的命令:
docker run --rm -it -v `pwd`/data:/usr/share/nginx/html/data:rw -p 8090:80 soulteary/ngx-php:8-microblog
当 Docker 镜像下载完毕之后,我们将看到一个和普通 Nginx 镜像启动无异的日志输出:
/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d//docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh10-listen-on-ipv6-by-default.sh: info: Getting the checksum of /etc/nginx/conf.d/default.conf10-listen-on-ipv6-by-default.sh: info: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf/docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh/docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh/docker-entrypoint.sh: Configuration complete; ready for start up2022/10/05 10:25:39 [notice] 1#1: using the "epoll" event method2022/10/05 10:25:39 [notice] 1#1: nginx/1.23.12022/10/05 10:25:39 [notice] 1#1: built by gcc 11.2.1 20220219 (Alpine 11.2.1_git20220219) 2022/10/05 10:25:39 [notice] 1#1: OS: Linux 5.10.76-linuxkit...
打开浏览器,输入 http://localhost:8090
,就能够看到效果啦。
随手输入一些内容,能够看到程序“跑的”还是挺快的。
在不进行应用优化、Nginx 优化的前提下,我们能够看到处理一个请求不过 2ms 左右。
接下来,我们来聊聊如何使用 NGX-PHP,学习了解这种开源方案背后的一些细节。完整的应用代码,我上传到了 soulteary/ngx-php-micro-blog,有需要可以自取。
准备工作
想要愉快的阅读和跟着本文游玩,只需要 Docker 环境,可以参考《在笔记本上搭建高性价比的 Linux 学习环境:基础篇》文章完成基础环境的准备,就不过多赘述了。
实现简单的微博应用
我们来使用“最好的语言:PHP”,实现一个简单的“微博/推特”程序。
简单实现模版类
使用 PHP “画一个”页面出来,可以用的方式非常多,最具可维护性的方式是使用”“模版”。为了不过多引入复杂性,就不使用 PHP 包管理器来为项目添加“模版引擎”了,我们来实现一个简单的模版类(不到 30 行):
class Template{ protected $dir = TEMPLATE_DIR . DIRECTORY_SEPARATOR; protected $vars = array(); public function __construct($dir = null) { if ($dir !== null) { $this->dir = $dir; } } public function render($file) { if (file_exists($this->dir . $file)) { include $this->dir . $file; } else { throw new Exception('no template file ' . $file . ' present in directory ' . $this->dir); } } public function __set($name, $value) { $this->vars[$name] = $value; } public function __get($name) { return $this->vars[$name]; }}
在完成简单的模版功能之后,我们就能够在应用中使用 new Template, template->render('template.name.html')
来进行页面结果的渲染了。
简单实现主要逻辑
接下来,我们来实现“微博”的主要流程逻辑,大概 130 行左右的代码就能够搞定:
class Whisper{ public function __construct() { if (empty($_POST['content'])) { $start_time = microtime(true); $page = 1; if (!empty($_GET['p'])) { $page = (int) filter_var($_GET['p'], FILTER_SANITIZE_NUMBER_INT); if ($page < 1) { $page = 1; } } $tpl = new Template(); $tpl->data = $this->loadData($page); ob_start(); $tpl->render('main.html'); ob_end_flush(); $end_time = microtime(true); echo "\n"; } else { $content = trim($_POST['content']); if (strlen($content) == 0) { echo ERROR_IS_EMPTY; exit; } $content = (string) filter_var($content, FILTER_SANITIZE_SPECIAL_CHARS); $this->postWhisper($content); } } private function postWhisper($content) { $date = date('Y-m-d g:i:s A'); $filename = DATA_DIR . DIRECTORY_SEPARATOR . date('YmdHis') . ".txt"; $file = fopen($filename, "w+"); $content = $date . "\n" . $content . "\n"; fwrite($file, $content); fclose($file); header("location: /"); } private function loadData($page) { $result = [ 'whispers' => [], 'pagination' => ['hide' => true], ]; $files = []; if ($handle = @opendir(DATA_DIR)) { while ($file = readdir($handle)) { if (!is_dir($file)) { $files[] = $file; } } } rsort($files); $total = sizeof($files); if ($total == 0) { return $result; } $page = $page - 1; $start = $page * WHISPER_PER_PAGE; if (($start + WHISPER_PER_PAGE) > $total) { $last = $total; } else { $last = $start + WHISPER_PER_PAGE; } for ($i = $start; $i < $last; $i++) { $raw = file(DATA_DIR . DIRECTORY_SEPARATOR . $files[$i]); $date = trim($raw[0]); unset($raw[0]); $content = ""; foreach ($raw as $value) { $content .= $value; } $data = array( 'date' => $date, 'content' => $content, ); $result['whispers'][] = $data; } $result['pagination'] = $this->getPagination($start, $last, $page, $total); return $result; } private function getPagination($start, $last, $page, $total) { if ($total <= WHISPER_PER_PAGE) { return ['hide' => true]; } $page = $page + 1; $next = 0; $prev = 0; if ($start == 0) { if ($last < $total) { $next = $page + 1; } } else { if ($last < $total) { $next = $page + 1; $prev = $page - 1; } else { $prev = $page - 1; } } return [ 'hide' => false, 'prev' => $prev, 'next' => $next, 'page' => $page, 'last' => ceil($total / 5), ]; }}new Whisper();
简单实现页面模版
完成主要程序实现之后,我们来实现页面模版,大概 120 行就能够搞定:
DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link rel="stylesheet" href="assets/css/bootstrap.min.css"> <link rel="stylesheet" href="assets/css/style.css"> <title>Whispertitle>head><body> <div id="root"> <div class="container" id="brand-container"> <div class="row"> <div class="py-4 text-center"> <h2> <span>Whisperspan> <img class="logo" class="lazy" data-src="assets/img/logo.svg" alt="" width="72" height="57" /> h2> <p class="lead">a simplest example.p> div> div> div> <div class="container" id="post-container"> <div class="row"> <div class="col-xs-12"> <h4 class="mb-3"># Post a Whisperh4> <form class="mb-3" action="/" method="post" novalidate > <div class="row g-3"> <div class="col-12"> <textarea class="form-control mb-2" placeholder="enter content here..." name="content" rows="4" required>textarea> <button class="w-100 btn btn-primary" type="submit">Postbutton> div> div> form> div> div> div> <div class="container" id="whispers-container"> data['pagination']['hide']):?> <div class="row row-cols-sm-auto"> <div class="col-sm-9"> <hr class="my-3 w-100" /> div> <div class="col-sm-3"> <div aria-label="Page navigation" id="pagination"> <ul class="pagination justify-content-center pagination-sm"> data['pagination']['prev']==0):?> <li class="page-item disabled"> <a class="page-link">Previousa> li> <li class="page-item"> <a class="page-link" href="?p==$this->data['pagination']['prev']?>">Previousa> li> data['pagination']['next']==0):?> <li class="page-item disabled"> <a class="page-link">Nexta> li> <li class="page-item"> <a class="page-link" href="?p==$this->data['pagination']['next']?>">Nexta> li> ul> div> div> div> data['whispers'])>0):?> <div class="row row-cols-sm-auto"> <h4 class="col-sm-12"> <span># Listspan> data['pagination']['hide']):?> <span class="text-muted" id="page-info">Page #=$this->data['pagination']['page']?> / =$this->data['pagination']['last']?>span> h4> div> <div class="row"> <div class="col-xs-12"> <ul class="list-group w-auto"> data['whispers'] as $whisper): ?> <li class="list-group-item d-flex gap-3 py-3" aria-current="true"> <img class="lazy" data-src="assets/img/icon.svg" width="32" height="32" class="rounded-circle flex-shrink-0"> <div class="d-flex gap-2 w-100 justify-content-between"> <div> <h6 class="mb-0 whisper-content">=$whisper['content']?>h6> <p class="mb-0 opacity-75">=$whisper['date']?>p> div> <small class="opacity-50 text-nowrap timeago" data-value="=$whisper['date']?>">small> div> li> ul> div> div> div> <div class="container"> <div class="row"> <div class="my-3 pt-3 text-muted text-center text-small"> <ul class="list-inline mb-1"> <li class="list-inline-item">© 2007–=date('Y');?> @soulteary: <a href="https://soulteary.com/" target="_blank">Bloga>li> <li class="list-inline-item"><a href="https://www.zhihu.com/people/soulteary" target="_blank">Zhihua>li> <li class="list-inline-item"><a href="https://weibo.com/u/1220149481" target="_blank">Weiboa>li> ul> div> div> div> div> <script class="lazy" data-src="assets/js/main.js">script>body>html>
使用 PHP 官方镜像验证程序
为了方便后续的演示和性能对比,这里我们直接声明一些路径为 Nginx 容器的地址,所以当你看到后续 Apache 镜像中使用的路径,不必惊讶:
date_default_timezone_set('Asia/shanghai');define('TEMPLATE_DIR', '/usr/share/nginx/html/templates');define('DATA_DIR', '/usr/share/nginx/html/data');define('WHISPER_PER_PAGE', 5);...
在完成程序调整之后,我们简单编写一个 compose
配置,来使用 PHP 官方提供的 Docker 镜像来验证程序是否能够正常运行:
version: '3'services: talk: image: php:8.1.10-apache-buster restart: always ports: - 8090:80 volumes: - ./app:/var/www/html/ - ./app/templates:/usr/share/nginx/html/templates:rw - ./app/data:/usr/share/nginx/html/data:rw
使用 docker-compose up
启动程序之后,我们访问 http://localhost:8090
就能够看到文章一开头的界面了。随便输入点内容,然后点击“发布” 按钮,能够看到一切符合预期,功能可以正常工作。
确认程序能够正常工作后,我们来将程序迁移到 NGX-PHP 环境中。
这部分的代码,可以参考项目的提交记录:soulteary/ngx-php-micro-blog/commit/d97385b945c998385cbc7dee813529f05b4f15d3
构建 NGX PHP 容器镜像
这里,我们借助很早之前提到过的一个项目 https://github.com/nginx-with-docker/nginx-docker-playground 来快速完成 NGX PHP 这个 Nginx 模块的构建,关于“如何在容器时代高效使用 Nginx 三方模块”,可以参考这篇文章。
# Nginx Docker Playground (https://github.com/nginx-with-docker/nginx-docker-playground)FROM soulteary/prebuilt-nginx-modules:base-1.23.1-alpine AS BuilderRUN sed -i -E "s/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g" /etc/apk/repositoriesRUN apk update && apk --no-cache add curl gcc g++ make musl-dev linux-headers gd-dev geoip-dev libxml2-dev libxslt-dev openssl-dev pcre-dev perl-dev pkgconf zlib-dev libedit-dev ncurses-dev php8-dev php8-embed git unzip argon2-devENV PHP_LIB=/usr/libWORKDIR /usr/class="lazy" data-src# Nginx Development Kit (https://github.com/vision5/ngx_devel_kit)ARG DEVEL_KIT_MODULE_CHECKSUM=e15316e13a7b19a3d2502becbb26043a464a135aARG DEVEL_KIT_VERSION=0.3.1ARG DEVEL_KIT_NAME=ngx_devel_kitRUN curl -L "https://github.com/vision5/ngx_devel_kit/archive/v${DEVEL_KIT_VERSION}.tar.gz" -o "v${DEVEL_KIT_VERSION}.tar.gz" && \ echo "${DEVEL_KIT_MODULE_CHECKSUM} v${DEVEL_KIT_VERSION}.tar.gz" | shasum -c && \ tar -zxC /usr/class="lazy" data-src -f v${DEVEL_KIT_VERSION}.tar.gz && \ mv ${DEVEL_KIT_NAME}-${DEVEL_KIT_VERSION}/ ${DEVEL_KIT_NAME}# Nginx PHP Module (Mirror https://github.com/nginx-with-docker/ngx_http_php_module-class="lazy" data-src)ARG MODULE_CHECKSUM=aeef775b2beb8378cb295a4da29b80d98274e1faARG MODULE_VERSION=masterARG MODULE_NAME=ngx_http_php_module-class="lazy" data-srcARG MODULE_SOURCE=https://github.com/nginx-with-docker/ngx_http_php_module-class="lazy" data-srcRUN curl -L "${MODULE_SOURCE}/archive/refs/heads/${MODULE_VERSION}.zip" -o "v${MODULE_VERSION}.zip" && \ echo "${MODULE_CHECKSUM} v${MODULE_VERSION}.zip" | shasum -c && \ unzip "v${MODULE_VERSION}.zip" && \ mv "$MODULE_NAME-$MODULE_VERSION" "$MODULE_NAME"# NginxRUN cd /usr/class="lazy" data-src/nginx && \ CONFARGS=$(nginx -V 2>&1 | sed -n -e 's/^.*arguments: //p') \ CONFARGS=${CONFARGS/-Os -fomit-frame-pointer -g/-Os} && \ echo $CONFARGS && \ ./configure --with-compat $CONFARGS --with-ld-opt="-Wl,-rpath,${PHP_LIB}" --add-dynamic-module=../${DEVEL_KIT_NAME} --add-dynamic-module=../${MODULE_NAME} && \ make modules
完成构建之后,我们使用多阶段构建,制作最终的应用镜像就好了:
FROM nginx:1.23.1-alpineLABEL MAINTAINER=soulteary@gmail.comCOPY --from=Builder /usr/lib/libphp.so /usr/lib/COPY --from=Builder /usr/lib/libargon2.so.1 /usr/lib/COPY --from=Builder /lib/libz.so.1 /lib/COPY --from=Builder /etc/php8/php.ini /etc/php8/COPY --from=Builder /usr/class="lazy" data-src/nginx/objs/ndk_http_module.so /etc/nginx/modules/COPY --from=Builder /usr/class="lazy" data-src/nginx/objs/ngx_http_php_module.so /etc/nginx/modules/ENV PHP_LIB=/usr/libCOPY conf/nginx.conf /etc/nginx/COPY app/index.php /usr/share/nginx/html/COPY app/assets /usr/share/nginx/html/assetsCOPY app/templates /usr/share/nginx/html/templatesRUN mkdir -p /usr/share/nginx/html/data && \ chown nginx:nginx /usr/share/nginx/html/data && \ chmod 777 /usr/share/nginx/html/data
这个小节的完整代码,在这里可以找到: soulteary/ngx-php-micro-blog/Dockerfile。使用 docker build -t soulteary/ngx-php:8-microblog .
,完成基础镜像构建,我们将得到一个 12MB 左右的小巧的、包含了 Nginx PHP 模块的镜像。
在完成了基础镜像构建之后,我们来进行程序的“改造”。
将 PHP 程序适配 NGX PHP 环境
如果我们不修改任何代码,通过调整 docker compose
配置文件,切换容器镜像和挂载的文件,是可以让程序在我们新构建的 NGX-PHP 镜像中运行的。
version: '3'services: talk: image: soulteary/ngx-php:8-microblog restart: always ports: - 8090:80 volumes: - ./app/data:/usr/share/nginx/html/data:rw - ./app/index.php:/usr/share/nginx/html/index.php:ro - ./app/templates:/usr/share/nginx/html/templates:ro - ./app/assets:/usr/share/nginx/html/assets:ro
但是我们会得到一些报错,导致程序不能正常运行。
解决变量、函数重复定义的问题
我们首先可能遇到的问题就是类似下面的报错,告诉我们重复声明了“某些内容”,比如常量:
Warning: Constant TEMPLATE_DIR already defined in /usr/share/nginx/html/index.php on line 4
或者重复声明了“某些类”:
Fatal error: Cannot declare class Template, because the name is already in use in /usr/share/nginx/html/index.php on line 18
出现这两个问题的原因,是因为 NGX PHP 模块中,“全局变量和静态变量”都是不安全的。
解决第一个问题,我们可以有两个方案,降低声明的作用域,或者加上一些防御性判断:
defined('TEMPLATE_DIR') or define('TEMPLATE_DIR', '/usr/share/nginx/html/templates');
解决第二个问题,我们只能够依赖添加判断来避免重复声明:
if (!class_exists('Template')) { class Template { // ... }}
解决完毕上面两个问题,程序就能够正常展示界面了。
解决参数获取不到的问题
虽然解决了上面的问题,程序能够正常展示,但是我们会发现提交任何内容,程序都不会有“正确的反应”,而 Nginx 日志中也没有任何错误信息。
出现这个问题的原因是,在 NGX PHP 环境下,PHP 获取用户提交数据的方式由 $_GET
和 $_POST
改为了 ngx_query_args()
和 ngx_post_args()
。
为了解决这个问题,并且保持我们的程序依旧能够在官方 PHP 环境中运行、调试,可以实现一个简单的 getArgs
方法,让程序兼容不同的环境:
private function getArgs($key, $method){ $dataSource = null; $isNginxEnv = false; if ($method == 'GET') { if (function_exists('ngx_query_args')) { $dataSource = ngx_query_args(); $isNginxEnv = true; } else { $dataSource = $_GET; } } else { if (function_exists('ngx_post_args')) { $dataSource = ngx_post_args(); $isNginxEnv = true; } else { $dataSource = $_POST; } } if (!isset($dataSource[$key])) { return ""; } return $isNginxEnv ? trim(urldecode($dataSource[$key])) : trim($dataSource[$key]);}
对应的,调整上文中程序获取用户输入数据的方法,就能够让程序正常的在 NGX PHP 容器中运行啦。
最终应用程序
最终的应用程序,算上换行大概 220 行左右:
date_default_timezone_set('Asia/shanghai');defined('TEMPLATE_DIR') or define('TEMPLATE_DIR', '/usr/share/nginx/html/templates');defined('DATA_DIR') or define('DATA_DIR', '/usr/share/nginx/html/data');defined('WHISPER_PER_PAGE') or define('WHISPER_PER_PAGE', 5);defined('ERROR_IS_EMPTY') or define('ERROR_IS_EMPTY', '内容不能为空');if (defined('DATA_DIR')) { if (!file_exists(DATA_DIR)) { mkdir(DATA_DIR); }} else { echo "需要定义数据目录"; exit;}if (!class_exists('Template')) { class Template { protected $dir = TEMPLATE_DIR . DIRECTORY_SEPARATOR; protected $vars = array(); public function __construct($dir = null) { if ($dir !== null) { $this->dir = $dir; } } public function render($file) { if (file_exists($this->dir . $file)) { include $this->dir . $file; } else { throw new Exception('no template file ' . $file . ' present in directory ' . $this->dir); } } public function __set($name, $value) { $this->vars[$name] = $value; } public function __get($name) { return $this->vars[$name]; } }}if (!class_exists('Whisper')) { class Whisper { private function getArgs($key, $method) { $dataSource = null; $isNginxEnv = false; if ($method == 'GET') { if (function_exists('ngx_query_args')) { $dataSource = ngx_query_args(); $isNginxEnv = true; } else { $dataSource = $_GET; } } else { if (function_exists('ngx_post_args')) { $dataSource = ngx_post_args(); $isNginxEnv = true; } else { $dataSource = $_POST; } } if (!isset($dataSource[$key])) { return ""; } return $isNginxEnv ? trim(urldecode($dataSource[$key])) : trim($dataSource[$key]); } private function redir($url) { if (function_exists('ngx_header_set')) { ngx_header_set("Location", $url); ngx_exit(NGX_HTTP_MOVED_TEMPORARILY); } else { header("Location: " . $url); } } public function __construct() { $content = $this->getArgs('content', 'POST'); if (empty($content)) { $start_time = microtime(true); $page = 1; $page = $this->getArgs('p', 'GET'); if (!empty($page)) { $page = (int) filter_var($page, FILTER_SANITIZE_NUMBER_INT); if ($page < 1) { $page = 1; } } else { $page = 1; } $tpl = new Template(); $tpl->data = $this->loadData($page); ob_start(); $tpl->render('main.html'); ob_end_flush(); $end_time = microtime(true); echo "\n"; } else { $content = htmlentities((string) filter_var($content, FILTER_SANITIZE_SPECIAL_CHARS)); $this->postWhisper($content); } } private function postWhisper($content) { $date = date('Y-m-d g:i:s A'); $filename = DATA_DIR . DIRECTORY_SEPARATOR . date('YmdHis') . ".txt"; $file = fopen($filename, "w+"); $content = $date . "\n" . $content . "\n"; fwrite($file, $content); fclose($file); $this->redir("/"); } private function loadData($page) { $result = [ 'whispers' => [], 'pagination' => ['hide' => true], ]; $files = []; if ($handle = @opendir(DATA_DIR)) { while ($file = readdir($handle)) { if (!is_dir($file)) { $files[] = $file; } } } rsort($files); $total = sizeof($files); if ($total == 0) { return $result; } $page = $page - 1; $start = $page * WHISPER_PER_PAGE; if (($start + WHISPER_PER_PAGE) > $total) { $last = $total; } else { $last = $start + WHISPER_PER_PAGE; } for ($i = $start; $i < $last; $i++) { $raw = file(DATA_DIR . DIRECTORY_SEPARATOR . $files[$i]); $date = trim($raw[0]); unset($raw[0]); $content = ""; foreach ($raw as $value) { $content .= $value; } $data = array( 'date' => $date, 'content' => html_entity_decode($content), ); $result['whispers'][] = $data; } $result['pagination'] = $this->getPagination($start, $last, $page, $total); return $result; } private function getPagination($start, $last, $page, $total) { if ($total <= WHISPER_PER_PAGE) { return ['hide' => true]; } $page = $page + 1; $next = 0; $prev = 0; if ($start == 0) { if ($last < $total) { $next = $page + 1; } } else { if ($last < $total) { $next = $page + 1; $prev = $page - 1; } else { $prev = $page - 1; } } return [ 'hide' => false, 'prev' => $prev, 'next' => $next, 'page' => $page, 'last' => ceil($total / 5), ]; } }}new Whisper();
简单的性能比较
除了相信相对中立的机构的测试结果之外,我们也可以自己进行应用性能测试,来验证 NGX-PHP 是否真的能够“降本增效”。
下面我们就用上面最终实现好的程序,分别在我们构建的 soulteary/ngx-php:8
镜像和 PHP 官方镜像 php:8.1.10-apache-buster
中进行简单的请求性能测试:
先使用开启 OPCACHE 之后的官方镜像(php:8.1.10-apache-buster
),完成30s 的压力测试:
wrk -t16 -c 100 -d 30s http://127.0.0.1:8090Running 30s test @ http://127.0.0.1:8090 16 threads and 100 connections Thread Stats Avg Stdev Max +/- Stdev Latency 132.92ms 158.49ms 1.98s 86.67% Req/Sec 54.38 56.83 670.00 94.88% 22603 requests in 30.08s, 49.40MB read Socket errors: connect 0, read 0, write 0, timeout 112Requests/sec: 751.53Transfer/sec: 1.64MB
接着,使用我们构建好的 NGX PHP 镜像,在不开启缓存的情况下进行测试:
wrk -t16 -c 100 -d 30s http://127.0.0.1:8090Running 30s test @ http://127.0.0.1:8090 16 threads and 100 connections Thread Stats Avg Stdev Max +/- Stdev Latency 94.01ms 15.94ms 431.01ms 81.62% Req/Sec 64.02 11.33 148.00 74.26% 30715 requests in 30.09s, 65.03MB readRequests/sec: 1020.65Transfer/sec: 2.16MB
可以看到,性能提升还是比较明显的。
最后
好了,关于 NGX PHP 的第一篇文章就聊到这里吧。关于更多的细节,或许后面有机会,我会再写一两篇文章进行分享。
–EOF
本文使用「署名 4.0 国际 (CC BY 4.0)」许可协议,欢迎转载、或重新修改使用,但需要注明来源。 署名 4.0 国际 (CC BY 4.0)
本文作者: 苏洋
创建时间: 2022年10月05日
统计字数: 16668字
阅读时间: 34分钟阅读
本文链接: https://soulteary.com/2022/10/05/nginx-black-magic-low-cost-high-performance-applications-using-ngx-php-modules.html
来源地址:https://blog.csdn.net/soulteary/article/details/127176248
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341