nginx+swoole+yii2 vs nginx+php-fpm+yii2 简单的性能对比

1.本地环境的搭建

环境:

  • wsl,Ubuntu版本 18.04 LTS
  • 安装docker,安装docker-compose
  • 接口测试是在局域网环境测试

注:cpu是四核,Intel(R) Core(TM) i5-7500 CPU @ 3.40GHz,空余内存12G. 但是由于本机运行了大量的服务,所以参考价值不大。
两种运行环境是在同一台电脑下做的对比,且运行其中一个环境会先停掉另外一个,不存在相互影响的情况。
php都开启了opcache。

1.1 docker+nginx+swoole +yii2

具体配置文件:https://github.com/10xjzheng/SwooleVsPhpfpm/tree/master/swoole

直接上docker-compose 配置

version: '3.4'
services:
    web:
        image: nginx:latest
        volumes:
            - ./nginx/config/nginx.conf:/etc/nginx/nginx.conf:ro
            - ./nginx/config/qmyx.conf:/etc/nginx/conf.d/qmyx.conf:ro
            #- "./nginx/ssl:/etc/ssl"
            - "/mnt/d/wamp/www/qmyx-base:/usr/share/nginx/html/qmyx-base" #需要修改为你项目的目录
        ports:
            - "80:80"  # api http
            - "443:443" # api https
        restart: always
        depends_on:
            - php
    php:
        image: 10xjzheng/swoole:latest
        volumes:
            - "/mnt/d/wamp/www/qmyx-base:/home/web" #需要修改为你项目的目录
        ports:
            - "8888:80"
        command: php /home/web/yii swoole-backend/start #需要修改为你项目的目录
        restart: always

swoole镜像是自己build的,dockerfile如下:

FROM php:7.4

ADD sources.list /etc/apt/sources.list

# install modules : GD iconv
RUN apt-get update && apt-get install -y \
        procps \
        libfreetype6-dev \
        libjpeg62-turbo-dev \
        libpng-dev \
        openssl \
        libssh-dev \
        libpcre3 \
        libpcre3-dev \
        libnghttp2-dev \
        libhiredis-dev \
        libonig-dev \
        curl \
        wget \
        zip \
        unzip \
        git && \
        apt-get autoremove && apt-get clean
# install php pdo_mysql opcache
# WARNING: Disable opcache-cli if you run you php
RUN docker-php-ext-configure gd --with-freetype --with-jpeg && \
    docker-php-ext-install \
    iconv \
    gd \
    pdo_mysql \
    mysqli \
    iconv \
    mbstring \
    json \
    opcache \
    sockets \
    pcntl && \
    echo "opcache.enable_cli=1" >> /usr/local/etc/php/conf.d/docker-php-ext-opcache.ini

#install redis
RUN pecl install redis && docker-php-ext-enable redis

# install composer
ENV COMPOSER_ALLOW_SUPERUSER 1
RUN curl -sS https://getcomposer.org/installer | php && \
    mv composer.phar /usr/local/bin/composer && \
    composer self-update --clean-backups

