Thursday, May 8, 2014

Apache MPM Worker + mod_fastcgi + PHP-FPM

Apache MPM Worker + mod_fastcgi + PHP-FPM

Quick Notes:
[] There are two kinds of Apache MPM (Multi-Processing Modules):
- prefork
- worker (threaded MPM)

[] Apache MPM:
- MPM prefork + mod_php
- MPM worker (threaded MPM) + mod_fastcgi
- MPM worker (threaded MPM) + mod_fcgid

Note: if you want to use either mod_fastcgi or mod_fcgid, be aware of that each process has its own APC opcode cache, which in turn wastes memory resource, unless you use PHP-FPM to share APC cache between processes.

[] 由於 php-fpm 是自帶的 FastCGI Process Manager , 所以和原本的 php fastcgi 有所不同 , php-fpm 若要搭配 Apache 只能以 mod_fastcgi 來運作 , 原本 Apache 有開發 mod_fcgid , 但 mod_fcgid 本身算是個 FastCGI Process Manager , 只能執行本地端 FastCGI , 而 php-fpm 可以用 Unix Socket 或 TCP 模式讓任何具備執行外部 FastCGI 程序的 WebServer 連接 , 因此 lighttpd 或 Ngix 等都可以 , 唯獨世上次爛的 Apache 要外掛 mod_fastcgi , 最爛的當然是 IIS 了 , 哈

[] PHP-FPM not only supports TCP/IP connections (ex: 127.0.0.1:9000) but also the socket based connections (ex: /tmp/php-fpm.sock).

The advantage of running PHP-FPM on socket connections instead of TCP/IP is that the socket connections are much more faster than TCP/IP connections (around 10-15%) because it saves the passing the data over the different layers of TCP/IP stack.
Therefore, it is recommended to run the PHP-FPM on socket connections over TCP/IP when you are using the same server for Apache and PHP-FPM. If you are using the different servers for Apache and PHP-FPM then the socket connections for PHP-FPM will not work.

[] the topic of sharing APC opcode cache between processes.

[] Apache 改為 MPM Worker 模式運作 (instead of MPM prefork)

甚麼是 worker 模式 ? 簡單來說就是 Apache 會搭配多行程 + 多執行緒的方式運作 , 每個行程會有多個執行續同時服務客戶端連線 , 記憶體及效能都較好 , 由於 php-fpm 或 php fastcgi 都已經是獨立於 Apache 行程了 , 所以 Apache 若再跑 prefork 模式就浪費了 , 因此可以切換為 worker 模式 。

[] Use mod_fastcgi's FastCgiExternalServer to point to PHP-FPM, no more wrapper script please!!

[] Most of apache config examples are outdated or wrong, or copy from other wrong articles.

[] PHP-FPM is a daemon and mod_fcgid's official site (http://httpd.apache.org/mod_fcgid/mod/mod_fcgid.html) is not talking about PHP-FPM, it is talking about php patched with fastcgi library http://www.fastcgi.com/drupal/node/5?q=node/10.

[] PHP-FPM is native in PHP since 5.3.3, so the mod_fcgid is not the choice for PHP user at the mean time, unless it provide Directive similiar to mod_fastcgi's FastCgiExternalServer.

[] Why shouldn't I use Apache2 with a threaded MPM in a production environment?
PHP is glue. It is the glue used to build cool web applications by sticking dozens of 3rd-party libraries together and making it all appear as one coherent entity through an intuitive and easy to learn language interface. The flexibility and power of PHP relies on the stability and robustness of the underlying platform. It needs a working OS, a working web server and working 3rd-party libraries to glue together. When any of these stop working PHP needs ways to identify the problems and fix them quickly. When you make the underlying framework more complex by not having completely separate execution threads, completely separate memory segments and a strong sandbox for each request to play in, further weaknesses are introduced into PHP's system.

If you want to use a threaded MPM, look at a FastCGI configuration where PHP is running in its own memory space.

http://php.net/manual/en/faq.installation.php

System Specifications
- FreeBSD 8.2-STABLE-201105
- apache-worker-2.2.19
- php5-5.3.6_1
- php5-extensions-1.5
- mysql-server-5.5.13
- ap22-mod_fastcgi-2.4.6_1
- pecl-APC-3.1.9_1
- memcached-1.4.5_2
- pecl-memcached-1.0.2

Install Apache
# cd /usr/ports/www/apache22-worker-mpm ; make install

Install PHP5
# cd /usr/ports/lang/php5 ; make install
or
# cd /usr/ports/lang/php52 ; make install

FPM=on "Build FPM version (experimental)"
MULTIBYTE=on "Enable zend multibyte support"
SUHOSIN=on "Enable Suhosin protection system"
MAILHEAD=on "Enable mail header patch"
LINKTHR=on "Link thread lib (for threaded extensions)"

