2021年3月18日 星期四

Laravel-S (Swoole 加速) 使用重點整理

在此所做之重點整理,係針對使用 hhxsv5/laravel-s 來對 Laravel 開發的網站提供 Swoole 加速功能,其專案網站為

基本資料

作業系統安裝的軟體
CentOS: 7.9.2009
Apache: 2.4.6
PHP: 7.0.33
Supervisor: 3.4.0-1
PHP Composer: 1.10.20
PHP Swoole: 2.2.0

透過 Composer 安裝 PHP package
LaravelS: 3.6.4
Laravel Framework: 5.1.46 (LTS)

Docker 設定

dockerfile
FROM centos:7.9.2009

RUN yum -y install http://rpms.famillecollet.com/enterprise/remi-release-7.rpm \
    && yum -y update \
    && yum -y install --enablerepo=remi,remi-php70 \
        httpd php php-pdo php-pgsql \
        php-xml php-mbstring php-mcrypt \
        php-opcache php-pecl-swoole2 php-pecl-inotify \
        unzip supervisor \
    && yum clean all \
    && rm -rf /var/cache/yum \
    && curl -o /usr/bin/composer https://getcomposer.org/download/1.10.20/composer.phar \
    && chmod a+x /usr/bin/composer

# add config file
COPY conf/httpd.conf /etc/httpd/conf/httpd.conf
COPY conf/supervisord.conf /etc/supervisord.conf
COPY conf/rev_proxy.conf /etc/httpd/conf.d/

#port and entry
EXPOSE 80

CMD ["/usr/bin/supervisord"]

其中,php-pecl-swoole2 為 PHP 加 Swoole 功能,php-pecl-inotify 則是為了偵測檔案變動重新載入程式。

rev_proxy.conf
<IfModule deflate_module>
    SetOutputFilter DEFLATE
    DeflateCompressionLevel 2
    AddOutputFilterByType DEFLATE text/html text/plain text/css text/javascript application/json application/javascript application/x-javascript application/xml application/x-httpd-php image/jpeg image/gif image/png font/ttf font/otf image/svg+xml
</IfModule>

RemoteIPHeader X-Forwarded-For

ProxyRequests Off
ProxyPreserveHost On

supervisord.conf
[supervisord]
nodaemon=true
logfile=/var/log/supervisord.log
pidfile=/var/run/supervisord.pid

[program:ntuocw_laravels]
command=php /var/www/html/ntu-ocw/web-src/bin/laravels start
redirect_stderr=true
autostart=true
autorestart=true
#user=http
numprocs=1
process_name=%(program_name)s_%(process_num)s
stdout_logfile=/var/www/html/ntu-ocw/storage/logs/bin_laravel-s-log1.log
stdout_logfile_maxbytes=1MB
stdout_logfile_backups=5


[program:httpd]
redirect_stderr=true
command=/usr/sbin/httpd -DFOREGROUND
process_name = httpd

對了,開發測試時,請手動下指令啟動,不要使用 supvervisord 啟動,若程式有問題,會陷入 reload 的無限迴圈。

httpd.conf 則只是要加上 "AllowOverride All",允許在目錄下使用 .htaccess 加上 rewrite rule。更重要的是基於安全,關閉目錄瀏覽功能 "Options -Indexes"。

使用 docker-compose 管理 docker,主要是下面三種指令
docker-compose build  ==> 建立 image
docker-compose up     ==> 啟動
docker-compose up -d  ==> 啟動,在背景執行
docker-compose down   ==> 停止

docker-compose.yml
version: '2'

services:
  web:
    build:
      context: ./
    #  dockerfile: Swoole_dockerfile
    restart: always
    working_dir: /var/www/html
    volumes:
      - ../web:/var/www/html
      - ../work_tmp:/work_tmp
    environment:
      COMPOSER_HOME: /work_tmp/Composer
    ports:
      - 8180:80


基本設定

以普通使用者的身份進入 container 中。
docker exec -it --user 1000:1000 ocw_docker_web_1 bash

進入後,建立 Laravel 5.1 的專案。
composer create-project laravel/laravel ntu-ocw "5.1.*"
在 composer.json 加入。
"require": {
    "php": ">=5.5.9",
    "laravel/framework": "5.1.*",
    "hhxsv5/laravel-s": "3.6.*"
},

然後執行 composer update,更新和安裝 PHP package

在 config/app.php,加入
'providers' => [
    //...
    Hhxsv5\LaravelS\Illuminate\LaravelSServiceProvider::class,
],

不然在執行下述指令時,會出現 Command "laravels" is not defined

php artisan laravels publish

若出現無法寫入 storage 的檔案時,
sudo chmod -R a+w storage/


執行 publish 指令後會複製下列 4個檔案
config/laravels.php
bin/laravels
bin/fswatch
bin/inotify


修改 .htaccess,為了確定修改正確,可將 index.php 刪除。
DirectoryIndex index.php   ==> Apache 預設為 index.html

