使用 Nginx Unit 替代 PHP-FPM
简介⌗
Nginx Unit 是由 Nginx 团队开发的又一力作,可能没有人比他们更了解 Nginx 已经 Unit 如何平衡性能和可用性。
没错,Unit 最重要的特性就是 Flexibility
,你可以使用 RESTful API 动态的来修改 Unit 的运行配置。在更新配置时,只要你清楚的知道自己在做什么的话,可以保证服务的零中断。
最主要的是它实现了 SAPI,可以取代 PHP-FPM 在 PHP 中重要的位置,在网上看到几年前的测评文章,对比 PHP-FPM 有不少性能优势。
所以我打算使用 Nginx Unit + PHP 8.1,在做一组性能测试对比。
构建运行环境⌗
我这里采用 Docker 来构建和部署 Unit 服务,在 Dockerfile 中我安装了 MongoDB 扩展和 PHPRedis 扩展,均采用最新版本。
Dockerfile
FROM php:8.1-cli as BUILDER
LABEL maintainer="NGINX Docker Maintainers <[email protected]>"
RUN set -ex \
&& apt-get update \
&& apt-get install --no-install-recommends --no-install-suggests -y ca-certificates mercurial build-essential libssl-dev libpcre2-dev \
&& mkdir -p /usr/lib/unit/modules /usr/lib/unit/debug-modules \
&& hg clone https://hg.nginx.org/unit \
&& cd unit \
&& hg up 1.26.1 \
&& NCPU="$(getconf _NPROCESSORS_ONLN)" \
&& DEB_HOST_MULTIARCH="$(dpkg-architecture -q DEB_HOST_MULTIARCH)" \
&& CC_OPT="$(DEB_BUILD_MAINT_OPTIONS="hardening=+all,-pie" DEB_CFLAGS_MAINT_APPEND="-Wp,-D_FORTIFY_SOURCE=2 -fPIC" dpkg-buildflags --get CFLAGS)" \
&& LD_OPT="$(DEB_BUILD_MAINT_OPTIONS="hardening=+all,-pie" DEB_LDFLAGS_MAINT_APPEND="-Wl,--as-needed -pie" dpkg-buildflags --get LDFLAGS)" \
&& CONFIGURE_ARGS="--prefix=/usr \
--state=/var/lib/unit \
--control=unix:/var/run/control.unit.sock \
--pid=/var/run/unit.pid \
--log=/var/log/unit.log \
--tmp=/var/tmp \
--user=unit \
--group=unit \
--openssl \
--libdir=/usr/lib/$DEB_HOST_MULTIARCH" \
&& ./configure $CONFIGURE_ARGS --cc-opt="$CC_OPT" --ld-opt="$LD_OPT" --modules=/usr/lib/unit/debug-modules --debug \
&& make -j $NCPU unitd \
&& install -pm755 build/unitd /usr/sbin/unitd-debug \
&& make clean \
&& ./configure $CONFIGURE_ARGS --cc-opt="$CC_OPT" --ld-opt="$LD_OPT" --modules=/usr/lib/unit/modules \
&& make -j $NCPU unitd \
&& install -pm755 build/unitd /usr/sbin/unitd \
&& make clean \
&& ./configure $CONFIGURE_ARGS --cc-opt="$CC_OPT" --modules=/usr/lib/unit/debug-modules --debug \
&& ./configure php \
&& make -j $NCPU php-install \
&& make clean \
&& ./configure $CONFIGURE_ARGS --cc-opt="$CC_OPT" --modules=/usr/lib/unit/modules \
&& ./configure php \
&& make -j $NCPU php-install \
&& ldd /usr/sbin/unitd | awk '/=>/{print $(NF-1)}' | while read n; do dpkg-query -S $n; done | sed 's/^\([^:]\+\):.*$/\1/' | sort | uniq > /requirements.apt
FROM php:8.1-cli
COPY docker-entrypoint.sh /usr/local/bin/
COPY --from=BUILDER /usr/sbin/unitd /usr/sbin/unitd
COPY --from=BUILDER /usr/sbin/unitd-debug /usr/sbin/unitd-debug
COPY --from=BUILDER /usr/lib/unit/ /usr/lib/unit/
COPY --from=BUILDER /requirements.apt /requirements.apt
RUN ldconfig
RUN set -x \
&& mkdir -p /var/lib/unit/ \
&& mkdir /docker-entrypoint.d/ \
&& addgroup --system unit \
&& adduser \
--system \
--disabled-login \
--ingroup unit \
--no-create-home \
--home /nonexistent \
--gecos "unit user" \
--shell /bin/false \
unit \
&& apt-get update \
&& apt-get upgrade -y \
&& apt-get --no-install-recommends --no-install-suggests -y install git libzip-dev zip openssl libssl-dev libcurl4-openssl-dev curl $(cat /requirements.apt) \
&& docker-php-ext-install zip \
&& pecl install lzf \
&& pecl install igbinary \
&& pecl install redis \
&& pecl install mongodb \
&& docker-php-ext-enable lzf igbinary redis mongodb \
&& docker-php-ext-install pcntl \
&& docker-php-ext-install pdo_mysql \
&& docker-php-ext-install opcache \
&& docker-php-ext-install exif \
&& apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* \
&& rm -f /requirements.apt \
&& rm /var/log/lastlog /var/log/faillog \
&& ln -sf /dev/stdout /var/log/unit.log
# Install Composer
RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" && \
EXPECTED_CHECKSUM="$(php -r 'copy("https://composer.github.io/installer.sig", "php://stdout");')" && \
php -r "if (hash_file('sha384', 'composer-setup.php') === '$EXPECTED_CHECKSUM') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;" && \
php composer-setup.php && \
php -r "unlink('composer-setup.php');" && \
mv composer.phar /usr/local/bin/composer && \
chmod +x /usr/local/bin/composer
# Configure locale.
ARG LOCALE=POSIX
ENV LC_ALL ${LOCALE}
STOPSIGNAL SIGTERM
ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
CMD ["unitd", "--no-daemon", "--control", "unix:/var/run/control.unit.sock"]
docker-compose.yml
version: "3.8"
services:
unit:
image: unit/php:laravel
build:
context: .
dockerfile: Dockerfile
working_dir: /usr/wwwroot/projects
restart: always
hostname: unit
container_name: unit
ports:
- 80:80
- 443:443
networks:
- services
volumes:
- ./projects:/usr/wwwroot/projects:rw
- ./services/php/8.1/etc:/usr/local/etc
networks:
services:
name: services
ipam:
driver: default
config:
- subnet: 10.0.8.0/24
projects/phpinfo/index.php
<?php
phpinfo();
打包镜像⌗
完成配置文件后的目录结构如下:
$ tree -L 3
.
├── Dockerfile
├── docker-compose.yml
├── docker-entrypoint.sh
├── php
│ └── etc
│ ├── pear.conf
│ └── php
└── projects
└── phpinfo
└── index.php
15 directories, 12 files
我已经将项目放到了 Github 上,需要的可以自行克隆。
$ docker-compose up -d
配置 Unit⌗
{
"listeners": {
"*:80": {
"pass": "routes"
}
},
"routes": [
{
"match": {
"uri": "!/index.php"
},
"action": {
"share": "/usr/wwwroot/projects/phpinfo$uri",
"fallback": {
"pass": "applications/laravel"
}
}
}
],
"applications": {
"laravel": {
"type": "php",
"processes": {
"max": 120,
"spare": 5,
"idle_timeout": 20
},
"root": "/usr/wwwroot/projects/phpinfo/",
"script": "index.php"
}
}
}
将上面的 JSON 对象压缩为字符串,然后执行下面命令将配置文件发送给 Unit。
$ docker exec -it laravel curl -X PUT -d '{"listeners":{"*:80":{"pass":"routes"}},"routes":[{"match":{"uri":"!/index.php"},"action":{"share":"/usr/wwwroot/projects/phpinfo$uri","fallback":{"pass":"applications/laravel"}}}],"applications":{"laravel":{"type":"php","processes":{"max":120,"spare":5,"idle_timeout":20},"root":"/usr/wwwroot/projects/phpinfo/","script":"index.php"}}}' --unix-socket /var/run/control.unit.sock http://localhost/config/
执行成功的话会返回:
{
"success": "Reconfiguration done."
}
然后访问 http://localhost 就可以打开 phpinfo 的页面了。
性能对比⌗
硬件配置⌗
4核心/8Gb 网络 100Mbps 的带宽峰值,磁盘是 ESSD 40G(2280 IOPS)。
服务配置⌗
压测 Unit + PHP 8.1.1⌗
ab -n 10000 -c 20 "http://47.100.202.147:8080/"
Server Software: Unit/1.26.1
Server Hostname: 47.100.202.147
Server Port: 8080
Document Path: /
Document Length: 17592 bytes
Concurrency Level: 20
Time taken for tests: 73.888 seconds
Complete requests: 10000
Failed requests: 0
Total transferred: 187050000 bytes
HTML transferred: 175920000 bytes
Requests per second: 135.34 [#/sec] (mean)
Time per request: 147.775 [ms] (mean)
Time per request: 7.389 [ms] (mean, across all concurrent requests)
Transfer rate: 2472.22 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 6 9 1.3 9 24
Processing: 17 136 716.7 34 5985
Waiting: 10 127 716.7 25 5977
Total: 24 145 716.7 43 5992
Percentage of the requests served within a certain time (ms)
50% 43
66% 48
75% 51
80% 53
90% 57
95% 62
98% 90
99% 5230
100% 5992 (longest request)
ab -n 10000 -c 50 "http://47.100.202.147:8080/"
Server Software: Unit/1.26.1
Server Hostname: 47.100.202.147
Server Port: 8080
Document Path: /
Document Length: 17592 bytes
Concurrency Level: 50
Time taken for tests: 61.516 seconds
Complete requests: 10000
Failed requests: 0
Total transferred: 187050000 bytes
HTML transferred: 175920000 bytes
Requests per second: 162.56 [#/sec] (mean)
Time per request: 307.580 [ms] (mean)
Time per request: 6.152 [ms] (mean, across all concurrent requests)
Transfer rate: 2969.41 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 6 10 4.4 9 151
Processing: 20 285 1350.1 99 11801
Waiting: 12 272 1350.4 85 11792
Total: 28 295 1350.1 109 11810
Percentage of the requests served within a certain time (ms)
50% 109
66% 119
75% 127
80% 132
90% 145
95% 156
98% 198
99% 10018
100% 11810 (longest request)
ab -n 10000 -c 100 "http://47.100.202.147:8080/"
Server Software: Unit/1.26.1
Server Hostname: 47.100.202.147
Server Port: 8080
Document Path: /
Document Length: 17592 bytes
Concurrency Level: 100
Time taken for tests: 57.325 seconds
Complete requests: 10000
Failed requests: 0
Total transferred: 187050000 bytes
HTML transferred: 175920000 bytes
Requests per second: 174.45 [#/sec] (mean)
Time per request: 573.247 [ms] (mean)
Time per request: 5.732 [ms] (mean, across all concurrent requests)
Transfer rate: 3186.52 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 6 14 10.7 10 79
Processing: 22 515 2178.1 210 18381
Waiting: 13 493 2179.3 190 18364
Total: 31 528 2177.9 223 18394
Percentage of the requests served within a certain time (ms)
50% 223
66% 254
75% 266
80% 273
90% 293
95% 305
98% 365
99% 16907
100% 18394 (longest request)
压测 Nginx + PHP-FPM + PHP 8.1.1⌗
ab -n 10000 -c 20 "http://47.100.202.147:8081/"
Server Software: nginx
Server Hostname: 47.100.202.147
Server Port: 8081
Document Path: /
Document Length: 17592 bytes
Concurrency Level: 20
Time taken for tests: 76.832 seconds
Complete requests: 10000
Failed requests: 0
Total transferred: 187160000 bytes
HTML transferred: 175920000 bytes
Requests per second: 130.15 [#/sec] (mean)
Time per request: 153.663 [ms] (mean)
Time per request: 7.683 [ms] (mean, across all concurrent requests)
Transfer rate: 2378.88 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 6 9 1.2 9 27
Processing: 18 141 724.5 39 6025
Waiting: 11 132 724.5 30 6016
Total: 24 150 724.5 48 6035
Percentage of the requests served within a certain time (ms)
50% 48
66% 52
75% 54
80% 56
90% 61
95% 66
98% 95
99% 5314
100% 6035 (longest request)
ab -n 10000 -c 50 "http://47.100.202.147:8081/"
Server Software: nginx
Server Hostname: 47.100.202.147
Server Port: 8081
Document Path: /
Document Length: 17592 bytes
Concurrency Level: 50
Time taken for tests: 80.285 seconds
Complete requests: 10000
Failed requests: 0
Total transferred: 187160000 bytes
HTML transferred: 175920000 bytes
Requests per second: 124.56 [#/sec] (mean)
Time per request: 401.426 [ms] (mean)
Time per request: 8.029 [ms] (mean, across all concurrent requests)
Transfer rate: 2276.55 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 6 9 3.9 9 177
Processing: 19 375 1973.8 100 16940
Waiting: 11 362 1974.2 88 16931
Total: 26 384 1973.8 109 16950
Percentage of the requests served within a certain time (ms)
50% 109
66% 124
75% 134
80% 139
90% 150
95% 162
98% 331
99% 15107
100% 16950 (longest request)
ab -n 10000 -c 100 "http://47.100.202.147:8081/"
Server Software: nginx
Server Hostname: 47.100.202.147
Server Port: 8081
Document Path: /
Document Length: 17592 bytes
Concurrency Level: 100
Time taken for tests: 94.577 seconds
Complete requests: 10000
Failed requests: 0
Total transferred: 187160000 bytes
HTML transferred: 175920000 bytes
Requests per second: 105.73 [#/sec] (mean)
Time per request: 945.768 [ms] (mean)
Time per request: 9.458 [ms] (mean, across all concurrent requests)
Transfer rate: 1932.54 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 6 13 9.7 9 114
Processing: 22 862 4350.7 226 35655
Waiting: 13 842 4352.0 207 35643
Total: 30 875 4350.6 239 35663
Percentage of the requests served within a certain time (ms)
50% 239
66% 277
75% 297
80% 307
90% 329
95% 346
98% 19222
99% 32654
100% 35663 (longest request)
服务 | 并发数 | Requests per second | Time per request |
---|---|---|---|
Unit+PHP 8.1 | 20 | 135.34 | 7.389 |
Unit+PHP 8.1 | 50 | 162.56 | 6.152 |
Unit+PHP 8.1 | 100 | 174.45 | 5.732 |
Nginx+PHP-FPM | 20 | 130.15 | 7.683 |
Nginx+PHP-FPM | 40 | 124.56 | 8.029 |
Nginx+PHP-FPM | 100 | 105.73 | 9.458 |
从上面的数据来看,性能并没有想象中差距那么大,但是 Unit 却随着并发数的增加,性能呈现上升趋势,这个就有点意思了,难道这点并发不足以喂饱 Unit。
本地测试⌗
考虑到肯能是我本地网络原因,所以我又直接在服务器上进行本地测试:
ab -n 10000 -c 100 "http://127.0.0.1:8080/"
Server Software: Unit/1.26.1
Server Hostname: 127.0.0.1
Server Port: 8080
Document Path: /
Document Length: 17592 bytes
Concurrency Level: 100
Time taken for tests: 99.609 seconds
Complete requests: 10000
Failed requests: 0
Total transferred: 187050000 bytes
HTML transferred: 175920000 bytes
Requests per second: 100.39 [#/sec] (mean)
Time per request: 996.091 [ms] (mean)
Time per request: 9.961 [ms] (mean, across all concurrent requests)
Transfer rate: 1833.83 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.1 0 2
Processing: 4 914 5118.6 240 43107
Waiting: 4 912 5118.5 238 43107
Total: 4 914 5118.6 240 43108
Percentage of the requests served within a certain time (ms)
50% 240
66% 274
75% 295
80% 306
90% 335
95% 369
98% 460
99% 39589
100% 43108 (longest request)
ab -n 10000 -c 100 "http://127.0.0.1:8081/"
Server Software: nginx
Server Hostname: 127.0.0.1
Server Port: 8081
Document Path: /
Document Length: 17592 bytes
Concurrency Level: 100
Time taken for tests: 92.971 seconds
Complete requests: 10000
Failed requests: 0
Total transferred: 187160000 bytes
HTML transferred: 175920000 bytes
Requests per second: 107.56 [#/sec] (mean)
Time per request: 929.709 [ms] (mean)
Time per request: 9.297 [ms] (mean, across all concurrent requests)
Transfer rate: 1965.92 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.2 0 3
Processing: 3 854 4601.4 236 38351
Waiting: 3 854 4601.4 236 38351
Total: 3 854 4601.4 236 38351
Percentage of the requests served within a certain time (ms)
50% 236
66% 282
75% 301
80% 313
90% 336
95% 358
98% 427
99% 35636
100% 38351 (longest request)
本地 WRK 压测⌗
wrk -t4 -c50 http://localhost:8080
Running 10s test @ http://localhost:8080
4 threads and 50 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 81.71ms 43.75ms 234.97ms 66.18%
Req/Sec 57.10 52.38 252.00 82.73%
2052 requests in 10.01s, 36.61MB read
Requests/sec: 205.03
Transfer/sec: 3.66MB
wrk -t4 -c50 http://localhost:8081
Running 10s test @ http://localhost:8081
4 threads and 50 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 98.90ms 97.57ms 786.41ms 95.98%
Req/Sec 55.98 52.61 300.00 83.20%
2099 requests in 10.01s, 37.48MB read
Requests/sec: 209.69
Transfer/sec: 3.74MB
总结⌗
经过几组测试发现,Unit 内嵌 PHP 8.1 的性能似乎和 NginX + PHP 8.1 + PHP-FPM 差距不大,不知道是 JIT 的功劳还是 Unit 内嵌 PHP 8.1 开启 JIT.CLI 没有生效。不可否认的一点是,PHP 这两年来的升级,性能确实提升了不少。
等后面有空再做一下 Swoole 和 RoadRunner 的测试。
I hope this is helpful, Happy hacking…