我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Nginx 黑魔法:使用 NGX-PHP 模块低成本实现高性能应用

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

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

2020 年和 2022 年的两轮框架评分测试

在最近的 2022 年测试中,框架开销非常低,位于排行榜第五和第六名。

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 ,就能够看到效果啦。

2022 年测试中,框架开销排行

随手输入一些内容,能够看到程序“跑的”还是挺快的。

发一些只有自己看的到的“微博”

在不进行应用优化、Nginx 优化的前提下,我们能够看到处理一个请求不过 2ms 左右。

单次请求服务端处理时间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=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=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 #data['pagination']['page']?> / 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">h6>                  <p class="mb-0 opacity-75">p>                div>                <small class="opacity-50 text-nowrap timeago" data-value="'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– @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

Nginx 黑魔法:使用 NGX-PHP 模块低成本实现高性能应用

下载Word文档到电脑,方便收藏和打印~

下载Word文档

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录