# Handle Front Controller...
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$  http://localhost:10520/$1 [P,L]

在 .env 加入,再重新啟動 bin/laravels。測試時,worker_num 不用設太多。
# configs for Laravel-S
LARAVELS_LISTEN_PORT=10520
LARAVELS_WORKER_NUM=5
LARAVELS_INOTIFY_RELOAD=true
執行後,使用 ps -ax 看到的執行緒
  PID TTY      STAT   TIME COMMAND
      1 ?        Ss     0:01 /usr/bin/python /usr/bin/supervisord
      8 ?        Sl     0:00 /var/www/html/ntu-ocw/web-src laravels: master process
      9 ?        S      0:00 /usr/sbin/httpd -DFOREGROUND
     22 ?        S      0:00 /var/www/html/ntu-ocw/web-src laravels: manager process
     28 ?        S      0:00 /var/www/html/ntu-ocw/web-src laravels: worker process 0
     29 ?        S      0:00 /var/www/html/ntu-ocw/web-src laravels: worker process 1
     30 ?        S      0:00 /var/www/html/ntu-ocw/web-src laravels: worker process 2
     31 ?        S      0:00 /var/www/html/ntu-ocw/web-src laravels: worker process 3
     32 ?        S      0:10 /var/www/html/ntu-ocw/web-src laravels: worker process 4
     33 ?        S      0:00 /var/www/html/ntu-ocw/web-src laravels: inotify process
     72 pts/0    Ss     0:00 bash
    111 ?        S      0:00 /usr/sbin/httpd -DFOREGROUND
    203 ?        Z      0:00 [httpd] <defunct>
    227 ?        S      0:01 /usr/sbin/httpd -DFOREGROUND
      ........
    265 ?        S      0:00 /usr/sbin/httpd -DFOREGROUND
    266 pts/0    R+     0:00 ps -ax


可修改 config/laravels.php,增加監視的檔案 .env
'inotify_reload'           => [
        'enable'        => env('LARAVELS_INOTIFY_RELOAD', false),
        'watch_path'    => base_path(),
        'file_types'    => ['.php', '.env'],
        'excluded_dirs' => [],
        'log'           => true,
    ],