# install swoole
#TIP: it always get last stable version of swoole coroutine.
RUN cd /root && \
    curl -o /tmp/swoole-releases https://github.com/swoole/swoole-src/releases -L && \
    cat /tmp/swoole-releases | grep 'href=".*archive.*.tar.gz"' | head -1 | \
    awk -F '"' ' {print "curl -o /tmp/swoole.tar.gz https://github.com"$2" -L" > "/tmp/swoole.download"}' && \
    sh /tmp/swoole.download && \
    tar zxvf /tmp/swoole.tar.gz && cd swoole-src* && \
    phpize && \
    ./configure \
    --enable-coroutine \
    --enable-openssl  \
    --enable-http2  \
    --enable-async-redis \
    --enable-mysqlnd && \
    make && make install && \
    docker-php-ext-enable swoole && \
    echo "swoole.fast_serialize=On" >> /usr/local/etc/php/conf.d/docker-php-ext-swoole-serialize.ini && \
    rm -rf /tmp/*

# set China timezone
RUN /bin/cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
    echo 'Asia/Shanghai' > /etc/timezone && \
    echo "[Date]\ndate.timezone=Asia/Shanghai" > /usr/local/etc/php/conf.d/timezone.ini

nginx配置如下:

server {
    proxy_ignore_client_abort on;
    set $web /usr/share/nginx/html/qmyx-base/backend/web;
    root $web;
    server_name my.swoole.com;

    location ~* .(ico|gif|bmp|jpg|jpeg|png|swf|js|css|mp3)$ {
        root  $web;
    }

    location / {
        proxy_http_version 1.1;
        proxy_set_header Connection "keep-alive";
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header Host http://my.swoole.com;
        proxy_pass http://php:80;
    }
 }

1.2 docker+nginx+php-fpm+yii2

具体配置文件:https://github.com/10xjzheng/SwooleVsPhpfpm/tree/master/php-fpm
直接上docker-compose 配置

version: '3.4'
services:
    web:
        image: nginx:latest
        volumes:
            - ./nginx/config/nginx.conf:/etc/nginx/nginx.conf:ro
            - ./nginx/config/qmyx.conf:/etc/nginx/conf.d/qmyx.conf:ro
            #- "./nginx/ssl:/etc/ssl"
            - /mnt/d/wamp/www/qmyx-base:/usr/share/nginx/html/qmyx-base:rw #需要修改为你项目的目录
        ports:
            - "80:80"
        restart: always
        depends_on:
            - php
    php:
        image: 10xjzheng/php-fpm:latest
        volumes:
            - /mnt/d/wamp/www/qmyx-base:/usr/share/nginx/html/qmyx-base:rw #需要修改为你项目的目录
        ports:
            - "9000:9000"
        restart: always

php-fpm镜像也是自己build的,跟swoole镜像区别只在于镜像源swoole用的是php7.4,php-fpm用的是php-fpm7.4,然后swoole镜像装了swoole后者没有。
dockerfile:

FROM php:7.4-fpm

ADD sources.list /etc/apt/sources.list

# install modules : GD iconv
RUN apt-get update && apt-get install -y \
        procps \
        libfreetype6-dev \
        libjpeg62-turbo-dev \
        libpng-dev \
        openssl \
        libssh-dev \
        libpcre3 \
        libpcre3-dev \
        libnghttp2-dev \
        libhiredis-dev \
        libonig-dev \
        curl \
        wget \
        zip \
        unzip \
        git && \
        apt-get autoremove && apt-get clean
# install php pdo_mysql opcache
# WARNING: Disable opcache-cli if you run you php
RUN docker-php-ext-configure gd --with-freetype --with-jpeg && \
    docker-php-ext-install \
    iconv \
    gd \
    pdo_mysql \
    mysqli \
    iconv \
    mbstring \
    json \
    opcache \
    sockets \
    pcntl && \
    echo "opcache.enable_cli=1" >> /usr/local/etc/php/conf.d/docker-php-ext-opcache.ini

#install redis
RUN pecl install redis && docker-php-ext-enable redis

# install composer
ENV COMPOSER_ALLOW_SUPERUSER 1
RUN curl -sS https://getcomposer.org/installer | php && \
    mv composer.phar /usr/local/bin/composer && \
    composer self-update --clean-backups

# set China timezone
RUN /bin/cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
    echo 'Asia/Shanghai' > /etc/timezone && \
    echo "[Date]\ndate.timezone=Asia/Shanghai" > /usr/local/etc/php/conf.d/timezone.ini

1.3 压测工具wrk的安装

  • 安装:依次执行下列命令:
git clone https://github.com/wg/wrk.git
cd wrk
make
cp wrk /usr/local/bin
  • 使用方法: wrk <选项> <被测HTTP服务的URL>
  Options:                                            
    -c, --connections <N>  跟服务器建立并保持的TCP连接数量  
    -d, --duration    <T>  压测时间           
    -t, --threads     <N>  使用多少个线程进行压测   

    -s, --script      <S>  指定Lua脚本路径       
    -H, --header      <H>  为每一个HTTP请求添加HTTP头      
        --latency          在压测结束后,打印延迟统计信息   
        --timeout     <T>  超时时间     
    -v, --version          打印正在使用的wrk的详细版本信息

  <N>代表数字参数,支持国际单位 (1k, 1M, 1G)
  <T>代表时间参数,支持时间单位 (2s, 2m, 2h)
  • 示例
wrk -t 8 -c 200 -d 30s  --latency http://www.5lmh.com
Running 30s test @ http://www.5lmh.com (压测时间30s)
  8 threads and 200 connections (共8个测试线程,200个连接)
  Thread Stats   Avg      Stdev     Max   +/- Stdev
              (平均值) (标准差)(最大值)(正负一个标准差所占比例)
    Latency    46.67ms  215.38ms   1.67s    95.59%
    (延迟)
    Req/Sec     7.91k     1.15k   10.26k    70.77%
    (处理中的请求数)
  Latency Distribution (延迟分布)
     50%    2.93ms
     75%    3.78ms
     90%    4.73ms
     99%    1.35s (99分位的延迟)
  1790465 requests in 30.01s, 684.08MB read (30.01秒内共处理完成了1790465个请求,读取了684.08MB数据)
Requests/sec:  59658.29 (平均每秒处理完成59658.29个请求)
Transfer/sec:     22.79MB (平均每秒读取数据22.79MB)

2.压测实验

2.1 YII2测试代码

yii2框架使用高级版本。执行下面命令:

php composer.phar create-project yiisoft/yii2-app-advanced advanced

或者直接下载再解压:
https://github.com/yiisoft/yii2/releases/download/2.0.32/yii-advanced-app-2.0.32.tgz

然后安装yii2-swoole:

composer require "feehi/yii2-swoole"

具体说明文档见:https://github.com/liufee/yii2-swoole

根据上述文档修改配置:

'controllerMap'=>[
        'swoole-backend' => [
            'class' => feehi\console\SwooleController::class,
            'rootDir' => str_replace('console/config', '', __DIR__ ),//yii2项目根路径
            'app' => 'backend',
            'host' => '0.0.0.0',
            'port' => 80,
            'web' => 'web',//默认为web。rootDir app web目的是拼接yii2的根目录,如果你的应用为basic,那么app为空即可。
            'debug' => true,//默认开启debug,上线应置为false
            'env' => 'dev',//默认为dev,上线应置为prod
            'swooleConfig' => [
               'reactor_num' => 4, //4核
                'worker_num' => 10, //10个进程
                'daemonize' => false,
                'log_file' => __DIR__ . '/../../backend/runtime/logs/swoole.log',
                'log_level' => 0,
                'pid_file' => __DIR__ . '/../../backend/runtime/server.pid',
            ],
        ]
    ],

测试代码分为两种:

  • 1.没有db操作的,直接返回信息
  • 2.有db操作

2.2 我们先来测试没有db操作的

  • 代码如下:
class BrokerController extends Controller
{

    /**
     * Displays homepage.
     * @return array
     */
    public function actionIndex()
    {
        Yii::$app->response->format = Response::FORMAT_JSON;
        return ['code' => 0, 'message' => 'success'];
    }
}
  • 先运行swoole环境
