在uWSGI中运行PHP脚本¶
You can safely run PHP scripts using 你可以通过uWSGI的 CGI 支持安全运行PHP脚本。这个方法的缺点是每次请求生成新的PHP解释器引发的延迟。
要获得超级不错的性能,你会想要嵌入PHP解释器到uWSGI核心中,并使用PHP插件。
构建¶
一堆发行版 (例如Fedora, Red Hat和CentOS) 包含了一个 php-embedded
包。安装它,以及 php-devel
,然后你应该能够构建php插件:
python uwsgiconfig.py --plugin plugins/php
# You can set the path of the php-config script with UWSGICONFIG_PHPPATH.
UWSGICONFIG_PHPPATH=/opt/php53/bin/php-config python uwsgiconfig.py --plugin plugins/php
# or directly specify the directory in which you have installed your php environment
UWSGICONFIG_PHPDIR=/opt/php53 python uwsgiconfig.py --plugin plugins/php
如果有链接问题 (例如找不到库),那么安装那些缺失的包 (ncurses-devel
, gmp-devel
, pcre-devel
...),但是要警告下你,如果你添加了修改uWSGI核心行为的开发包 (pcre
就是其中一个),那么你也 _需要_ 重新编译uWSGI服务器,否则会引发奇怪的问题。
对于那些不提供一个libphp包的发行版 (例如,所有基于Debian的发行版),你必须在 ./configure
中带上 --enable-embed
标志来重新构建PHP:
./configure --prefix=/usr/local --with-mysql --with-mysqli --with-pdo-mysql --with-gd --enable-mbstring --enable-embed
# That's a good starting point
Ubuntu 10.04 (较新的版本包括官方嵌入libphp的sapi)¶
# Add ppa with libphp5-embed package
sudo add-apt-repository ppa:l-mierzwa/lucid-php5
# Update to use package from ppa
sudo apt-get update
# Install needed dependencies
sudo apt-get install php5-dev libphp5-embed libonig-dev libqdbm-dev
# Compile uWSGI PHP plugin
python uwsgiconfig --plugin plugins/php
多个PHP版本¶
有时 (如果你是ISP,那么总是) 你或许在系统中安装了多个PHP版本。在这种情况下,你会需要对每个PHP版本使用一个uWSGI插件:
UWSGICONFIG_PHPDIR=/opt/php51 python uwsgiconfig.py --plugin plugins/php default php51
UWSGICONFIG_PHPDIR=/opt/php52 python uwsgiconfig.py --plugin plugins/php default php52
UWSGICONFIG_PHPDIR=/opt/php53 python uwsgiconfig.py --plugin plugins/php default php53
‘default’是你的服务器核心的构建配置文件。如果你不带一个指定的配置文件构建uWSGI,那么它将会是’default’。
然后,你可以使用 plugins php51
加载一个指定的插件,等等。你不能在同一个uWSGI进程内加载多个PHP版本。
用nginx运行PHP应用¶
如果你有简单的应用 (基于文件扩展名),那么你可以使用像这样的东东:
location ~ \.php$ {
root /your_document_root;
include uwsgi_params;
uwsgi_modifier1 14;
uwsgi_pass 127.0.0.1:3030;
}
或许你想要检查所有包含字符串 .php
的URI:
location ~ \.php {
root /your_document_root;
include uwsgi_params;
uwsgi_modifier1 14;
uwsgi_pass 127.0.0.1:3030;
}
现在,只需运行带一堆进程的uWSGI服务器:
uwsgi -s :3030 --plugin php -M -p 4
# Or abuse the adaptive process spawning with the --cheaper option
uwsgi -s :3030 --plugin php -M -p 40 --cheaper 4
这将允许多达40个并发php请求,但只会在需要的时候试着生成(或摧毁)worker,维持一个包含4个进程的最小池。
高级配置¶
默认情况下,PHP插件将会愉悦地执行任何你传给它的脚本。你或许想要用 php-allowed-ext
选项限制到一个扩展名子集。
uwsgi --plugin php --master --socket :3030 --processes 4 --php-allowed-ext .php --php-allowed-ext .inc
无前端服务器运行PHP应用¶
这是一个样例配置,有一个“公用的”uWSGI实例,它运行一个PHP应用,并提供静态文件。对于例子而言,它有点复杂,但对于棘手配置而言,应该是一个不错的开始点。
[uwsgi]
; load the required plugins, php is loaded as the default (0) modifier
plugins = http,0:php
; bind the http router to port 80
http = :80
; leave the master running as root (to allows bind on port 80)
master = true
master-as-root = true
; drop privileges
uid = serena
gid = serena
; our working dir
project_dir = /var/www
; chdir to it (just for fun)
chdir = %(project_dir)
; check for static files in it
check-static = %(project_dir)
; ...but skip .php and .inc extensions
static-skip-ext = .php
static-skip-ext = .inc
; search for index.html when a dir is requested
static-index = index.html
; jail our php environment to project_dir
php-docroot = %(project_dir)
; ... and to the .php and .inc extensions
php-allowed-ext = .php
php-allowed-ext = .inc
; and search for index.php and index.inc if required
php-index = index.php
php-index = index.inc
; set php timezone
php-set = date.timezone=Europe/Rome
; disable uWSGI request logging
disable-logging = true
; use a max of 17 processes
processes = 17
; ...but start with only 2 and spawn the others on demand
cheaper = 2
一个更极端的例子,混合了 CGI 和PHP,使用 internal routing 和一点 configuration logic 。
[uwsgi]
; load plugins
plugins-dir = /proc/unbit/uwsgi
plugins = cgi,php,router_uwsgi
; set the docroot as a config placeholder
docroot = /accounts/unbit/www/unbit.it
; reload whenever this config file changes
; %p is the full path of the current config file
touch-reload = %p
; set process names to something meaningful
auto-procname = true
procname-prefix-spaced = [unbit.it]
; run with at least 2 processes but increase up to 8 when needed
master = true
processes = 8
cheaper = 2
; check for static files in the docroot
check-static = %(docroot)
; check for cgi scripts in the docroot
cgi = %(docroot)
logto = /proc/unbit/unbit.log
;rotate logs when filesize is higher than 20 megs
log-maxsize = 20971520
; a funny cycle using 1.1 config file logic
for = .pl .py .cgi
static-skip-ext = %(_)
static-index = index%(_)
cgi-allowed-ext = %(_)
endfor =
; map cgi modifier and helpers
; with this trick we do not need to give specific permissions to cgi scripts
cgi-helper = .pl=perl
route = \.pl$ uwsgi:,9,0
cgi-helper = .cgi=perl
route = \.cgi$ uwsgi:,9,0
cgi-helper = .py=python
route = \.py$ uwsgi:,9,0
; map php modifier as the default
route = .* uwsgi:,14,0
static-skip-ext = .php
php-allowed-ext = .php
php-allowed-ext = .inc
php-index = index.php
; show config tree on startup, just to see
; how cool is 1.1 config logic
show-config = true
uWSGI API支持¶
对一些uWSGI API的初期支持已经在1.1版本添加了。这是支持函数的列表:
- uwsgi_version()
- uwsgi_setprocname($name)
- uwsgi_worker_id()
- uwsgi_masterpid()
- uwsgi_signal($signum)
- uwsgi_rpc($node, $func, ...)
- uwsgi_cache_get($key)
- uwsgi_cache_set($key, $value)
- uwsgi_cache_update($key, $value)
- uwsgi_cache_del($key)
是哒,这意味着你可以使用RPC,从PHP调用Python函数。
from uwsgidecorators import *
# define a python function exported via uwsgi rpc api
@rpc('hello')
def hello(arg1, arg2, arg3):
return "%s-%s-%s" (arg3, arg2, arg1)
Python says the value is <? echo uwsgi_rpc("", "hello", "foo", "bar", "test"); ?>
设置 uwsgi_rpc
的第一个参数为空,将会触发本地rpc。
或者你可以共享uWSGI cache...
uwsgi.cache_set("foo", "bar")
<? echo uwsgi_cache_get("foo"); ?>
uWSGI缓存之上的会话 (uWSGI >=2.0.4)¶
从uWSGI 2.0.4起,你可以将PHP会话存储在uWSGI缓存中。
[uwsgi]
plugins = php
http-socket = :9090
http-socket-modifier1 = 14
; create a cache with 1000 items named 'mysessions'
cache2 = name=mysessions,items=1000
; set the 'uwsgi' session handler
php-set = session.save_handler=uwsgi
; use the 'mysessions' cache for storing sessions
php-set = session.save_path=mysessions
; or to store sessions in remote caches...
; use the '[email protected]:3030' cache for storing sessions
php-set = [email protected]:3030
Zend Opcode Cache (uWSGI >= 2.0.6)¶
由于某些神秘的原因,在嵌入SAPI中,Opcode Cache是禁用的。
你可以通过告诉PHP引擎运行在apache SAPI之下(使用 php-sapi-name
选项)来绕过这个问题:
[uwsgi]
plugins = php
php-sapi-name = apache
http-socket = :9090
http-socket-modifier1 = 14
ForkServer (uWSGI >= 2.1)¶
Fork服务器 (由Intellisurvey赞助) 是2.1分支的主要特性之一。它允许你从指定的父亲那里继承你的vassal,而不是Emperor。
PHP插件已被扩展,来支持fork服务器,所以你可以拥有一个php基本实例池,其中,vassal可以 fork() 。这意味着,你可以共享opcode cache以及做其他花样。
多亏了uWSGI 2.1中的vassal属性,我们可以选择一个vassal将从哪个父亲中调用fork()。
注解
你需要Linux内核 >= 3.4 (这个特性要求 PR_SET_CHILD_SUBREAPER
) 以获得“稳定”使用。否则,你的Emperor将不能够正确wait()孩子(children) (这将会减缓你的vassal的重新生成,并且会导致各种形式的竞争条件)。
在下面的例子中,我们将会生成3个vassal,一个 (称为base.ini) 将会初始化一个PHP引擎,而其他两个将会从第一个 fork() 。
[uwsgi]
; base.ini
; force the sapi name to 'apache', this will enable the opcode cache
early-php-sapi-name = apache
; load a php engine as soon as possible
early-php = true
; ... and wait for fork() requests on /run/php_fork.socket
fork-server = /run/php_fork.socket
然后2个vassal
[emperor]
; tell the emperor the address of the fork server
fork-server = /run/php_fork.socket
[uwsgi]
; bind to port :4001
socket = 127.0.0.1:4001
; force all requests to be mapped to php
socket-modifier1 = 14
; enforce a DOCUMENT_ROOT
php-docroot = /var/www/one
; drop privileges
uid = one
gid = one
[emperor]
; tell the emperor the address of the fork server
fork-server = /run/php_fork.socket
[uwsgi]
; bind to port :4002
socket = 127.0.0.1:4002
; force all requests to be mapped to php
socket-modifier1 = 14
; enforce a DOCUMENT_ROOT
php-docroot = /var/www/two
; drop privileges
uid = two
gid = two
这两个vassal是完全无关的 (即使它们是从同一个父亲那里fork过来的),所以你可以移除特权,使用不同的进程策略,等等。
现在生成Emperor:
uwsgi --emperor phpvassals/ --emperor-collect-attr fork-server --emperor-fork-server-attr fork-server
--emperor-collect-attr
迫使Emperor在vassal文件的[emperor]部分搜索’fork-server’属性,而--emperor-fork-server-attr
告诉它使用这个参数作为fork服务器的地址。
显然,如果一个vassal不公开这么一个属性,那么它将会正常地从Emperor fork()。