在 app/Providers/AppServiceProvider.php 的 boot() 中,加入 laravels.received_request 事件的處理程式片段,因為要處理的事情太多,將程式寫在另外的檔案中。
class AppServiceProvider extends ServiceProvider
{
    // Bootstrap any application services.
    public function boot()
    {
        // 送交 Laravel 前的處理
        \Event::listen('laravels.received_request', function (\Illuminate\Http\Request $req, $app) {
            // 這裡才是真的透過 browser瀏覽
            // 才有某些關於 client 的 SERVER 資料
            \App\Lib\SW_util::boot_setup($req, $app);
        });

        // Laravel 結束後的處理
        \Event::listen('laravels.generated_response', function (\Illuminate\Http\Request $req, \Symfony\Component\HttpFoundation\Response $rsp, $app) {
            // $rsp->headers->set('Content-Type', 'image/png');// Change header of response
        });
    }

為了處理使用 Laravel-S 帶來的差異性,把相關的程式碼統一放在 app\Lib\SW_util.php 中。下面的 boot_setup() 專門用來處理 boot 的事情。
// boot() 的啟始程序
public static function boot_setup(\Illuminate\Http\Request $req, $app)
{
    // 在這裡 print 的訊息,都會出現在執行 laravels start 的 console
    // echo "\n\n===== laravels.received_request =====\n";

    session()->clear();

    // 去除附加的預設執行檔 index.php
    $req_uri = $req->server->get("REQUEST_URI");
    $d_php = '/index.php';  // 配合 .htaccess 的 DirectoryIndex 設定

    if (starts_with($req_uri, $d_php)) {
        $req_uri = substr($req_uri, strlen($d_php));

        if ($req_uri == '') {
            $req_uri = '/';
        }

        $req->server->set("REQUEST_URI", $req_uri);
    }

    // 把 IP 改成實際的 remote IP
    $req->server->set("REMOTE_ADDR", $req->server->get("HTTP_X_FORWARDED_FOR"));

    // $_SERVER["SCRIPT_FILENAME"] =>
    // "/var/www/html/ajtest/ntu-ocw/web-src/bin/laravels"
    $pre_s = '/var/www/html/';
    $tail_s = '/web-src/bin/laravels';
    $url_path = substr($req->server->get("SCRIPT_NAME"), strlen($pre_s));
    $url_path = substr($url_path, 0, (0-strlen($tail_s)));

    \URL::forceRootUrl('http://'.$req->server->get('HTTP_HOST').'/'.$url_path);

    self::$dump_handler = null;
}


在 boot() 中的 laravels.received_request 的事件要處理的重點包括下列各點
  • 修改 SERVER['REQUEST_URI'],移除 index.php
  • session()->clear(),清除前一個連線留下的 session 資料
  • 使用 URL::forceRootUrl() 設定 host 的 url,url() 和 asset() 產生的網址才會正確
  • 以 SERVER ['HTTP_X_FORWARDED_FOR'] 取代 SERVER['REMOTE_ADDR'],這樣 Request::ip() 才能取得正確的使用者 IP
  • 假如有用 static variables 存放連線運作的變數,要記得在此重置

在剛開始設定 Laravel-S 可以連線時,要開啟目錄的預設網頁時,一直會出現無法 match route 的錯誤,真是讓人感到挫折灰心。後來根據錯誤訊息,追縱 Laravel 的原始碼,直接找到 Illuminate\Routing\Router.php,在出錯的那個 function,把傳過來 url 列印出來,才發現收到的 httpd 送來的預設檔名,index.html,後來自己改成 index.php。在執行 match route 之前必須將 index.php 移除。在 Symfony 中,有相關程式的片段。

Session 全部變成一樣了

將系統切換之後,在測試留言功能時,發現怎麼我第一次用,就有亂七八糟的留言內容,並且顯示驗證碼錯誤。再仔細檢查,不得了,所有的 session 內容全部變成一樣了,裡面還有我剛剛登入管理端的帳號身份。因為下班後才發現,只好把系統先關掉,將 session 全刪掉,再重新啟動系統。使用者不能留言,也就只好忍耐一下囉,終究先前曾經因系統設定錯誤,一兩個月留言功能都不正常,也沒人反應。

短時間內,找不到其他使用者的相關經驗分享,或是 Laravel-S 對 session 處理的相關說明。就先自己追蹤程式吧,發現是 Laravel 的 session/store.php 中,會將 attributes 陣列的資料與儲存的 session 資料合併。而 Session store 只在 laravel-s start 時 Instance 一次,因而其 attributes array 會保留前一次連線的 session 資料。幸好此系統僅是供使用者瀏覽,只有一個網頁讓使用者留言用,session 不正確沒有造成太大的影響。

Laravel 框架是使用 Symfony 的 Session,其 Session Store 是 Implement Symfony 的 SessionInterface。在實際運作時,建立的 session 資料是放在 attributes 陣列中,等到連線結束時,才寫回儲存裝置中。在緊急情況下,直接修改 store.php,在 function start() 中直接將 attributes 陣列清空。後來找到 interface 中有提供 function clear(),其功能即是將 attributes 陣列清空。

要取得 session 物件有幾種方式,session() 或 $req->session(),在 boot 時, session 還沒加進 $req 中,呼叫 $req->session() 會出現錯誤 "Session store not set on request"。還好可以正常呼叫 session()->clear(), 將 attributes 陣列清除。

依照上面的作法,將問題解決了。過了一陣子,隨意搜尋關於 Larave-S 的資訊,發現早就有人反應此問題,甚至在最早查資料時,就曾看過有關的敘述,只是那時一點概念都沒有,不曉得他在說什麼。而且 Larave-S 也解決了此問題,在 setting 中有相關說明,只是自己不能理解。

解決辦法很簡單,在 config/laravels.php 中的 'cleaners' 加入設定即可。

// Need to configure the following cleaners if you use the session/authentication/passport in your project
'cleaners' => [
    Hhxsv5\LaravelS\Illuminate\Cleaners\SessionCleaner::class,
    Hhxsv5\LaravelS\Illuminate\Cleaners\AuthCleaner::class,
],

這樣就可以了,就這麼簡單,真是踏破鐵鞋方覓得,嗯,頂多是多按了一些鍵盤啦。

調整目錄

調整後的目錄大致是這樣

ntu-ocw
   +- assets
   |     +- css, js, img, .....
   +- vendor
   |     +- hhxsv5, laravel, .....
   +- storage
   |     +- app, framework, logs, ....
   +- web-src
   |     +- bin, app, config, ....
   |     +- storage (僅供 laravels 存放資料 laravels.json  laravels.pid)
   + .htaccess, favicon.ico, robots.txt


因為改變了 vendor 的位置,修改 composer.json,然後再次執行 composer update
"config": {
    "preferred-install": "dist",
    "vendor-dir": "../vendor"
}

bin/laravels 的修改,將 $basePath 以 BASEDIR 取代
// $basePath = realpath(__DIR__ . '/../');
// BASEDIR, VENDORDIR 同時要定義在 artisan 中
define('BASEDIR', realpath(__DIR__ . '/../'));
define('VENDORDIR', realpath(__DIR__.'/../../vendor'));

$loader = new Psr4Autoloader();
$loader->register();

// Register laravel-s
$loader->addNamespace('Hhxsv5\LaravelS', VENDORDIR . '/hhxsv5/laravel-s/src');

// Register laravel-s dependencies
$loader->addNamespace('Symfony\Component\Console', VENDORDIR . '/symfony/console');
$loader->addNamespace('Symfony\Contracts\Service', VENDORDIR . '/symfony/service-contracts');
$loader->addNamespace('Symfony\Contracts', VENDORDIR . '/symfony/contracts');

$command = new Hhxsv5\LaravelS\Console\Portal(BASEDIR);
$input = new Symfony\Component\Console\Input\ArgvInput();
$output = new Symfony\Component\Console\Output\ConsoleOutput();
$code = $command->run($input, $output);
exit($code);

laravel-s 會呼叫 artisan,因此其亦要配合修改
define('BASEDIR', __DIR__.'');
define('VENDORDIR', realpath(__DIR__.'/../vendor'));

require BASEDIR.'/bootstrap/autoload.php';
$app = require_once BASEDIR.'/bootstrap/app.php';
$app->useStoragePath(realpath(BASEDIR.'/../storage'));

bootstrap/app.php 的修改,將 storage 移到 web-src 外部,便於備份程式。但是 laravel-s 將storage 的目錄寫死在程式,在 web-src 下保留一個 storage 目錄,只供 laravel-s使用。
$app = new Illuminate\Foundation\Application(
    realpath(__DIR__.'/../')
);

$app->useStoragePath(realpath(BASEDIR.'/../storage'));

bootstrap/autoload.php 的修改
require VENDORDIR.'/autoload.php';

效能測試

花了這麼多時間了解,查資料,追蹤,思考,測試,除錯,才把系統套到 Laravel-S,用上 Swoole 加速的功能,總會想了解花費的心血得到多少的收益。若是收效不大,大可不用,造成維護上的麻煩。

下面先用 siege 做壓力測試,看看網站每秒最多能處理多少連線。再用 curl 觀察個別連線花費的時間。

使用 siege 做壓力測試

PHP 7.0+Swoole 2.2,在對外開放的網站上,同時有其他使用者連上來
$ siege -c 100 -t 5s http://ocw.aca.ntu.edu.tw/ntu-ocw/ > /dev/null

** SIEGE 4.0.7
** Preparing 100 concurrent users for battle.
The server is now under siege...

Lifting the server siege...
Transactions:		         769 hits
Availability:		      100.00 %
Elapsed time:		        4.54 secs
Data transferred:	       14.84 MB
Response time:		        0.56 secs
Transaction rate:	      169.38 trans/sec
Throughput:		        3.27 MB/sec
Concurrency:		       94.54
Successful transactions:         769
Failed transactions:	           0
Longest transaction:	        1.65
Shortest transaction:	        0.03

PHP 7.0,在正式網站上,為了測試臨時開啟,未對外開放。
$ siege -c 100 -t 5s http://ocw.aca.ntu.edu.tw:8180/ntu-ocw/ > /dev/null

** SIEGE 4.0.7
** Preparing 100 concurrent users for battle.
The server is now under siege...

Lifting the server siege...
Transactions:		         863 hits
Availability:		      100.00 %
Elapsed time:		        4.28 secs
Data transferred:	       27.31 MB
Response time:		        0.40 secs
Transaction rate:	      201.64 trans/sec
Throughput:		        6.38 MB/sec
Concurrency:		       81.50
Successful transactions:         863
Failed transactions:	           0
Longest transaction:	        2.98
Shortest transaction:	        0.00

測試結果,很意外的,使用 Swoole 加速的網站,效能反而比較差。猜測可能的原因,網站會受到各種的安全防護的包圍過濾封包,防護裝置的效能也會影響效能。另外,其他使用者會佔用連線資料,如查詢資料庫,花較多的時間,也會擋到測試的連線。而專為測試開啟的伺服器,則不受其他使用者連線的影響。

為了排除其他使用者的影響,以及受安全防護裝置的拖累,另外在開發用的測試環境再跑一次壓力測試。不論正式及開發環境,都是使用 docker 提供服務。

PHP 7.0+Swoole 2.2,在開發用的測試網站上
$ siege -c 100 -t 5s http://10.161.86.117:8180/ntu-ocw/ > /dev/null

** SIEGE 4.0.7
** Preparing 100 concurrent users for battle.
The server is now under siege...

Lifting the server siege...
Transactions:		       30103 hits
Availability:		      100.00 %
Elapsed time:		        4.05 secs
Data transferred:	     1053.04 MB
Response time:		        0.01 secs
Transaction rate:	     7432.84 trans/sec
Throughput:		      260.01 MB/sec
Concurrency:		       98.84
Successful transactions:       30103
Failed transactions:	           0
Longest transaction:	        0.25
Shortest transaction:	        0.00

PHP 7.0,在開發用的測試網站上
$ siege -c 100 -t 5s http://10.161.86.117:8080/ajtest/ntu-ocw/ > /dev/null

** SIEGE 4.0.7
** Preparing 100 concurrent users for battle.
The server is now under siege...

Lifting the server siege...
Transactions:		        2664 hits
Availability:		      100.00 %
Elapsed time:		        4.03 secs
Data transferred:	      361.01 MB
Response time:		        0.15 secs
Transaction rate:	      661.04 trans/sec
Throughput:		       89.58 MB/sec
Concurrency:		       97.29
Successful transactions:        2664
Failed transactions:	           0
Longest transaction:	        0.31
Shortest transaction:	        0.03

在測試環境得到結果,使用 Swoole 加速每秒大約可以處理 10倍的連線。對了,在這網頁有用到 Laravel 的 cache 功能,會將資料庫查詢 cache 在檔案中,因此不受資料庫效能的影響。

使用 curl 測單一連線時間

另一方面,測試個別連線的花費時間
寫個簡單的 script,curltime
#!/bin/bash

curl -w @- -o /dev/null -s "$@" <<'EOF'
    time_namelookup:  %{time_namelookup}\n
       time_connect:  %{time_connect}\n
    time_appconnect:  %{time_appconnect}\n
   time_pretransfer:  %{time_pretransfer}\n
      time_redirect:  %{time_redirect}\n
 time_starttransfer:  %{time_starttransfer}\n
                    ----------\n
         time_total:  %{time_total}\n
EOF

PHP 7.0+Swoole 2.2,在對外開放的網站上,同時有其他使用者連上來
$ ./curltime http://140.112.161.116/ntu-ocw/
    time_namelookup:  20
       time_connect:  2286
    time_appconnect:  0
   time_pretransfer:  2303
      time_redirect:  0
 time_starttransfer:  19828
                    ----------
         time_total:  30103

PHP 7.0,在正式網站上,為了測試臨時開啟,未對外開放。
$ ./curltime http://140.112.161.116:8180/ntu-ocw/
    time_namelookup:  92
       time_connect:  2724
    time_appconnect:  0
   time_pretransfer:  2739
      time_redirect:  0
 time_starttransfer:  94360
                    ----------
         time_total:  101133

從結果的數來看,時間單位應是 microseconds。

為了減少 DNS 查詢時間,使用 IP 連線。花費的時間會一直變動,使用 Swoole 加速,每個連線花費的時間大約在 30ms 左右。而未加速時,花費時間大約為 100ms 左右。

以上使用正式開放的網站測試,可能受網站安全防護或其他使用者連線的影響。接著使用測試環境來測試,比較能看到純粹網站的效能。

PHP 7.0+Swoole 2.2,在開發用的測試網站上
$ ./curltime http://10.161.86.117:8180/ntu-ocw/
    time_namelookup:  89
       time_connect:  295
    time_appconnect:  0
   time_pretransfer:  400
      time_redirect:  0
 time_starttransfer:  13112
                    ----------
         time_total:  13402

PHP 7.0,在開發用的測試網站上
$ ./curltime http://10.161.86.117:8080/ajtest/ntu-ocw/
    time_namelookup:  17
       time_connect:  70
    time_appconnect:  0
   time_pretransfer:  110
      time_redirect:  0
 time_starttransfer:  23723
                    ----------
         time_total:  24125

使用 Swoole 加速,每個連線花費的時間大約在 15ms 左右。而未加速時,花費時間大約為 25ms 左右。

2021年3月6日 星期六

Gentoo 與 GeForce 9500 GT

[  189.317025] nvidia-nvlink: Nvlink Core is being initialized, major device number 245
[  189.317618] NVRM: The NVIDIA GeForce 9500 GT GPU installed in this system is
               NVRM:  supported through the NVIDIA 340.xx Legacy drivers. Please
               NVRM:  visit http://www.nvidia.com/object/unix.html for more
               NVRM:  information.  The 455.28 NVIDIA driver will ignore
               NVRM:  this GPU.  Continuing probe...
[  189.317642] NVRM: No NVIDIA graphics adapter found!
[  189.317927] nvidia-nvlink: Unregistered the Nvlink Core, major device number 245


