在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插件:

  1. python uwsgiconfig.py --plugin plugins/php
  2. # You can set the path of the php-config script with UWSGICONFIG_PHPPATH.
  3. UWSGICONFIG_PHPPATH=/opt/php53/bin/php-config python uwsgiconfig.py --plugin plugins/php
  4. # or directly specify the directory in which you have installed your php environment
  5. 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:

  1. ./configure --prefix=/usr/local --with-mysql --with-mysqli --with-pdo-mysql --with-gd --enable-mbstring --enable-embed
  2. # That's a good starting point

Ubuntu 10.04 (较新的版本包括官方嵌入libphp的sapi)

  1. # Add ppa with libphp5-embed package
  2. sudo add-apt-repository ppa:l-mierzwa/lucid-php5
  3. # Update to use package from ppa
  4. sudo apt-get update
  5. # Install needed dependencies
  6. sudo apt-get install php5-dev libphp5-embed libonig-dev libqdbm-dev
  7. # Compile uWSGI PHP plugin
  8. python uwsgiconfig --plugin plugins/php

多个PHP版本

有时 (如果你是ISP,那么总是) 你或许在系统中安装了多个PHP版本。在这种情况下,你会需要对每个PHP版本使用一个uWSGI插件:

  1. UWSGICONFIG_PHPDIR=/opt/php51 python uwsgiconfig.py --plugin plugins/php default php51
  2. UWSGICONFIG_PHPDIR=/opt/php52 python uwsgiconfig.py --plugin plugins/php default php52
  3. UWSGICONFIG_PHPDIR=/opt/php53 python uwsgiconfig.py --plugin plugins/php default php53

‘default’是你的服务器核心的构建配置文件。如果你不带一个指定的配置文件构建uWSGI,那么它将会是’default’。

然后,你可以使用 plugins php51 加载一个指定的插件,等等。你不能在同一个uWSGI进程内加载多个PHP版本。

用nginx运行PHP应用

如果你有简单的应用 (基于文件扩展名),那么你可以使用像这样的东东:

  1. location ~ \.php$ {
  2. root /your_document_root;
  3. include uwsgi_params;
  4. uwsgi_modifier1 14;
  5. uwsgi_pass 127.0.0.1:3030;
  6. }

或许你想要检查所有包含字符串 .php 的URI:

  1. location ~ \.php {
  2. root /your_document_root;
  3. include uwsgi_params;
  4. uwsgi_modifier1 14;
  5. uwsgi_pass 127.0.0.1:3030;
  6. }

现在,只需运行带一堆进程的uWSGI服务器:

  1. uwsgi -s :3030 --plugin php -M -p 4
  2. # Or abuse the adaptive process spawning with the --cheaper option
  3. uwsgi -s :3030 --plugin php -M -p 40 --cheaper 4

这将允许多达40个并发php请求,但只会在需要的时候试着生成(或摧毁)worker,维持一个包含4个进程的最小池。

高级配置

默认情况下,PHP插件将会愉悦地执行任何你传给它的脚本。你或许想要用 php-allowed-ext 选项限制到一个扩展名子集。

  1. uwsgi --plugin php --master --socket :3030 --processes 4 --php-allowed-ext .php --php-allowed-ext .inc

无前端服务器运行PHP应用

这是一个样例配置,有一个“公用的”uWSGI实例,它运行一个PHP应用,并提供静态文件。对于例子而言,它有点复杂,但对于棘手配置而言,应该是一个不错的开始点。

  1. [uwsgi]
  2. ; load the required plugins, php is loaded as the default (0) modifier
  3. plugins = http,0:php
  4.  
  5. ; bind the http router to port 80
  6. http = :80
  7. ; leave the master running as root (to allows bind on port 80)
  8. master = true
  9. master-as-root = true
  10.  
  11. ; drop privileges
  12. uid = serena
  13. gid = serena
  14.  
  15. ; our working dir
  16. project_dir = /var/www
  17.  
  18. ; chdir to it (just for fun)
  19. chdir = %(project_dir)
  20. ; check for static files in it
  21. check-static = %(project_dir)
  22. ; ...but skip .php and .inc extensions
  23. static-skip-ext = .php
  24. static-skip-ext = .inc
  25. ; search for index.html when a dir is requested
  26. static-index = index.html
  27.  
  28. ; jail our php environment to project_dir
  29. php-docroot = %(project_dir)
  30. ; ... and to the .php and .inc extensions
  31. php-allowed-ext = .php
  32. php-allowed-ext = .inc
  33. ; and search for index.php and index.inc if required
  34. php-index = index.php
  35. php-index = index.inc
  36. ; set php timezone
  37. php-set = date.timezone=Europe/Rome
  38.  
  39. ; disable uWSGI request logging
  40. disable-logging = true
  41. ; use a max of 17 processes
  42. processes = 17
  43. ; ...but start with only 2 and spawn the others on demand
  44. cheaper = 2