docker-compose up  -d

监控一下进程的cpu和内存:

docker stats
image.png

swoole启用了10个worker进程,使用了59M内存,cpu基本没占用。

执行压测命令,即3个线程1000个链接,持续3s。

wrk -t 3 -c 1000 -d 3s --latency http://my.swoole.com/broker/index

cpu瞬间飙升,内存也飙升:


image.png

image.png

看看测试报告:


image.png

qps是每秒80个左右,还有不少timeout,不知道是什么原因。
延迟分布的话,居然要1.9s左右,这只是个返回json数据的接口,晕死。

  • php-fpm环境
    为了保持和swoole一样数量的worker进程,我把配置改成以下配置:


    image.png

    同swoole环境,先启动:

docker-compose up  -d

监控一下进程的cpu和内存:

docker stats

启动后,占用的内存和CPU如下图:


image.png

看似内存占用会少一点。

然后还是执行一下压测命令,和swoole保持一致:

wrk -t 3 -c 1000 -d 3s --latency http://my.swoole.com/broker/index

可以看到CPU同样暴涨,但内存的情况却好很多:


image.png

image.png

再看看测试报告:


image.png

从测试报告来看,从延迟分布来看swoole相当好一点,只是没那么明显,但是从qps来看,swoole要好很多,swoole是80 requests/sec, 而php-fpm只有60 requests/sec。

  • 结果分析
  1. 关于swoole在高并发情况下,所占内存会高出很多,甚至达到1g以上,还伴随压测有一些timeout的情况的问题的探索。
    我写了压测过程中php进程占用内存的日志,grep查询了一下,如下图:


    image.png

    代码是:

