基本資料
Docker 設定
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 則是為了偵測檔案變動重新載入程式。
<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]
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
docker-compose build ==> 建立 image
docker-compose up ==> 啟動
docker-compose up -d ==> 啟動,在背景執行
docker-compose down ==> 停止
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
基本設定
docker exec -it --user 1000:1000 ocw_docker_web_1 bash
composer create-project laravel/laravel ntu-ocw "5.1.*"
"require": {
"php": ">=5.5.9",
"laravel/framework": "5.1.*",
"hhxsv5/laravel-s": "3.6.*"
},
'providers' => [
//...
Hhxsv5\LaravelS\Illuminate\LaravelSServiceProvider::class,
],
php artisan laravels publish
若出現無法寫入 storage 的檔案時,
sudo chmod -R a+w storage/
config/laravels.php
bin/laravels
bin/fswatch
bin/inotify
DirectoryIndex index.php ==> Apache 預設為 index.html
# Handle Front Controller...
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ http://localhost:10520/$1 [P,L]
# configs for Laravel-S
LARAVELS_LISTEN_PORT=10520
LARAVELS_WORKER_NUM=5
LARAVELS_INOTIFY_RELOAD=true
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
'inotify_reload' => [
'enable' => env('LARAVELS_INOTIFY_RELOAD', false),
'watch_path' => base_path(),
'file_types' => ['.php', '.env'],
'excluded_dirs' => [],
'log' => true,
],
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
});
}
// 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;
}
- 修改 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
"config": {
"preferred-install": "dist",
"vendor-dir": "../vendor"
}
// $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);
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'));
$app = new Illuminate\Foundation\Application(
realpath(__DIR__.'/../')
);
$app->useStoragePath(realpath(BASEDIR.'/../storage'));
require VENDORDIR.'/autoload.php';
效能測試
花了這麼多時間了解,查資料,追蹤,思考,測試,除錯,才把系統套到 Laravel-S,用上 Swoole 加速的功能,總會想了解花費的心血得到多少的收益。若是收效不大,大可不用,造成維護上的麻煩。
下面先用 siege 做壓力測試,看看網站每秒最多能處理多少連線。再用 curl 觀察個別連線花費的時間。
使用 siege 做壓力測試
$ 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
$ 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
$ 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
$ 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 測單一連線時間
#!/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
$ ./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
$ ./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 左右。
以上使用正式開放的網站測試,可能受網站安全防護或其他使用者連線的影響。接著使用測試環境來測試,比較能看到純粹網站的效能。
$ ./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
$ ./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