一个更极端的例子,混合了 CGI 和PHP,使用 internal routing 和一点 configuration logic

  1. [uwsgi]
  2. ; load plugins
  3. plugins-dir = /proc/unbit/uwsgi
  4. plugins = cgi,php,router_uwsgi
  5.  
  6. ; set the docroot as a config placeholder
  7. docroot = /accounts/unbit/www/unbit.it
  8.  
  9. ; reload whenever this config file changes
  10. ; %p is the full path of the current config file
  11. touch-reload = %p
  12.  
  13. ; set process names to something meaningful
  14. auto-procname = true
  15. procname-prefix-spaced = [unbit.it]
  16.  
  17. ; run with at least 2 processes but increase up to 8 when needed
  18. master = true
  19. processes = 8
  20. cheaper = 2
  21.  
  22. ; check for static files in the docroot
  23. check-static = %(docroot)
  24. ; check for cgi scripts in the docroot
  25. cgi = %(docroot)
  26.  
  27. logto = /proc/unbit/unbit.log
  28. ;rotate logs when filesize is higher than 20 megs
  29. log-maxsize = 20971520
  30.  
  31. ; a funny cycle using 1.1 config file logic
  32. for = .pl .py .cgi
  33. static-skip-ext = %(_)
  34. static-index = index%(_)
  35. cgi-allowed-ext = %(_)
  36. endfor =
  37.  
  38. ; map cgi modifier and helpers
  39. ; with this trick we do not need to give specific permissions to cgi scripts
  40. cgi-helper = .pl=perl
  41. route = \.pl$ uwsgi:,9,0
  42. cgi-helper = .cgi=perl
  43. route = \.cgi$ uwsgi:,9,0
  44. cgi-helper = .py=python
  45. route = \.py$ uwsgi:,9,0
  46.  
  47. ; map php modifier as the default
  48. route = .* uwsgi:,14,0
  49. static-skip-ext = .php
  50. php-allowed-ext = .php
  51. php-allowed-ext = .inc
  52. php-index = index.php
  53.  
  54. ; show config tree on startup, just to see
  55. ; how cool is 1.1 config logic
  56. 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函数。

  1. from uwsgidecorators import *
  2.  
  3. # define a python function exported via uwsgi rpc api
  4. @rpc('hello')
  5. def hello(arg1, arg2, arg3):
  6. return "%s-%s-%s" (arg3, arg2, arg1)
  1. Python says the value is <? echo uwsgi_rpc("", "hello", "foo", "bar", "test"); ?>

设置 uwsgi_rpc 的第一个参数为空,将会触发本地rpc。

或者你可以共享uWSGI cache

  1. uwsgi.cache_set("foo", "bar")
  1. <? echo uwsgi_cache_get("foo"); ?>

uWSGI缓存之上的会话 (uWSGI >=2.0.4)

从uWSGI 2.0.4起,你可以将PHP会话存储在uWSGI缓存中。

  1. [uwsgi]
  2. plugins = php
  3. http-socket = :9090
  4. http-socket-modifier1 = 14
  5. ; create a cache with 1000 items named 'mysessions'
  6. cache2 = name=mysessions,items=1000
  7. ; set the 'uwsgi' session handler
  8. php-set = session.save_handler=uwsgi
  9. ; use the 'mysessions' cache for storing sessions
  10. php-set = session.save_path=mysessions
  11.  
  12. ; or to store sessions in remote caches...
  13. ; use the 'foobar@192.168.173.22:3030' cache for storing sessions
  14. php-set = session.save_path=foobar@192.168.173.22:3030

Zend Opcode Cache (uWSGI >= 2.0.6)

由于某些神秘的原因,在嵌入SAPI中,Opcode Cache是禁用的。

你可以通过告诉PHP引擎运行在apache SAPI之下(使用 php-sapi-name 选项)来绕过这个问题:

  1. [uwsgi]
  2. plugins = php
  3. php-sapi-name = apache
  4. http-socket = :9090
  5. 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()

  1. [uwsgi]
  2. ; base.ini
  3.  
  4. ; force the sapi name to 'apache', this will enable the opcode cache
  5. early-php-sapi-name = apache
  6. ; load a php engine as soon as possible
  7. early-php = true
  8.  
  9. ; ... and wait for fork() requests on /run/php_fork.socket
  10. fork-server = /run/php_fork.socket

然后2个vassal

  1. [emperor]
  2. ; tell the emperor the address of the fork server
  3. fork-server = /run/php_fork.socket
  4.  
  5. [uwsgi]
  6. ; bind to port :4001
  7. socket = 127.0.0.1:4001
  8. ; force all requests to be mapped to php
  9. socket-modifier1 = 14
  10. ; enforce a DOCUMENT_ROOT
  11. php-docroot = /var/www/one
  12. ; drop privileges
  13. uid = one
  14. gid = one
  1. [emperor]
  2. ; tell the emperor the address of the fork server
  3. fork-server = /run/php_fork.socket
  4.  
  5. [uwsgi]
  6. ; bind to port :4002
  7. socket = 127.0.0.1:4002
  8. ; force all requests to be mapped to php
  9. socket-modifier1 = 14
  10. ; enforce a DOCUMENT_ROOT
  11. php-docroot = /var/www/two
  12. ; drop privileges
  13. uid = two
  14. gid = two

这两个vassal是完全无关的 (即使它们是从同一个父亲那里fork过来的),所以你可以移除特权,使用不同的进程策略,等等。

现在生成Emperor:

  1. 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()。