SwooleLogger::flushLog(posix_getpid(). '--' . ' after mem:'.$m2.'MB');

即记录了当前pid的进程占用的php内存。
可以看到swoole进程在高并发情况下,内存会暴涨到120M以上。
随后用docker logs CID(容器ID) 也可以验证我的想法:

image.png

这是导致有一定数量的timeout的原因。

为什么会这样子呢?因为swoole是常驻进程,不断的handle请求,然后用的又是协程,即没个请求占用2M内存,那么同时处理100个请求就是200M,如果压测时间缩短,有可能10个worker进程有可能同时达到顶峰,2G。当然在我们这里有些请求处理完内存会被释放,所以并没直接去到2g,但我们能捕获到达到1g以上的情况。而php-fpm是每一个接口一个进程跑完就释放,所以内存不会这么暴涨。

2.关于php-fpm的3s的时间段内处理请求数量少并有那么多timeout的问题。
docker logs CID(容器ID) 看看nginx的容器,可以看到在大量200请求里面还夹杂着不少499:

image.png

499 / ClientClosed Request
An Nginx HTTP server extension. This codeis introduced to log the case when the connection is closed by
client whileHTTP server is processing its request, making server unable to send the HTTP header back

即php-fpm响应太慢,nginx直接closed了请求。
这是为什么php-fpm 在3s内完成的请求比swoole慢的原因,swoole使用了协程,对于网络IO是很有效率的,同一时间段内能接受更多请求也合理。

  1. 结论:
    swoole在并发情况下的qps要比php-fpm好很多,但在内存方面需要优化。

2.3 我们再来测试有db操作的

需要自己先配置mysql的链接。
yii 测试代码如下:

<?php
namespace backend\controllers;

use backend\entities\UserEntity;
use yii\web\Controller;
use Yii;
use yii\web\Response;

/**
 * Site controller
 */
class BrokerController extends Controller
{

    /**
     * Displays homepage.
     *
     * @return string
     */
    public function actionIndex()
    {
        Yii::$app->response->format = Response::FORMAT_JSON;
        /** @var UserEntity  $broker */
        $broker = UserEntity::find()
            ->where(['mobile_tel' => '15220165155'])
            ->select('user_id, user_name,mobile_tel')
            ->limit(1)->asArray()->one();

        $bank = UserEntity::getDb()->createCommand("select * from user_bank where user_id = '{$broker['b_regbrokerId']}'")
            ->queryOne();
        return array_merge($broker, $bank);
    }
}

运行了2个SQL语句。

  • 直接压测swoole,先把并发请求降下来
 wrk -t 5 -c 500 -d 5s --latency http://my.swoole.com/broker/index
image.png

5s内能处理65个requests, qps是12.5+ Requests/sec。

  • 在压测php-fpm
 wrk -t 5 -c 500 -d 5s --latency http://my.swoole.com/broker/index
image.png

5s内能处理60个requests, qps是12+ Requests/sec。

多次压测结果,swoole的qps和单位时间内处理的请求还是相对好一点,从延迟发布来看,swoole的表现也相对好一点。

3. 结论

  1. swoole在并发情况下性能还是要好一点,但在我这里测试并不明显。可能是因为yii2框架的原因,也可能是环境的原因。毕竟不是独立的服务器;
  2. swoole并发情况下的内存问题需要有好的解决方式,一个是可以调大php的限制内存,另外一个是对yii框架运行时占用的内存做优化。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270