/usr/ports/lang/php52 # make showconfig
===> The following configuration options are available for php52-5.2.17_2:
CLI=on "Build CLI version"
CGI=on "Build CGI version"
APACHE=off "Build Apache module"
DEBUG=off "Enable debug"
SUHOSIN=on "Enable Suhosin protection system (not for jails)"
MULTIBYTE=on "Enable zend multibyte support"
IPV6=off "Enable ipv6 support"
MAILHEAD=on "Enable mail header patch"
REDIRECT=off "Enable force-cgi-redirect support (CGI only)"
DISCARD=off "Enable discard-path support (CGI only)"
FASTCGI=on "Enable fastcgi support (CGI only)"
FPM=on "Enable fastcgi process manager (CGI only)"
PATHINFO=on "Enable path-info-check support (CGI only)"
LINKTHR=on "Link thread lib (for threaded extensions)"
===> Use 'make config' to modify these settings

Copy php.ini file
// For PHP5.3
# cp /usr/local/etc/php.ini-production /usr/local/etc/php.ini

// For PHP5.2
# cp /usr/local/etc/php.ini-recommended /usr/local/etc/php.ini

Install PHP Extensions
# cd /usr/ports/lang/php52-extensions ; make install

/usr/ports/lang/php52-extensions # make showconfig
===> The following configuration options are available for php52-extensions-1.3_1:
BCMATH=off "bc style precision math functions"
BZ2=off "bzip2 library support"
CALENDAR=off "calendar conversion support"
CTYPE=on "ctype functions"
CURL=on "CURL support"
DBA=off "dba support"
DBASE=off "dBase library support"
DOM=on "DOM support"
EXIF=off "EXIF support"
FILEINFO=off "fileinfo support"
FILTER=on "input filter support"
FRIBIDI=off "FriBidi support"
FTP=off "FTP support"
GD=on "GD library support"
GETTEXT=on "gettext library support"
GMP=off "GNU MP support"
HASH=on "HASH Message Digest Framework"
ICONV=on "iconv support"
IMAP=off "IMAP support"
INTERBASE=off "Interbase 6 database support (Firebird)"
JSON=on "JavaScript Object Serialization support"
LDAP=off "OpenLDAP support"
MBSTRING=on "multibyte string support"
MCRYPT=off "Encryption support"
MHASH=off "Crypto-hashing support"
MING=off "ming shockwave flash support"
MSSQL=off "MS-SQL database support"
MYSQL=off "MySQL database support"
MYSQLI=on "MySQLi database support"
NCURSES=off "ncurses support (CLI only)"
ODBC=off "unixODBC support"
OPENSSL=off "OpenSSL support"
PCNTL=off "pcntl support (CLI only)"
PCRE=on "Perl Compatible Regular Expression support"
PDF=off "PDFlib support (implies GD)"
PDO=on "PHP Data Objects Interface (PDO)"
PDO_SQLITE=on "PDO sqlite driver"
PDO_MYSQL=on "PDO mysql driver"
PGSQL=off "PostgreSQL database support"
POSIX=on "POSIX-like functions"
PSPELL=off "pspell support"
READLINE=off "readline support (CLI only)"
RECODE=off "recode support"
SESSION=on "session support"
SHMOP=off "shmop support"
SIMPLEXML=on "simplexml support"
SNMP=off "SNMP support"
SOAP=off "SOAP support"
SOCKETS=off "sockets support"
SPL=on "Standard PHP Library"
SQLITE=on "sqlite support"
SYBASE_CT=off "Sybase database support"
SYSVMSG=off "System V message support"
SYSVSEM=off "System V semaphore support"
SYSVSHM=off "System V shared memory support"
TIDY=off "TIDY support"
TOKENIZER=on "tokenizer support"
WDDX=off "WDDX support (implies XML)"
XML=on "XML support"
XMLREADER=on "XMLReader support"
XMLRPC=off "XMLRPC-EPI support"
XMLWRITER=on "XMLWriter support"
XSL=off "XSL support (Implies DOM)"
YAZ=off "YAZ support (ANSI/NISO Z39.50)"
ZIP=off "ZIP support"
ZLIB=off "ZLIB support"
===> Use 'make config' to modify these settings

Install mod_fastcgi
# cd /usr/ports/www/mod_fastcgi ; make install

Edit httpd.conf
# vim /usr/local/etc/apache22/httpd.conf
LoadModule fastcgi_module libexec/apache22/mod_fastcgi.so
Include etc/apache22/extra/httpd-mpm.conf

Edit php.conf
# vim /usr/local/etc/apache22/Includes/php.conf
<IfModule prefork.c>
  LoadModule php5_module libexec/apache22/libphp5.so
  AddType application/x-httpd-php .php .html
  AddType application/x-httpd-php-source .phps
</IfModule>