 [I] x11-drivers/nvidia-drivers
     Available versions:  ~390.132-r4(0/390)^mtd 390.138-r4(0/390)^mtd 435.21-r6(0/435)^mtd 440.100-r2(0/440)^mtd 450.80.02(0/450)^mtd 455.28(0/455)^mtd ~455.38(0/455)^mtd {+X compat +driver gtk3 +kms +libglvnd multilib static-libs +tools uvm wayland ABI_MIPS="n32 n64 o32" ABI_S390="32 64" ABI_X86="32 64 x32" KERNEL="FreeBSD linux"}
     Installed versions:  455.28(0/455)^mtd(03時17分06秒 西元2020年11月13日)(X driver gtk3 kms libglvnd multilib tools wayland -compat -static-libs -uvm ABI_MIPS="-n32 -n64 -o32" ABI_S390="-32 -64" ABI_X86="32 64 -x32" KERNEL="linux -FreeBSD")
     Homepage:            https://www.nvidia.com/Download/Find.aspx
     Description:         NVIDIA Accelerated Graphics Driver


[I] sys-firmware/nvidia-firmware
     Available versions:  (~)340.32-r1^md
     Installed versions:  340.32-r1^md(17時50分07秒 西元2021年02月15日)
     Homepage:            https://nouveau.freedesktop.org/wiki/VideoAcceleration/
     Description:         Kernel and mesa firmware for nouveau (video accel and pgraph)

2021年3月3日 星期三

Laravel-S (Swoole 加速) 的初步探索整理

在測試了最熱門的 swooletw/laravel-swoole 不成功之後,再測熱門程度差了好幾個等級,甚至 Google "laravel swoole" 的關鍵字,在搜尋結果都不會出現的 hhxsv5/laravel-s,成功了,那就繼續探所下去吧。

首先,新手真的不曉得該如 rewrite rule,轉過來的 request,有啟動 php 程式,但一直出 match route 錯誤,真是讓人感到挫折灰心。後來根據錯誤訊息,追縱 Laravel 的原始碼,直接找到 Illuminate\Routing\Router.php,在出錯的那個 function,加上下列程式片段,這樣它就會在執行的命令視窗顯示轉過來的網址,再依此結果修正 rewrite rule。

findRoute($request)
{
    echo "findRoute\n";
    var_dump($request->getRequestUri()); exit;
    ....
} 

發現在沒有指定檔名,要開啟預設檔案時,會轉過來 index.html,解決方式是在 .htaccess 加入。
DirectoryIndex index.php

然後在 route.php 將 Route::get('/', ... 改成如下
Route::get('/index.php', function () { ... });
在 Laravel 中使用的 dump() 是 Symfony\Component\VarDumper::dump() 建立的 helper function,會呼叫 CliDumper(),其會將資料輸出到執行 laravels start 的命令視窗。

解決辦法,參考原程式,自己另建一個 dump()

static 變數的測試,噢,會互相影響,所以要改很多 ...,只能再看看吧!!

另一困擾,假如網頁不是放在伺服器的 DocumentRoot 目錄下,asset() 和 url() 不會加上網頁的目錄,如 http://ll.cc/blog/list ...,其中 blog 不會出現。這要改的也是不少。

解決辦法是修改檔案 app/Providers/AppServiceProvider.php 中的 boot method。
public function boot()
{
    \URL::forceRootUrl("http://localhost:8180/blog"); 
}

亦可將其定義在 .env 中,例如 APP_URL。只要加上兩行就能解決問題,算是不錯了。

經過反覆測試,最後修改 boot() 的內容如下,程式的其餘部份不用改太多就可順利執行。
/*
 * 此為 laravels.received_request, After LaravelS parsed Swoole\Http\Request 
 * to Illuminate\Http\Request, before Laravel's Kernel handles this request.
 */
public function boot()
{
    // 只會在 loading 時被 call 一次
    echo "===================\n";

    \Event::listen('laravels.received_request', 
        function (\Illuminate\Http\Request $req, $app) 
        {
            // 這裡才是真的透過 browser瀏覽
            // 才有某些關於 client 的 SERVER 資料
            // 去除附加的預設執行檔 index.php
            $req_uri = $req->server->get("REQUEST_URI");
            $def_php = '/index.php';

            if (substr($req_uri, 0, strlen($def_php)) == $def_php) {
                $req_uri = substr($req_uri, strlen($def_php));
                if ($req_uri == '') {
                    $req_uri = '/';
                }

                $req->server->set("REQUEST_URI", $req_uri);
            }

            // 看起來,root url 並不是依據 "HTTP_HOST" 產生的
            // 改變 "HTTP_HOST",並不會改變 url() 和 asset() 的結果
            // 但是又找不到其他變數含有 port 資料
            // $req_host = $req->server->get("HTTP_HOST");
            // echo $req_host."<br>\n";
            // $req->server->set("HTTP_HOST", $req_host.'abc');
            // $req->server->set("HTTP_HOST", env('URL_PATH'));
            var_dump($req->request->all());
            echo base_path()."<br>\n";
            // initialize static varibles
            Util_sw::clean();
            // $req->query->set('get_key', 'hhxsv5');// Change query of request
            // $req->request->set('post_key', 'hhxsv5'); // Change post of request
            // echo asset(env('URL_PATH'))."<br>\n";
            \URL::forceRootUrl('http://'.$req->server->get('HTTP_HOST').'/'.env('URL_PATH'));
        }
    );
    
    // echo request()->getPathInfo();
    // echo \URL::to('/')."<br>\n";
    // var_dump(getallheaders());
    // \URL::forceRootUrl(env("APP_URL")); 
}

在上面的程式片段中,加了一堆測試輸出訊息的程式,就是在摸索階段為了瞭解其特性而加的。其餘的再繼續整理中。

依照 Laravel-S 專案上的說明,將 Laravel 的 .htaccess 的內容修改如下,就是在檔案不存在時,轉到 laravel-s 伺服程式處理。而 Apache 的 httpd,在瀏覽目錄時,設定的預設程式是 index.html,在這裡把它改成 index.php。

# Alternate default index page
DirectoryIndex index.php

<IfModule mod_rewrite.c>
    <IfModule mod_negotiation.c>
        Options -MultiViews
    </IfModule>

    RewriteEngine On

    # Redirect Trailing Slashes If Not A Folder...
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule ^(.*)/$ /$1 [L,R=301]

    # Handle Front Controller...
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule ^(.*)$  balancer://laravels/$1 [P,L]
</IfModule>

針對上面的 rewrite rule,要修改 reverse proxy 的設定。
<IfModule deflate_module>
    SetOutputFilter DEFLATE
    DeflateCompressionLevel 2
    AddOutputFilterByType DEFLATE text/html text/plain text/css text/javascript application/json application/javascript application/x-javascript application/xml application/x-httpd-php image/jpeg image/gif image/png font/ttf font/otf image/svg+xml
</IfModule>


RemoteIPHeader X-Forwarded-For

ProxyRequests Off
ProxyPreserveHost On

<Proxy balancer://laravels>  
    BalancerMember http://127.0.0.1:5200 loadfactor=7
    #BalancerMember http://192.168.1.2:5200 loadfactor=3
    #BalancerMember http://192.168.1.3:5200 loadfactor=1 status=+H
    ProxySet lbmethod=byrequests
</Proxy>

因為一個伺服器不只一個網頁程式,所以並未依照 Laravel-S 的說明,將網頁指向根目錄 (/)。為了解決此問題,測試了好久才搞定。


另外,不想修改網站設定,依自己的習慣調網站的目錄架構,將 public 目錄下的東西移到網頁的根目錄,程式則移到 web-src 下面,storage 不太好移,仍然放在 web-src 下,所以還需進一步修改 bin/laravels.php、artisan,bootstrap/autoload.php。


後續發展,經過半個多月的摸索,終於將一個自己維護的網站套上 Laravel-S 了,後面再開一篇文章來整理修改和注意的重點。





測試 - 使用 Swoole 加速 Laravel

Swoole 這名稱聽過好一陣子了,但聽起來感覺不怎麼特別,另外也沒有特別要求速度的網頁,或者使用的 OS 是老掉牙的 Windows Server 2003,就沒什麼很注意。最近稍有空閒,想找點新東西來玩玩,活絡一下大腦,才稍微深入研究探討。

首先看一下執行的畫面,嗯,網頁主要是有個圖比較好看。


設定測試環境

現代程式開發人員的好處就是,不用在自己日常用的電腦上裝一堆不知有沒有的軟體來測試新的東西,只要寫個 docker 的設定檔,就可以弄一個測試環境。測試 Swoole 的 Dockerfile 如下。

FROM centos:7.9.2009

RUN yum -y install \
      http://rpms.famillecollet.com/enterprise/remi-release-7.rpm \
   && yum -y install --enablerepo=remi,remi-php70 \
        httpd php php-pdo php-mysqlnd \
        php-xml php-mbstring php-mcrypt \
        php-opcache php-pecl-swoole2 unzip \
    && yum clean all \
    && rm -rf /var/cache/yum \
    && curl -o /usr/bin/composer \
       https://getcomposer.org/download/1.10.20/composer.phar \
    && chmod a+x /usr/bin/composer

#port and entry
EXPOSE 80

CMD ["/usr/sbin/httpd", "-D", "FOREGROUND"]

其中,大部分是 Laravel 使用的功能,只有 php-pecl-swoole2 為 PHP 增加 Swoole 功能,php-pecl-inotify 則是為了偵測檔案變動重新載入程式。


測試 laravel-swoole 及 Laravel-S

Google 和 Github 上,都是 swooletw/laravel-swoole 的關注度高很多,那就先測試 swooletw/laravel-swoole。

composer update 時發生錯誤,處理方式

使用個人使用者身分,進入 docker 中執行 composer

docker exec -it --user 1000:1000 docker_web_1 bash


在建立 Laravel 的 project 之後,依照 laravel-swoole 的說明修改 composer.json,然後執行 composer update,這時會出錯,錯誤訊息如下 
"Allowed memory size of 1610612736 bytes exhausted ... " 

參考網路找到的資訊
https://stackoverflow.com/questions/49212475/composer-require-runs-out-of-memory-php-fatal-error-allowed-memory-size-of-161

執行 composer update 時增加記憶體,成功安裝 PHP 的 Package

bash-4.2$ php -r "echo ini_get('memory_limit').PHP_EOL;"

128M

bash-4.2$ php -d memory_limit=-1 /usr/bin/composer update

然後依據說明,在命令列下指令啟動 swoole server 

php artisan swoole:http {start|stop|restart|reload|infos}

然後用瀏覽器瀏覽網頁,但是一直出錯。測試結果都是不成功,在讀取網頁時,一直出現下列錯誤

PHP Fatal error:  Call to undefined method Symfony\Component\HttpFoundation\ResponseHeaderBag::allPreserveCaseWithoutCookies() in /var/www/html/swoole_tst/vendor/swooletw/laravel-swoole/src/Server/Response.php on line 73

根據 google 到的資訊

allPreserveCaseWithoutCookies() is available after Symfony 3.4, what's your Laravel version?

Before Laravel 5.3, symfony/http-foundation is too old to use function allPreserveCaseWithoutCookies(). I'll try to make it compatible in the next release.

換了較舊版本的 laravel-swoole,或換較新版本的 Laravel,都不能成功,最後放棄。

第一次的測試碰到挫折,那就改為使用 hhxsv5/laravel-s 來測試。相對而言,laravel-s 較為麻煩,耐心的安裝設定後,依照說明下指令啟動 laravels,看到執行的畫面了,稍微了解運作方式之後,也可以在瀏覽器看到結果了。

bash-4.2$ php bin/laravels start

basePath: /var/www/html/swoole_tst/web-src 

VENDORDIR: /var/www/html/swoole_tst/web-src/../vendor

 _                               _  _____ 

| |                             | |/ ____|

| |     __ _ _ __ __ ___   _____| | (___  

| |    / _` | '__/ _` \ \ / / _ \ |\___ \ 

| |___| (_| | | | (_| |\ V /  __/ |____) |

|______\__,_|_|  \__,_| \_/ \___|_|_____/ 

                                           

Speed up your Laravel/Lumen

>>> Components

+---------------------------+--------------+

| Component                 | Version      |

+---------------------------+--------------+

| PHP                       | 7.0.33       |

| Swoole                    | 2.2.0        |

| LaravelS                  | 3.6.4        |

| Laravel Framework [local] | 5.1.46 (LTS) |

+---------------------------+--------------+

>>> Protocols

+-----------+--------+-------------------+----------------+

| Protocol  | Status | Handler           | Listen At      |

+-----------+--------+-------------------+----------------+

| Main HTTP | On     | Laravel Framework | 127.0.0.1:5200 |

+-----------+--------+-------------------+----------------+

>>> Feedback: https://github.com/hhxsv5/laravel-s

[2021-03-03 07:41:57] [TRACE] Swoole is running, press Ctrl+C to quit.


下面是 Laravel 5.1 的composer.json,只是多一行而已,為了保險起見,裝的 laravel-s 是前一版的。

{

    "name": "laravel/laravel",

    "description": "The Laravel Framework.",

    "keywords": ["framework", "laravel"],

    "license": "MIT",

    "type": "project",

    "require": {

        "php": ">=5.5.9",

        "laravel/framework": "5.1.*",

        "hhxsv5/laravel-s": "3.6.*"

    },

    "require-dev": {

        "fzaninotto/faker": "~1.4",

        "mockery/mockery": "0.9.*",

        "phpunit/phpunit": "~4.0",

        "phpspec/phpspec": "~2.1"

    },

    "autoload": {

        "classmap": [

            "database"

        ],

        "psr-4": {

            "App\\": "app/"

        }

    },

    "autoload-dev": {

        "classmap": [

            "tests/TestCase.php"

        ]

    },

    "scripts": {

        "post-root-package-install": [

            "php -r \"copy('.env.example', '.env');\""

        ],

        "post-create-project-cmd": [

            "php artisan key:generate"

        ],

        "post-install-cmd": [

            "Illuminate\\Foundation\\ComposerScripts::postInstall",

            "php artisan optimize"

        ],

        "post-update-cmd": [

            "Illuminate\\Foundation\\ComposerScripts::postUpdate",

            "php artisan optimize"

        ]

    },

    "config": {

        "preferred-install": "dist",

        "vendor-dir": "../vendor"

    }

}

在 laravel-s  的 git 專案網頁中,相關的說明蠻細的,一下子吸收不了那麼多,其中有一段是使用 httpd 的 reverse-proxy 來提供服務的說明,就照著設,內容大致如下,可以正常運作,但不盡完美,等後面測試累積較多經驗後,再來整理說明。

ProxyRequests Off


<Proxy *>

Order allow,deny

Allow from all

</Proxy>


# ProxyVia On


ProxyPass /swoole-web http://127.0.0.1:5200

ProxyPassReverse /swoole-web http://127.0.0.1:5200


初步測試後的心得

初步測試後,認為在使用上有一些要處理的問題,如下

  • 使用 url() 和 asset() 產生的 url 都是 http://127.0.0.1:5200 開頭
  • 只能處理透過 index.php 產生的網頁,assets 下的,如圖檔,js,css檔,都不能讀取
  • dump(),tracy debugger 等都不能運作
  • 依據 hhxsv5/laravel-s 的提示,必須使用 request,response 來處理輸入/輸出
目前的結論是,假如很在乎效能,就必須大幅調整程式,才能使用。另外,簡單的測試,原來的網頁存取次數 460.83 trans/sec,使用 swoole 加速後 1851.85 trans/sec,約 4倍,很爽的飆速感覺。

後續報告,經過 2 個多星期的奮鬥之後,想法有一些改變,可以在進入 kernel 的服務之前,預做一些處理,程式要改動部份就很少了。

網誌存檔