<IfModule worker.c>
  FastCGIExternalServer /usr/local/sbin/php-fpm -socket /tmp/php-fpm.sock -idle-timeout 900
  AddHandler php-fastcgi .php
  Action php-fastcgi /usr/local/sbin/php-fpm.fcgi
  ScriptAlias /usr/local/sbin/php-fpm.fcgi /usr/local/sbin/php-fpm

  <Directory /usr/local/sbin>
    Options ExecCGI FollowSymLinks
    SetHandler fastcgi-script
    Order allow,deny
    Allow from all
  </Directory>
</IfModule>

DirectoryIndex index.php index.html index.htm

Edit php-fpm.conf:
# vim /usr/local/etc/php-fpm.conf
#listen = 127.0.0.1:9000
listen = /tmp/php-fpm.sock

listen.owner = www
listen.group = www
listen.mode = 0660

Edit rc.conf
# vim /etc/rc.conf
## fpm
php_fpm_enable="YES"

## apache
apache22_enable="YES"
apache22_http_accept_enable="YES"

Edit no-accf.conf
# vim /usr/local/etc/apache22/Includes/no-accf.conf
<IfDefine NOHTTPACCEPT>
### make sure you do comment out following two lines
### in order to make accf_http module (the 'httpready' Accept Filter) to work.
#AcceptFilter http none
#AcceptFilter https none
</IfDefine>

Start php-fpm, apache22:
# /usr/local/etc/rc.d/php-fpm start
# /usr/local/etc/rc.d/apache22 start

Make sure it's running as MPM Worker
# httpd -V
Server MPM: Worker

Benchmarking Apache to give us an impression of how our current Apache installation performs
# ab -c 100 -n 10000 http://web-test.local/index.php

測試心得
隨便寫個 <?php echo "hello world"; ?> 來測試

我用 ab -c xx -n xxxx 的方式來測試效能及穩定度 , 分別用 20 , 100 , 500 條連線來測試 50000 次 request

大概可以直接說結果 , 就不看數據了

在 20 及 100 條連線下 , mod_php 的執行時間大約在 3~4 秒 , php-fpm 則在 7~8 秒 , 看樣子 php-fpm 挺鳥的說 ~~

但 500 條連線就見真章了 , mod_php 會很不穩 , 中間會 lag , 所以測試的數據忽上忽下 , 有時 7 秒 , 有時 10 秒甚至 15 秒

而 php-fpm 仍然在 7~8 秒 , 非常穩定

這代表甚麼 ????

Apache worker 模式 + php-fpm 在一個連線數量很多很操的 Server 上會表現得很穩定 , 另外我要說一下 , mod_fastcgi 其實效能並不佳 , 很多測試數據都有說比 Apache 自己寫的 mod_fcgid 差很多 , 我自己也玩過 mod_fcgid + php 也是如此 , 但 mod_fastcgi 表現卻比較穩定 , mod_fcgid 在連線數量多及連續 request 次數多時也會有 lag 情形 , 因此, 有興趣尋求最佳php運作環境的人的可以試試看 lighttpd 或 ngix 甚至是號稱比 lighttpd 及 ngix 快很多的 cherokee 來搭配 php-fpm , 應該會有更棒的表現

後來想想 , hello world 在傳輸的數據太少 , 於是再多測試 <?php phpinfo(); ?> 這個方式

也是用 20 /100/500 條連線來 request 1 萬次

20 條連線時 , mod_php 最佳時間為 6.45 秒 , 而 php-fpm 為 6.42 秒 , 差不多 , 而且都不會 lag

100 條連線時 , mod_php 最佳時間為 6.43 秒 ,php-fpm 為 6.37 秒 , 還是差不多 , 而且都不會 lag

500 條連線時 , mod_php 最佳時間為 6.69 秒 , php-fpm 為 6.42 秒 , 看起來還是一樣 , 但 500 條連線時 , mod_php 發生 lag 情形較多次 , 也就是說可能會看到超過 10 秒以上的時間挺多次的

從上面的測試不難發現 , Apache fork 模式的架構仍有一定的瓶頸 , 而且在實際網站應用上不光只有 php , 還有其他的靜態網頁 , 如果使用 Apache+mod
_php 跑 , 這樣靜態網頁的效能會跟著被拖慢 , 而 Apache worker + php-fpm (或 php fastcgi) 的好處是 , Apache 只把 php 的處理權交出去 , 靜態網頁仍由 Apache 處理 , 這樣在整體效能及記憶體用量都會改善許多

若以單項只跑 php 來看 , mod_php 在一個人不多的網站其實是最快的 , 但當流量大到一定程度 , 除非多買硬體啦 , 若要節省成本 , 採用 FastCGI 架構才是最好的方式

Reference:
http://www.brandonturner.net/blog/2009/07/fastcgi_with_php_opcode_cache/
http://voidweb.com/2010/10/get-high-performance-php-fpm-socket-connections/
http://www.pigo.idv.tw/archives/832
http://benpptung.blogspot.com/2010/11/modfcgi-vs.html

1 comment:

Web Development Company said...
This comment has been removed by a blog administrator.