uWSGI项目¶
uWSGI项目旨在为构建托管服务开发全栈。
使用通用的API和通用的配置风格来实现应用服务器 (对于各种编程语言和协议),代理,进程管理器和监控器。
由于其可插拔架构,可以对其扩展以支持更多的平台和语言。
目前,你可以用C, C++和Objective-C来编写插件。
名字中的”WSGI”部分归功于同名Python标准,因为它是该项目第一个开发的插件。
通用性、高性能、低资源使用和可靠性是该项目的强项(也是唯一遵循的法则)。
包含的组件(更新至最新的稳定版本)¶
核心 (实现配置、进程管理、socket创建、监控、日志记录、共享内存块、ipc、集群成员和 uWSGI订阅服务器)
请求插件 (为各种语言和平台实现应用服务器接口:WSGI, PSGI, Rack, Lua WSAPI, CGI, PHP, Go ...)
网关 (实现负载均衡器、代理和路由器)
Emperor (实现大量实例管理和监控)
循环引擎 (实现事件和并发,组件可以在reforking, threaded, asynchronous/evented和green thread/coroutine模式下运行。支持多种技术,包括uGreen, Greenlet, Stackless, Gevent, Coro::AnyEvent, Tornado, Goroutines和Fibers)
注解
uWSGI是一个具有快速发布周期的非常活跃的项目。出于这个原因,代码和文档可能不总是同步。我们试着尽最大的努力维护好文档,但这是一个艰苦的工作。为此表示歉意。如果你碰到问题了,那么邮件列表是寻求有关uWSGI的帮助的最佳来源。欢迎文档(除代码之外)贡献者。
快速入门¶
Python/WSGI应用快速入门¶
这个快速入门将会告诉你如何部署简单的WSGI应用和常见的web框架。
这里,Python指的是CPython,对于PyPy,你需要使用特定的插件: PyPy插件,Jython支持正在开发中。
注解
要遵循此快速入门,你至少需要uWSGI 1.4。更老的版本不再维护,并且有灰常多bug!
安装uWSGI以及Python支持¶
uWSGI是一个(大的)C应用,因此,你需要一个C编译器 (例如gcc或者clang),以及Python开发头文件。
在基于Debian的发行版上,
apt-get install build-essential python-dev
就够了。
你有多种方式为Python安装uWSGI:
via pip
pip install uwsgi
使用网络安装器
curl http://uwsgi.it/install | bash -s default /tmp/uwsgi
(这将会把uWSGI二进制安装到
/tmp/uwsgi
,你可以随意修改它)。通过下载源tarball文件,然后执行”make”命令
wget http://projects.unbit.it/downloads/uwsgi-latest.tar.gz tar zxvf uwsgi-latest.tar.gz cd <dir> make
(构建之后,当前目录下,你会获得一个
uwsgi
二进制文件)。
未涵盖通过包发布来安装(不可能取悦所有人),但是所有的一般规则都适用。
当使用发行版提供的包来测试这个快速入门时,你也许想要考虑一件事,就是非常有可能你的发行版本以模块化的方式构建了uWSGI (每个特性都是一个必须加载的不同插件)。要完成这个快速入门,你必须在第一个系列的例子前面加上 --plugin python,http
,以及当移除了HTTP路由器时加上 --plugin python
(如果这对你没意义,那就请继续往下读)。
第一个WSGI应用¶
让我们从一个简单的”Hello World”开始:
def application(env, start_response):
start_response('200 OK', [('Content-Type','text/html')])
return [b"Hello World"]
(将其保存为 foobar.py
)。
正如你所见,它由一个简单的Python函数组成。之所以称之为 “application”,是因为它是uWSGI Python加载器将会搜索的默认函数 (但你显然可以自定义)。
将其部署在HTTP端口9090¶
现在,启动uWSGI来运行一个HTTP服务器/路由器,它会传递请求到你的WSGI应用:
uwsgi --http :9090 --wsgi-file foobar.py
就这样。
注解
当你有一个前端web服务器,或者你正进行某些形式的基准时,不要使用 --http
,使用 --http-socket
。继续阅读快速入门来了解原因。
添加并发和监控¶
你想进行的第一个调整可能是增加并发性 (默认情况下,uWSGI启动一个单一的进程和一个单一的线程)。
你可以用 --processes
选项添加更多的进程,或者使用 --threads
选项添加更多的线程 (或者可以同时添加)。
uwsgi --http :9090 --wsgi-file foobar.py --master --processes 4 --threads 2
这将会生成4个进程 (每个进程有2个线程),一个master进程 (在Inc死掉的时候会生成它们) 和HTTP路由器 (见前面)。
一个重要的任务是监控。在生产部署上,了解正在发生的事情是至关重要的。stats子系统允许你将uWSGI的内部统计数据作为JSON导出:
uwsgi --http :9090 --wsgi-file foobar.py --master --processes 4 --threads 2 --stats 127.0.0.1:9191
对你的应用进行几次请求,然后telnet到端口9191,你会获得大量有趣的信息。你可能想要使用”uwsgitop” (仅需 pip install
来安装它),这是一个类似于top的工具,用来监控实例。
注解
绑定stats socket到一个私有地址 (除非你知道你在做什么),否则,每个人都能够访问它!
将它放在一个完整的web服务器之后¶
即使uWSGI HTTP路由器是稳定并且高性能的,但是你或许想要将你的应用放在一个全功能的web服务器之后。
uWSGI原生支持HTTP, FastCGI, SCGI及其特定的名为”uwsgi”的协议 (是哒,错误的命名选择)。最好的协议显然是uwsgi,nginx和Cherokee已经支持它了 (虽然有各种Apache模块可用)
一个常用的nginx配置如下:
location / {
include uwsgi_params;
uwsgi_pass 127.0.0.1:3031;
}
这表示“传递每一个请求给绑定到3031端口并使用uwsgi协议的服务器”。
现在,我们可以生成uWSGI来本地使用uwsgi协议:
uwsgi --socket 127.0.0.1:3031 --wsgi-file foobar.py --master --processes 4 --threads 2 --stats 127.0.0.1:9191
如果你要运行 ps aux
,那么你会看到一个进程。已经移除了HTTP路由器,因为我们的“worker” (被分配给uWSGI的进程) 本地使用uwsgi协议。
如果你的代理/web服务器/路由器使用HTTP,那么你必须告诉uWSGI本地使用http协议 (这与会自己生成一个代理的–http不同):
uwsgi --http-socket 127.0.0.1:3031 --wsgi-file foobar.py --master --processes 4 --threads 2 --stats 127.0.0.1:9191
开机自动启动uWSGI¶
如果你在想打开vi,然后写一个init.d脚本来生成uWSGI,那么请坐下(并且冷静下来),先确保你的系统没有提供一个更好(更现代)的方法。
每个发行版本都选择了一个启动系统 (Upstart, Systemd...) ,并且有大量可用的进程管理器 (supervisord, god, monit, circus...)。
uWSGI会跟它们都很好地集成 (我们希望是这样的),但如果你计划部署大量的应用,那么看看uWSGI Emperor - 它或多或少是每个devops工程师的梦想。
部署Django¶
Django大概是最常使用的Python web框架了。部署它是相当容易的 (我们继续配置4个进程,每个进程有2个线程)。
假设Django工程位于 /home/foobar/myproject
:
uwsgi --socket 127.0.0.1:3031 --chdir /home/foobar/myproject/ --wsgi-file myproject/wsgi.py --master --processes 4 --threads 2 --stats 127.0.0.1:9191
(使用 --chdir
,我们移到指定的目录下)。在Django中,需要使用它来正确加载模块。
哎呀!这是什么鬼?!是哒,你是对的,对的……处理这样长的命令行并不实际,并且愚蠢而易于犯错。不要害怕!uWSGI支持多种配置风格。在这个入门中,我们会使用.ini文件。
[uwsgi]
socket = 127.0.0.1:3031
chdir = /home/foobar/myproject/
wsgi-file = myproject/wsgi.py
processes = 4
threads = 2
stats = 127.0.0.1:9191
好得多了!
仅需运行:
uwsgi yourfile.ini
如果文件 /home/foobar/myproject/myproject/wsgi.py
(或者任何你的工程的名字) 并不存在,那么你很有可能使用的是Django的一个老(< 1.4)版本。在这种情况下,你需要多一点配置:
uwsgi --socket 127.0.0.1:3031 --chdir /home/foobar/myproject/ --pythonpath .. --env DJANGO_SETTINGS_MODULE=myproject.settings --module "django.core.handlers.wsgi:WSGIHandler()" --processes 4 --threads 2 --stats 127.0.0.1:9191
或者,使用.ini文件:
[uwsgi]
socket = 127.0.0.1:3031
chdir = /home/foobar/myproject/
pythonpath = ..
env = DJANGO_SETTINGS_MODULE=myproject.settings
module = django.core.handlers.wsgi:WSGIHandler()
processes = 4
threads = 2
stats = 127.0.0.1:9191
更老的(< 1.4)Django发布版本需要设置 env
, module
和 pythonpath
(..
允许我们访问 myproject.settings
模块)。
部署Flask¶
Flask是一个流行的Python web微框架。
将下面例子保存为 myflaskapp.py
:
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
return "<span style='color:red'>I am app 1</span>"
Flask将其WSGI函数 (就是那个在这个快速入门开始的地方我们称为”application”的那个)导出为”app”,因此,我们需要指示uWSGI使用它。我们仍然使用4个进程/2个线程,以及uwsgi socket:
uwsgi --socket 127.0.0.1:3031 --wsgi-file myflaskapp.py --callable app --processes 4 --threads 2 --stats 127.0.0.1:9191
(唯一添加的是 --callable
选项)。
部署web2py¶
又一个流行选择。解压缩web2py源发布版本到所选的目录下,然后编写一个uWSGI配置文件:
[uwsgi]
http = :9090
chdir = path_to_web2py
module = wsgihandler
master = true
processes = 8
注解
在近期的web2py发布版本中,你可能需要将 wsgihandler.py
脚本拷贝出 handlers
目录。
再次使用HTTP路由器。仅需在浏览器中访问端口9090,你就能看到web2py欢迎页面。
点击管理员界面,然后……哎哟,不能用,因为需要HTTPS。不要担心,uWSGI路由器是可以使用HTTPS的 (确保你有OpenSSL开发头文件:安装它们,然后重新构建uWSGI,构建系统将会自动检测到它)。
首先,生成密钥和证书:
openssl genrsa -out foobar.key 2048
openssl req -new -key foobar.key -out foobar.csr
openssl x509 -req -days 365 -in foobar.csr -signkey foobar.key -out foobar.crt
现在,你有了2个文件 (好吧,算上 foobar.csr
,是3个), foobar.key
和 foobar.crt
。修改uWSGI配置:
[uwsgi]
https = :9090,foobar.crt,foobar.key
chdir = path_to_web2py
module = wsgihandler
master = true
processes = 8
重新运行uWSGI,并且在浏览器中使用 https://
访问9090端口。
关于Python线程的注意事项¶
如果你在不使用线程的情况下启动uWSGI,那么Python GIL将不会启动,因此,你的应用生成的线程将不会运行。你或许不会喜欢这个选择,但是记住,uWSGI是一个语言无关的服务器,因此它的大多数选择都是为了维护它的“不可知论”。
但是,不用担心,uWSGI开发者做的选择基本上没有选项不能改变的。(也就是说,基本上你可以通过选项改变它)
如果你想要维护Python线程支持,而不为你的应用启动多线程,那么仅需添加 --enable-threads
选项 (或者在ini风格的文件中添加 enable-threads = true
)。
安全性和可用性¶
总是 避免以root用户运行你的uWSGI实例。你可以使用 uid
和 gid
选项来去除权限:
[uwsgi]
https = :9090,foobar.crt,foobar.key
uid = foo
gid = bar
chdir = path_to_web2py
module = wsgihandler
master = true
processes = 8
如果你需要绑定到特许端口 (例如用于HTTPS的443),那么使用共享socket。它们在去除权限之前创建,并且可以通过 =N
语法引用,其中, N
是socket号 (从0开始):
[uwsgi]
shared-socket = :443
https = =0,foobar.crt,foobar.key
uid = foo
gid = bar
chdir = path_to_web2py
module = wsgihandler
master = true
processes = 8
web应用部署的一个常见问题是“卡住的请求”。你所有的线程/worker都卡住了 (请求阻塞) ,而你的应用无法接收更多的请求。要避免这个问题,你可以设置一个 harakiri
定时器。它是一个监控器 (由master进程管理),会摧毁那些卡住超过指定秒数的进程 (小心选择 harakiri
值)。例如,你也许想要摧毁那些阻塞超过30秒的worker:
[uwsgi]
shared-socket = :443
https = =0,foobar.crt,foobar.key
uid = foo
gid = bar
chdir = path_to_web2py
module = wsgihandler
master = true
processes = 8
harakiri = 30
除此之外,自uWSGI 1.9起,stats服务器导出了全部请求变量,因此,你可以(实时)看到你的实例正在做什么 (对于每个worker, thread 或者异步核)。
卸载¶
uWSGI 卸载(offloading)子系统 允许你在特定模式匹配的适合尽快地释放你的worker,并且可以被委派给一个纯C线程。举几个栗子,从文件系统发送静态文件,从网络传输数据到客户端,等等。
卸载是非常复杂的,但它的使用对最终用户是透明的。如果你想试一试,那么仅需添加 --offload-threads <n>
,其中,<n>是要生成的线程数 (每个CPU1个是开始的一个不错的值)。
当启用了卸载线程,将会自动检测所有可以被优化的部分。
彩蛋:对不同Python版本使用相同的uWSGI二进制文件¶
正如我们所见的,uWSGI是由一个小核心和各种插件组成的。插件可以嵌入到二进制文件中,或者动态加载。当你为Python构建uWSGI的时候,一系列的插件,加上Python本身将会嵌入到最后的二进制文件中。
如果你想要支持多个Python版本,而不想为每个版本构建一个二进制文件的适合,这会是个问题。
最好的方法可能是使用一个小的二进制文件,其中内建语言无关的特性,然后为每个Python版本准备一个插件,按需加载。
在uWSGI源代码目录中:
make PROFILE=nolang
这将会构建一个uwsgi二进制文件,它内建了除了Python之外所有默认的插件。
现在,在相同的目录下,我们开始构建Python插件:
PYTHON=python3.4 ./uwsgi --build-plugin "plugins/python python34"
PYTHON=python2.7 ./uwsgi --build-plugin "plugins/python python27"
PYTHON=python2.6 ./uwsgi --build-plugin "plugins/python python26"
最后,你将获得三个文件: python34_plugin.so
, python27_plugin.so
, python26_plugin.so
。拷贝这些到你想要的目录下。 (默认情况下,uWSGI会在当前工作目录下搜索插件。)
现在,在你的配置文件中,你可以简单地添加 (在最顶部) plugins-dir 和 plugin 指令。
[uwsgi]
plugins-dir = <path_to_your_plugin_directory>
plugin = python26
这将会从目录下加载 python26_plugin.so
插件库到你拷贝插件到的那里。
现在……¶
有了这些概念,你应该已经能够投入生产了,但是uWSGI是一个庞大的项目,它有数百种特性和配置。如果你想成为一个更好的系统管理员,那么继续阅读整个文档吧。
perl/PSGI应用快速入门¶
以下指导将会代理安装和运行一个基于perl的uWSGI发行版,旨在运行PSGI应用。
安装带Perl支持的uWSGI¶
要构建uWSGI,你需要一个c编译器 (支持gcc和clang) 和python二进制文件 (它将只运行uwsgiconfig.py脚本,这个脚本将会执行各种编译步骤)。由于我们要构建一个带perl支持的uWSGI二进制文件,因此我们也需要perl开发头文件 (基于debian发行版的libperl-dev包)
你可以手工构建uWSGI:
python uwsgiconfig.py --build psgi
这与下面相同
UWSGI_PROFILE=psgi make
或者使用网络安装程序:
curl http://uwsgi.it/install | bash -s psgi /tmp/uwsgi
那会在/tmp/uwsgi中创建一个uWSGI二进制文件 (随意修改路径为任意你想要的)
发行版包的注意事项¶
你的发行版非常有可能包含了一个uWSGI包集,所以,除了核心部分之外,你需要安装所需插件。必须在你的配置中加载插件。在学习阶段,我们强烈建议不要使用发行版包,才能轻松遵循文档和教程。
一旦你适应了“uWSGI方式”,你就可以为你的部署选择最佳方法。
你第一个PSGI应用¶
将其保存为一个名为myapp.pl的文件
my $app = sub {
my $env = shift;
return [
'200',
[ 'Content-Type' => 'text/html' ],
[ "<h1>Hello World</h1>" ],
];
};
然后通过uWSGI,在http模式下运行它:
uwsgi --http :8080 --http-modifier1 5 --psgi myapp.pl
(如果’uwsgi’不在你当前的$PATH中,那么记得替换它)
或者如果你使用模块化构建 (就像你发行版那样)
uwsgi --plugins http,psgi --http :8080 --http-modifier1 5 --psgi myapp.pl
注解
当你有一个前端web服务器的时候,不要使用–http,使用–http-socket。接着读快速入门以了解为什么。
那个’–http-modifier1 5’是啥鬼东西???¶
uWSGI支持各种的语言和平台。当服务器接收到了一个请求的时候,它必须知道要把它“路由”到哪里。
每个uWSGI插件都被分配了一个数字 (modifier),perl/psgi这个分配到的是5。所以–http-modifier1 5表示”路由到psgi插件”
尽管uWSGI有一个更加”人类友好的” internal routing system ,但是使用modifier是最快的方式,所以,如果有可能的话,总是使用它们吧。
使用一个完整的web服务器:nginx¶
提供的http路由器,只是 (对哒,令人难以置信的) 一个路由器。你可以将它作为一个负载均衡器或者代理使用,但是如果你需要一个完整的web服务器 (来有效地提供静态文件,或者所有web服务器擅长的那些任务),那么你可以摆脱uwsgi http路由器 (如果你使用模块化构建,那么记得修改–plugins http,psgi为–plugins psgi),然后将你的应用放在nginx之后。
要与nginx通信,uWSGI可以使用各种协议:http, uwsgi, fastcgi, scgi...
最有效的是uwsgi那个。Nginx默认包含uwsgi协议支持。
在一个uwsgi socket之上运行你的psgi应用:
uwsgi --socket 127.0.0.1:3031 --psgi myapp.pl
然后在你的nginx配置中添加一个location节
location / {
include uwsgi_params;
uwsgi_pass 127.0.0.1:3031;
uwsgi_modifier1 5;
}
重载你的nginx服务器,它应该开始代理请求到你的uWSGI实例
注意,你不需要配置uWSGI来设置一个指定的modifier,nginx将会使用 uwsgi_modifier1 5;
指令来做到这点
如果你的代理/web服务器/路由器使用HTTP,那么你必须告诉uWSGI原生使用http协议 (这与–http不同,后者自己会生成一个代理):
uwsgi --http-socket 127.0.0.1:3031 --http-socket-modifier1 5 --psgi myapp.pl
正如你所见,我们需要指定使用的modifier1,因为http协议不能够携带这类信息
添加并发¶
你可以通过多进程、多线程或者各种异步模式为你的应用提供并发性。
要生成额外的进程,使用–processes选项
uwsgi --socket 127.0.0.1:3031 --psgi myapp.pl --processes 4
要拥有额外的线程,使用–threads
uwsgi --socket 127.0.0.1:3031 --psgi myapp.pl --threads 8
或者两者一起来
uwsgi --socket 127.0.0.1:3031 --psgi myapp.pl --threads 8 --processes 4
perl世界中一个非常常见的非阻塞/协程库是Coro::AnyEvent。uWSGI可以简单包括 coroae
插件来使用它 (甚至与多进程结合在一起)。
要构建一个带 coroae
支持的uWSGI二进制文件,只需运行
UWSGI_PROFILE=coroae make
或者
curl http://uwsgi.it/install | bash -s coroae /tmp/uwsgi
你将得到一个同时包含了 psgi
和 coroae
插件的uWSGI二进制文件。
现在,以Coro::AnyEvent模式运行你的应用:
uwsgi --socket 127.0.0.1:3031 --psgi myapp.pl --coroae 1000 --processes 4
它将运行4个进程,每个都能够管理多达1000个协程 (或者Coro微线程)。
添加鲁棒性:Master进程¶
高度推荐在生产应用上总是运行master进程。
它将不断监控你的进程/线程,并且会添加有趣的特性,例如 uWSGI Stats服务器
要启用master,只需添加–master
uwsgi --socket 127.0.0.1:3031 --psgi myapp.pl --processes 4 --master
使用配置文件¶
uWSGI有数百个选项。通过命令行与它们打交道基本上是种愚蠢的行为,所以试着一直使用配置文件吧。uWSGI支持各种标准 (xml, .ini, json, yaml...)。从一种移到另一种是相当简单的。你通过命令行可以使用的选项也一样可以在配置文件中使用,只需要移除 --
前缀即可:
[uwsgi]
socket = 127.0.0.1:3031
psgi = myapp.pl
processes = 4
master = true
或者xml:
<uwsgi>
<socket>127.0.0.1:3031</socket>
<psgi>myapp.pl</psgi>
<processes>4</processes>
<master/>
</uwsgi>
要使用配置文件来运行uWSGI,只需将其作为一个参数指定:
uwsgi yourconfig.ini
如果出于某些原因,你的配置不能以期望的扩展名 (.ini, .xml, .yml, .js) 结尾,那么你可以这样强制二进制文件使用一个指定的解析器:
uwsgi --ini yourconfig.foo
uwsgi --xml yourconfig.foo
uwsgi --yaml yourconfig.foo
等等等等
你甚至可以使用管道配置 (使用破折号来强制从标准输入读取):
perl myjsonconfig_generator.pl | uwsgi --json -
自动在开机启动uWSGI¶
如果你在考虑编写一些init.d脚本来生成uWSGI,那么只需坐下(并且冷静下来),检查看看你的系统是否没给你提供一个更好的(更现代的)方法。
每个发行版都选择了一个启动系统 (Upstart, Systemd...),并且有大量可用的进程管理器 (supervisord, god...).
uWSGI将会和它们很好的集成(我们希望是),但是如果你计划部署大量的应用,那么看看uWSGI Emperor ,它是每个devops的梦想。
安全性和可用性¶
总是避免将你的uWSGI实例作为root运行。你可以使用uid和gid选项来移除特权
[uwsgi]
socket = 127.0.0.1:3031
uid = foo
gid = bar
chdir = path_toyour_app
psgi = myapp.pl
master = true
processes = 8
web应用的部署的一个常见问题是“卡住请求”。你所有的线程/worker都卡在一个请求上了,导致你的应用不能再接收请求。
要避免那个问题,你可以设置一个 harakiri
定时器。它是一个监控器 (由master进程管理),它将销毁那些卡住超过指定秒数的进程
[uwsgi]
socket = 127.0.0.1:3031
uid = foo
gid = bar
chdir = path_toyour_app
psgi = myapp.pl
master = true
processes = 8
harakiri = 30
将会摧毁那些阻塞超过30秒的worker。小心选择harakiri的值!!!
除此之外,自uWSGI 1.9起,统计信息服务器导出了整套请求变量,所以你可以看到(实时)你的实例在做什么 (对每个worker,线程或者异步核)
启用统计信息服务器是很容易的:
[uwsgi]
socket = 127.0.0.1:3031
uid = foo
gid = bar
chdir = path_toyour_app
psgi = myapp.pl
master = true
processes = 8
harakiri = 30
stats = 127.0.0.1:5000
只需将其绑定到一个地址 (UNIX或者TCP),然后连接上 (你也可以使用telnet) 它,来接收你的实例的一个JSON表示。
uwsgitop
应用 (你可以在官方的github仓库中找到它) 是一个使用统计信息服务器来实现类top实时监控的工具的例子 (有颜色!!!)
卸载¶
uWSGI 卸载(offloading)子系统 允许你在某些特定的模式匹配的时候尽快释放你的worker,并且能够委托给一个纯c线程。例如,发送来自文件系统的静态文件,从网络传输数据到客户端,等等。
卸载是非常复杂的,但是它的使用对终端用户是透明的。如果你想要试一试,那么只需添加–offload-threads <n>,其中,<n>是要生成的线程数 (一个cpu一个线程是个不错的值)。
当启用了卸载线程时,所有可以被优化的部分将会被自动检测到。
以及现在¶
有了这些许概念,你应该已经可以上生产了,但是uWSGI是个巨大的项目,它有数百个特性和配置。如果你想要成为一个更好的系统管理员,那么请求继续阅读完整的文档。
ruby/Rack应用快速入门¶
以下指导将带你安装和运行一个基于Ruby的uWSGI发行版 ,旨在运行Rack应用。
安装带Ruby支持的uWSGI¶
要构建uWSGI,你需要一个C编译器 (支持gcc和clang) 和Python二进制文件 (用来运行uwsgiconfig.py脚本,它将执行各种编译步骤)。
因为我们正在构建一个带Ruby支持的uWSGI二进制文件,因此我们也需要Ruby开发头文件 (基于Debian发行版上的 ruby-dev
包)。
你可以手工构建uWSGI —— 这些都是等价的:
make rack
UWSGI_PROFILE=rack make
make PROFILE=rack
python uwsgiconfig.py --build rack
但是如果你懒癌犯了,那么你可以一次性下载、构建和安装一个uWSGI + Ruby二进制文件:
curl http://uwsgi.it/install | bash -s rack /tmp/uwsgi
或者以一种更“Ruby友好型”方式:
gem install uwsgi
这些方法都构建一个“单片”uWSGI二进制文件。uWSGI项目是由许多插件注册的。你可以选择构建服务器核心,并为每个特性使用一个插件(需要时加载),或者你可以构建一个带有所有你需要的特性的单个二进制文件。后者这种构建称之为“单片”。
这个快速入门假设使用一个单片二进制文件 (因此你无需加载插件)。如果你更喜欢使用你的包发行版 (而不是从官方来源构建uWSGI),那么见下。
关于发布包的注意事项¶
你的发行版非常有可能包含一个uWSGI包集合。那些uWSGI包趋向于高模块化的 (并且偶尔高度过时),因此,除了核心部分之外,你需要安装所需插件。必须在你的uWSGI配置中加载插件。在学习阶段,我们强烈建议不要使用发行版包,从而轻松跟着文档和教程。
一旦你适应了“uWSGI方式”,你就可以为你的部署选择最佳方式。
例如,本教程利用”http”和”rack”插件。如果你正使用模块化构建,那么确保通过 --plugins http,rack
选项加载它们。
你的第一个Rack应用¶
Rack是编写Ruby web应用的标准方式。
这是一个标准的Rack Hello world脚本 (称之为app.ru):
class App
def call(environ)
[200, {'Content-Type' => 'text/html'}, ['Hello']]
end
end
run App.new
扩展名 .ru
表示”rackup”,这是Rack发行版中包含的部署工具。Rackup使用了一点DSL,因此,要将其用到uWSGI中,你需要安装rack gem:
gem install rack
现在,我们准备好部署uWSGI了:
uwsgi --http :8080 --http-modifier1 7 --rack app.ru
(如果‘uwsgi’并不在你当前的$PATH中,记得替换它)
或者如果你正使用的是一个模块化构建 (就像你的发行版的那个)
uwsgi --plugins http,rack --http :8080 --http-modifier1 7 --rack app.ru
使用这个命令行,我们生成了一个HTTP代理,路由每个请求到一个进程 (名为’worker’),进程管理它并把响应发送回HTTP路由器 (接着路由器发送回客户端)。
如果你对为什么生成两个进程有疑问,那么(我告诉你)是因为这是你将会在生产上使用的正常架构 (一个带有一个后端应用服务器的前线web服务器)。
如果你不想要生成HTTP代理,而是直接强制worker应答HTTP请求,那么仅需将命令行修改为
uwsgi --http-socket :8080 --http-socket-modifier1 7 --rack app.ru
现在,你有了单个管理请求的进程了 (但记住,直接将应用服务器公开一般是危险的,并且并不通用)。
那个’–http-modifier1 7’东东是啥?¶
uWSGI支持多种语言和平台。当服务器接收到一个请求的时候,它必须知道将其“路由” 到哪里。
每个uWSGI插件都有一个分配的数字 (即modifier),ruby/rack这个使用的是7。因此, --http-modifier1 7
意思是“路由到rack插件”。
虽然uWSGI也有一个更加“人类友好型”的 internal routing system ,但是,使用modifier是最快的方式,所以,尽可能使用它们。
使用一个完整的web服务器:nginx¶
提供的HTTP路由器,是 (是哒,真的已经够了) 只是一个路由器。你可以将它当成一个负载均衡器或者代理使用,但是如果你需要一个完整的web服务器 (用于有效提供静态文件,或者一个web服务器所擅长的所有那些任务),那么你可以摆脱uwsgi HTTP路由器 (如果你正使用模块化构建,那么记得修改–plugins http,rack为–plugins rack),然后将你的应用放在Nginx之后。
为了与Nginx通信,uWSGI可以使用多个协议:HTTP, uwsgi, FastCGI, SCGI, 等等。
最有效的是uwsgi。Nginx默认支持uwsgi协议。
在一个uwsgi socket上运行你的rack应用:
uwsgi --socket 127.0.0.1:3031 --rack app.ru
然后在你的nginx配置中添加一个location节
location / {
include uwsgi_params;
uwsgi_pass 127.0.0.1:3031;
uwsgi_modifier1 7;
}
重载你的nginx服务器,然后它应该开始代理请求到你的uWSGI实例。
注意,你不需要配置uWSGI来设置一个指定的modifier,nginx将会使用 uwsgi_modifier1 5;
指令来实现它。
添加并发性¶
通过前面的例子,你部署了一个能够服务单个请求的栈。
要增加并发性,你需要添加更多的进程。如果你希望有一个魔法数学公式可以找到要生成的正确进程数,嗯……很抱歉,我们没有。你需要进行实验,并且监控你的应用,以找到正确的值。考虑到每个进程是你的应用的一个完全拷贝,因此,必须考虑内存使用。
要添加更多的进程,只需使用 –processes <n> 选项:
uwsgi --socket 127.0.0.1:3031 --rack app.ru --processes 8
将生成8个进程。
Ruby 1.9/2.0引入了一个改进的线程支持,uWSGI通过’rbthreads’插件支持它。当你编译uWSGI+ruby (>=1.9)单片二进制文件的时候,会自动内建这个插件。
要添加更多线程:
uwsgi --socket 127.0.0.1:3031 --rack app.ru --rbthreads 4
或者线程+进程
uwsgi --socket 127.0.0.1:3031 --rack app.ru --processes --rbthreads 4
有其他(一般更高级/复杂)的方式来提高并发性 (例如,’fiber’),但是,大多时候,你最终会得到一个普通的旧式多进程或者多线程模型。如果你感兴趣,那么请查看 Rack
之上的完整文档。
添加健壮性:Master进程¶
It is highly recommended to have the uWSGI master process always running on productions apps.
It will constantly monitor your processes/threads and will add fun features like the uWSGI Stats服务器.
To enable the master simply add --master
uwsgi --socket 127.0.0.1:3031 --rack app.ru --processes 4 --master
使用配置文件¶
uWSGI有数百个选项 (但一般来说,你不会使用超过数十个)。通过命令行处理它们有点蠢,因此,试着总是使用配置文件。
uWSGI支持各种标准 (XML, INI, JSON, YAML, 等等)。从一个移到另一个相当简单。你可以通过命令行使用的相同的选项也可以在配置文件中使用,只需将前缀 --
移除:
[uwsgi]
socket = 127.0.0.1:3031
rack = app.ru
processes = 4
master = true
或者xml:
<uwsgi>
<socket>127.0.0.1:3031</socket>
<rack>app.ru</rack>
<processes>4</processes>
<master/>
</uwsgi>
要通过配置文件运行uWSGI,只需将其当做一个参数指定:
uwsgi yourconfig.ini
如果出于某些原因,你的配置不以期望的扩展名 (.ini, .xml, .yml, .js) 结尾,那么你可以通过这种方式,强制二进制文件使用一个指定的解析器:
uwsgi --ini yourconfig.foo
uwsgi --xml yourconfig.foo
uwsgi --yaml yourconfig.foo
等等等等。
你甚至可以通过管道使用配置 (使用破折号强制从标准输入中读取):
ruby myjsonconfig_generator.rb | uwsgi --json -
当你生成多个进程时的fork()问题¶
uWSGI某种程度上是“Perl化的”,我们没法隐藏它。它的大多数抉择 (从“不止一种实现的方式”开始) 来自于Perl世界 (更一般来说,是来自于传统的UNIX系统管理员方法)。
当应用到其他语言/平台上的时候,这个方法有时会导致意外行为。
当你开始学习uWSGI的时候,你会面对的其中一个“问题”是它的 fork()
使用。
默认情况下,uWSGI在第一个生成的进程中加载你的应用,然后多次 fork()
自身。
这意味着,你的应用被单次加载,然后被拷贝。
虽然这个方法加速了服务器的启动,但是有些应用在这项技术下会出问题 (特别是那些在启动时初始化db连接的应用,因为将会在子进程中继承连接的文件描述符)。
如果你对uWSGI使用的粗暴的preforking不确定,那么只需使用 --lazy-apps
选项来禁用它。它将会强制uWSGI在每个worker中完全加载你的应用一次。
部署Sinatra¶
让我们忘掉fork(),回到有趣的事情上来。这次,我们部署一个Sinatra应用:
require 'sinatra'
get '/hi' do
"Hello World"
end
run Sinatra::Application
将其另存为 config.ru
,然后如前所见那样运行:
[uwsgi]
socket = 127.0.0.1:3031
rack = config.ru
master = true
processes = 4
lazy-apps = true
uwsgi yourconf.ini
好吧,或许你已经注意到,基本与前面的app.ru例子没啥区别。
这是因为,基本上,每个现代的Rack应用都将其自身作为一个.ru文件(一般称为config.ru)公开,因此,无需使用多个选项来加载应用 (例如,像Python/WSGI世界中的那样)。
部署RubyOnRails >= 3¶
从3.0起,Rails就是完全兼容Rack的,并且它公开了一个你可以直接加载的config.ru文件 (就像我们用Sinatra那样)。
与Sinatra的唯一不同是,你的项目有一个特定的布局/约定,期望你当前的工作目录是包含项目的那个目录,因此,让我们添加一个chdir选项:
[uwsgi]
socket = 127.0.0.1:3031
rack = config.ru
master = true
processes = 4
lazy-apps = true
chdir = <path_to_your_rails_app>
env = RAILS_ENV=production
uwsgi yourconf.ini
除了chdir之外,我们添加了’env’选项,它设置 RAILS_ENV
环境变量。
从4.0起,Rails支持多线程 (只适用于ruby 2.0):
[uwsgi]
socket = 127.0.0.1:3031
rack = config.ru
master = true
processes = 4
rbthreads = 2
lazy-apps = true
chdir = <path_to_your_rails_app>
env = RAILS_ENV=production
部署更老的RubyOnRails¶
较老的Rails版本并非完全Rack兼容的。出于这样的原因,uWSGI中有一个特定的选项,用来加载较老的Rails应用 (你也会需要’thin’这个gem)。
[uwsgi]
socket = 127.0.0.1:3031
master = true
processes = 4
lazy-apps = true
rails = <path_to_your_rails_app>
env = RAILS_ENV=production
所以,长话短说,指定 rails
选项,将rails应用的目录(而不是Rackup文件)作为参数传递。
Bundler和RVM¶
Bundler是用来管理依赖的标准的事实上的Ruby工具。基本上,你在Gemfile文本文件中指定你的应用所需的gem,然后启动bundler来安装它们。
要让uWSGI遵守bundler安装,你只需要添加:
rbrequire = rubygems
rbrequire = bundler/setup
env = BUNDLE_GEMFILE=<path_to_your_Gemfile>
(第一个并非ruby 1.9/2.x要求的必须节。)
基本上,这些行强制uWSGI加载bundler引擎,并且使用在 BUNDLE_GEMFILE
环境变量中指定的Gemfile。
当使用Bundler的时候 (就像现代框架所做的那样),你的常见部署配置将会是:
[uwsgi]
socket = 127.0.0.1:3031
rack = config.ru
master = true
processes = 4
lazy-apps = true
rbrequire = rubygems
rbrequire = bundler/setup
env = BUNDLE_GEMFILE=<path_to_your_Gemfile>
除了Bundler之外,RVM是另一个常见的工具。
它允许你在单个系统中安装多个 (独立的) Ruby版本 (带自己的gemset)。
要指示uWSGI使用一个指定RVM版本的gemset,只需使用 –gemset 选项:
[uwsgi]
socket = 127.0.0.1:3031
rack = config.ru
master = true
processes = 4
lazy-apps = true
rbrequire = rubygems
rbrequire = bundler/setup
env = BUNDLE_GEMFILE=<path_to_your_Gemfile>
gemset = ruby-2.0@foobar
只是注意,对于每个Ruby版本(是Ruby版本,不是gemset!),你都需要一个uWSGI二进制文件(或者如果你使用模块化构建,则是一个插件)。
如果你有兴趣,那么这里是构建uWSGI核心+每个rvm中安装的Ruby版本1个插件的命令列表。
# build the core
make nolang
# build plugin for 1.8.7
rvm use 1.8.7
./uwsgi --build-plugin "plugins/rack rack187"
# build for 1.9.2
rvm use 1.9.2
./uwsgi --build-plugin "plugins/rack rack192"
# and so on...
然后,如果你想要使用ruby 1.9.2和@oops gemset:
[uwsgi]
plugins = ruby192
socket = 127.0.0.1:3031
rack = config.ru
master = true
processes = 4
lazy-apps = true
rbrequire = rubygems
rbrequire = bundler/setup
env = BUNDLE_GEMFILE=<path_to_your_Gemfile>
gemset = ruby-1.9.2@oops
开机时自动启动uWSGI¶
如果你正想着打开vi,然后编写一个init.d脚本来生成uWSGI,那么坐下(并且淡定),首先确保你的系统并没有提供一个更好(更现代)的方法。
每个发行版都选择了一个启动系统 (Upstart, Systemd...),并且有大量可用的进程管理器 (supervisord, god, monit, circus...)。
uWSGI将与它们完美集成(我们希望),但是如果你计划部署大量的应用,那么看看uWSGI Emperor - 它或多或少是每个devops工程师的梦想。
安全性和可用性¶
总是避免以root运行你的uWSGI实例。你可以使用uid和gid选项来移除特权。
[uwsgi]
socket = 127.0.0.1:3031
uid = foo
gid = bar
chdir = path_toyour_app
rack = app.ru
master = true
processes = 8
web应用部署的一个常见问题是“卡住的请求”。你所有的线程/worker都卡住了 (请求阻塞) ,而你的应用无法接收更多的请求。
要避免这个问题,你可以设置一个 harakiri
定时器。它是一个监控器 (由master进程管理),会摧毁那些卡住超过指定秒数的进程。
[uwsgi]
socket = 127.0.0.1:3031
uid = foo
gid = bar
chdir = path_toyour_app
rack = app.ru
master = true
processes = 8
harakiri = 30
这将会摧毁那些阻塞超过30秒的worker。小心选择 harakiri
值!
除此之外,自uWSGI 1.9起,stats服务器导出了全部请求变量,因此,你可以(实时)看到你的实例正在做什么 (对于每个worker, thread 或者异步核)。
启用统计信息服务器是很简单的:
[uwsgi]
socket = 127.0.0.1:3031
uid = foo
gid = bar
chdir = path_to_your_app
rack = app.ru
master = true
processes = 8
harakiri = 30
stats = 127.0.0.1:5000
只需将其绑定到一个地址 (UNIX或者TCP) 上,并且只需连接 (你也可以使用telnet) 到它上面,来接收你的实例的一个JSON内容。
uwsgitop
应用 (你可以在官方的github仓库中找到它) 是一个使用统计信息服务器来实现类top实时监控的工具的例子 (有颜色!!!)
内存使用¶
低内存使用是整个uWSGI项目的卖点之一。
不幸的是,默认对内存的积极态度也许(看好:是也许)会导致某些性能问题。
默认情况下,uWSGI Rack插件在每次请求之后调用Ruby GC (垃圾回收器)。如果你想要减少这个频率,只需添加``–rb-gc-freq <n>`` 选项,其中,n是调用GC之后的请求数。
如果你计划进行uWSGI的基准 (或者把它与其他方法对比),那么考虑它对GC的使用。
Ruby可以是一个真正的内存吞噬者,因此,我们更喜欢默认积极对待内存,而不是取悦hello-world基准点。
卸载¶
uWSGI 卸载(offloading)子系统 允许你在某些特定的模式匹配的时候尽快释放你的worker,并且能够委托给一个纯c线程。例如,发送来自文件系统的静态文件,从网络传输数据到客户端,等等。
卸载是非常复杂的,但是它的使用对终端用户是透明的。如果你想要试一试,那么只需添加–offload-threads <n>,其中,<n>是要生成的线程数 (一个cpu一个线程是个不错的值)。
当启用了卸载线程时,所有可以被优化的部分将会被自动检测到。
代码片段¶
这是uWSGI特性的一些最“有趣”的使用的集合。
X-Sendfile模拟¶
即使你的前端代理/web服务器不支持X-Sendfile (或者不能访问你的静态资源),但是你可以使用uWSGI的内部卸载(你的进程/线程将会委托实际的静态文件服务给卸载线程)来模拟它。
[uwsgi]
...
; load router_static plugin (compiled in by default in monolithic profiles)
plugins = router_static
; spawn 2 offload threads
offload-threads = 2
; files under /private can be safely served
static-safe = /private
; collect the X-Sendfile response header as X_SENDFILE var
collect-header = X-Sendfile X_SENDFILE
; if X_SENDFILE is not empty, pass its value to the "static" routing action (it will automatically use offloading if available)
response-route-if-not = empty:${X_SENDFILE} static:${X_SENDFILE}
强制使用HTTPS¶
这将会强制整个站点使用HTTPS。
[uwsgi]
...
; load router_redirect plugin (compiled in by default in monolithic profiles)
plugins = router_redirect
route-if-not = equal:${HTTPS};on redirect-permanent:https://${HTTP_HOST}${REQUEST_URI}
而这个只会强制 /admin
(使用HTTPS)
[uwsgi]
...
; load router_redirect plugin (compiled in by default in monolithic profiles)
plugins = router_redirect
route = ^/admin goto:https
; stop the chain
route-run = last:
route-label = https
route-if-not = equal:${HTTPS};on redirect-permanent:https://${HTTP_HOST}${REQUEST_URI}
最终,你可能也想要发送HSTS (强制安全传输技术,HTTP Strict Transport Security)头。
[uwsgi]
...
; load router_redirect plugin (compiled in by default in monolithic profiles)
plugins = router_redirect
route-if-not = equal:${HTTPS};on redirect-permanent:https://${HTTP_HOST}${REQUEST_URI}
route-if = equal:${HTTPS};on addheader:Strict-Transport-Security: max-age=31536000
Python自动重载 (DEVELOPMENT ONLY!)¶
在生产环境中,你可以监控文件/目录的改动来触发重载 (touch-reload, fs-reload...).
在开发期间,拥有一个用于所有的重载/已用python模块的监控器是很方便的。但是请只在开发期间才使用它。
检查是通过一个以指定频率扫描模块列表的线程来完成的:
[uwsgi]
...
py-autoreload = 2
将会每2秒检查一次python模块的改动,并最终重启实例。
再次说明:
警告
只在开发时使用它。
全栈CGI设置¶
这个例子从一个uWSGI的邮件列表线程生成。
我们在/var/www中有静态文件,在/var/cgi中有cgi。将会使用/cgi-bin挂载点访问cgi。所以将会在到/cgi-bin/foo.lua的请求上运行/var/cgi/foo.lua
[uwsgi]
workdir = /var
ipaddress = 0.0.0.0
; start an http router on port 8080
http = %(ipaddress):8080
; enable the stats server on port 9191
stats = 127.0.0.1:9191
; spawn 2 threads in 4 processes (concurrency level: 8)
processes = 4
threads = 2
; drop privileges
uid = nobody
gid = nogroup
; serve static files in /var/www
static-index = index.html
static-index = index.htm
check-static = %(workdir)/www
; skip serving static files ending with .lua
static-skip-ext = .lua
; route requests to the CGI plugin
http-modifier1 = 9
; map /cgi-bin requests to /var/cgi
cgi = /cgi-bin=%(workdir)/cgi
; only .lua script can be executed
cgi-allowed-ext = .lua
; .lua files are executed with the 'lua' command (it avoids the need of giving execute permission to files)
cgi-helper = .lua=lua
; search for index.lua if a directory is requested
cgi-index = index.lua
不同挂载点中的多个flask应用¶
写3个flask应用:
#app1.py
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "Hello World! i am app1"
#app2.py
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "Hello World! i am app2"
#app3.py
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "Hello World! i am app3"
每个将会被分别挂载在/app1, /app2, /app3上
要在uWSGI中挂载一个使用指定“键”的应用,使用–mount选项:
`
--mount <mountpoint>=<app>
`
在我们的例子中,我们想要挂载3个python应用,每个使用WSGI SCRIPT_NAME变量名作为键:
[uwsgi]
plugin = python
mount = /app1=app1.py
mount = /app2=app2.py
mount = /app3=app3.py
; generally flask apps expose the 'app' callable instead of 'application'
callable = app
; tell uWSGI to rewrite PATH_INFO and SCRIPT_NAME according to mount-points
manage-script-name = true
; bind to a socket
socket = /var/run/uwsgi.sock
现在,直接将你的webserver.proxy指向实例socket (无需进行额外的配置)
注意事项:默认情况下,每个应用都会在一个新的python解释器中加载 (那意味着每个应用都有一个相当棒的隔离的名字空间)。如果你想要在相同的python vm中加载所有的应用,那么使用–single-interpreter选项。
另一个注意事项:你或许发现到一个不起眼的”modifier1 30”引用技巧。它已经被弃用了,并且非常丑陋。uWSGI能够以许多种高级方式重写请求变量
最后一个注意事项:默认情况下,第一个加载的应用作为”默认应用”挂载。当没有挂载点匹配上的时候,将会使用那个应用。
OSX上的rbenv (应该也能在其他平台上用)¶
安装rbenv
brew update
brew install rbenv ruby-build
(不要像经典的howto中描述的那样在.bash_profile中设置魔术行,因为我们希望不破坏环境,并且让uWSGI摆脱它)
获取一个uWSGI压缩包,并且构建’nolang’版本 (它是一个单片版本,其中并未编译任何语言插件)
wget http://projects.unbit.it/downloads/uwsgi-latest.tar.gz
tar zxvf uwsgi-latest.tar.gz
cd uwsgi-xxx
make nolang
现在,开始安装你需要的ruby版本
rbenv install 1.9.3-p551
rbenv install 2.1.5
并且安装你需要的gem (这个例子中是sinatra):
# set the current ruby env
rbenv local 1.9.3-p551
# get the path of the gem binary
rbenv which gem
# /Users/roberta/.rbenv/versions/1.9.3-p551/bin/gem
/Users/roberta/.rbenv/versions/1.9.3-p551/bin/gem install sinatra
# from the uwsgi sources directory, build the rack plugin for 1.9.3-p551, naming it rack_193_plugin.so
# the trick here is changing PATH to find the right ruby binary during the build procedure
PATH=/Users/roberta/.rbenv/versions/1.9.3-p551/bin:$PATH ./uwsgi --build-plugin "plugins/rack rack_193"
# set ruby 2.1.5
rbenv local 2.1.5
rbenv which gem
# /Users/roberta/.rbenv/versions/2.1.5/bin/gem
/Users/roberta/.rbenv/versions/2.1.5/bin/gem install sinatra
PATH=/Users/roberta/.rbenv/versions/2.1.5/bin:$PATH ./uwsgi --build-plugin "plugins/rack rack_215"
现在,从一个ruby切换到另一个,只需修改插件:
[uwsgi]
plugin = rack_193
rack = config.ru
http-socket = :9090
或者
[uwsgi]
plugin = rack_215
rack = config.ru
http-socket = :9090
确保插件存储在当前的工作目录中,或者设置plugins-dir指令,又或者像这样涌绝对路径指定它们
[uwsgi]
plugin = /foobar/rack_215_plugin.so
rack = config.ru
http-socket = :9090
认证的WebSocket代理¶
应用服务器识别websocket流量,相对于应用自身策略/基础架构,使用任何CGI变量来认证/鉴权用户,然后卸载/代理请求到一个简单的kafka-websocket后端。
首先,创建 auth_kafka.py
:
from pprint import pprint
def application(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/plain')])
return ['It Works!']
def auth_kafka(request_uri, http_cookie, http_authorization):
pprint(locals())
return 'true'
import uwsgi
uwsgi.register_rpc('auth_kafka', auth_kafka)
然后创建 auth_kafka.ini
:
[uwsgi]
; setup
http-socket = 127.0.0.1:8000
master = true
module = auth_kafka
; critical! else worker timeouts apply to proxied websocket connections
offload-threads = 2
; match websocket protocol
kafka-ws-upgrade-regex = ^[Ww]eb[Ss]ocket$
; DRY place for websocket check
is-kafka-ws-request = regexp:${HTTP_UPGRADE};%(kafka-ws-upgrade-regex)
; location of the kafka-ws server
kafka-ws-host = 127.0.0.1:7080
; base endpoint uri for websocket server
kafka-ws-endpoint-uri = /v2/broker/
; call auth_kafka(...); if AUTH_KAFKA gets set, request is good!
route-if = %(is-kafka-ws-request) rpcvar:AUTH_KAFKA auth_kafka ${REQUEST_URI} ${HTTP_COOKIE} ${HTTP_AUTHORIZATION}
; update request uri to websocket endpoint (rewrite only changes PATH_INFO?)
route-if-not = empty:${AUTH_KAFKA} seturi:%(kafka-ws-endpoint-uri)?${QUERY_STRING}
; route the request to our websocket server
route-if-not = empty:${AUTH_KAFKA} httpdumb:%(kafka-ws-host)
启动一个”kafka-websocket”服务器:
nc -l -k -p 7080
现在,在一个web浏览器中访问 http://127.0.0.1:8000
!你应该看到 Hello!
。打开chrome的查看器或者firebug,然后输入:
ws = new WebSocket('ws://127.0.0.1:8000/?subscribe=true')
你应该看到这个请求代理到你的 nc
命令!这个模式允许内部网络托管一个或多或少全开/通用的kafka -> websocket网关,并且委托认证需求给应用服务器。使用 offload-threads
意味着代理请求 不会 阻塞worker;使用 httpdumb
避免了重整请求 (http
动作强制使用 HTTP/1.0
)
SELinux和uWSGI¶
SELinux允许你将web应用进程彼此隔离,并且限制每个程序只用于自身目的。应用可以被放置于高度隔离的独立沙箱中,将它们与其他应用以及底层操作系统分离开来。由于SELinux是在内核中实现的,因此不需要特殊编写或修改应用就能让其在SELinux之下使用。github上有一个 SELinux security policy for web applications ,非常适于uWSGI。这个安全策略也支持运行在一个域中的uWSGI emperor进程,以及运行在一个分隔域中的每个web应用的worker进程,即使使用了Linux名字空间,worker进程也只要求最小的特权。当然,使用SELinux不要求emperor模式,或者Linux名字空间。
在Linux上,有可能使用文件系统、ipc、uts、网络、pid和uid的专有视图来运行每个vassal。然后,每个vassal可以,比方说,修改文件系统布局、网络和主机名,而无需损坏主系统。有了这个设置,特权任务,例如挂载文件系统、设置主机名、配置网络和设置worker进程的gid和uid就可以在修改vassal进程的SELinux安全上下文之前完成了,确保每个worker进程只需要最少的特权。
首先,配置、编译和加载SELinux web应用安全策略。然后,重新标记应用文件。关于如何配置web应用策略的进一步信息可以在 SELinux security policy for web applications 中包含的README.md上找到。最后,在每个vassal的配置文件中,调用libselinux的setcon函数来设置web应用的SELinux安全上下文:
[uwsgi]
...
hook-as-user = callret:setcon system_u:system_r:webapp_id_t:s0
其中,id是域的标识。例如,foo是webapp_foo_t域的标识。
也许需要使用–dlopen选项在uWSGI地址空间内加载libselinux:
/path/to/uwsgi --dlopen /path/to/libselinux.so
目录¶
获取uWSGI¶
这些是uWSGI的当前版本。
发布 | 日期 | 链接 |
---|---|---|
不稳定/开发 | - | https://github.com/unbit/uwsgi/ |
稳定/LTS | 2016-05-13 | https://projects.unbit.it/downloads/uwsgi-2.0.13.1.tar.gz |
文档 | - | https://github.com/unbit/uwsgi-docs/ |
在一些OS/发行版中,uWSGI作为包可用
uWSGI拥有一个非常快速的开发周期,因此包可能并不是最新的。构建它需要少于30秒,仅需非常少的依赖 (只有Python解析器,一个C编译器/链接器和你所选语言的库/头文件)
安装uWSGI¶
从发布包安装¶
在一些操作系统/发布版本中,uWSGI已作为软件包提供。
通过源码包安装¶
要构建uWSGI,你需要Python和一个C编译器 (支持 gcc
和 clang
)。取决于你想要支持的语言,你将需要它们的开发头文件。在Debian/Ubuntu系统上,你可以这样安装它们(以及构建软件所需的其余的基础设施):
apt-get install build-essential python
而如果你想要构建一个带有Python/wsgi支持的二进制文件的话 (举个例子)
apt-get install python-dev
在Fedora/Redhat系统上,你可以这样安装它们:
yum groupinstall "Development Tools"
yum install python
而对于python/wsgi支持:
yum install python-devel
如果你的系统中有了可用的 make 变体,那么你可以简单运行 make 。如果没有 make (或者想有更多的控制权),那么运行:
python uwsgiconfig.py --build
你还可以使用pip来安装uWSGI (它将会构建一个带python支持的二进制文件)。
# Install the latest stable release:
pip install uwsgi
# ... or if you want to install the latest LTS (long term support) release,
pip install http://projects.unbit.it/downloads/uwsgi-lts.tar.gz
或者你可以使用ruby gems (它将会构建一个带ruby/rack支持的二进制文件)。
# Install the latest stable release:
gem install uwsgi
在构建的最后,你将会获得启用特性的报告。如果缺少了任何你需要的,那么仅需添加开发头文件,然后重新运行构建。例如,要构建带ssl和perl正则表达式支持的uWSGI,你需要libssl-dev和pcre头文件。
可替换构建配置文件¶
出于历史原因,当你运行’make’的时候,会构建把Python作为唯一支持的语言的uWSGI。你可以使用构建配置文件来自定义uWSGI服务器,配置文件位于 buildconf/ 目录中。你可以这样使用一个特定的配置文件:
python uwsgiconfig.py --build <profile>
或者你可以通过一个环境变量传递它:
UWSGI_PROFILE=lua make
# ... or even ...
UWSGI_PROFILE=gevent pip install uwsgi
模块化构建¶
这是你的发行版应该遵循的方法,也是在你想要在uWSGI之上构建一个商用服务必须遵循的方法(见下)。大部分的uWSGI特性是作为插件提供的。可以使用–plugin选项来加载插件。如果你想提供给用户最大量的灵活性,让他们只使用最少的资源,那么仅需创建一个模块化构建。一个名为”core”的构建配置文件是可用的。
python uwsgiconfig.py --build core
这将会狗偶家一个不带插件的uWSGi二进制文件。这被称为”服务器核心”。现在,你可以开始构建所需的所有的插件。检查源代码发布中的plugins/ 目录,以获取完整列表。
python uwsgiconfig.py --plugin plugins/psgi core
python uwsgiconfig.py --plugin plugins/rack core
python uwsgiconfig.py --plugin plugins/python core
python uwsgiconfig.py --plugin plugins/lua core
python uwsgiconfig.py --plugin plugins/corerouter core
python uwsgiconfig.py --plugin plugins/http core
...
记得要传递构建配置文件 (这里是’core’) 作为第三个参数。
uWSGI构建系统¶
- 更新至1.9.13
本页面描述了uWSGI构建系统是如何工作的,以及如何对其进行定制。
uwsgiconfig.py¶
这是一个旨在调用不同编译/链接阶段的python脚本。
在2009年期间,当开始定义uWSGI指导方针 (以及准则) 时,人们一致认为,autotools, cmake和它们的小伙伴们不受许多系统管理员喜爱。尽管它们相当标准,但是需要包数量,以及它们之前的不兼容(特别是在autotools的世界中)对于一个具有快速开发/演进的项目来说,是个问题,其中“从源代码编译”曾经,现在是,并且可能将是从产品获得最好的最好的方式。除此之外,构建过程一定要快 (在入门级的x86上少于1分钟是主要规则)
出于这样的原因,要编译uWSGI,你只需要有一个c编译套件 (gcc, clang...) 和一个python解释器。有人可能会说,perl可能是一个更好的选择,也许这是事实 (默认情况下,许多操作系统一般都会安装它),但是我们还是决定主要用python,因为,在uWSGI项目启动的时候,它是一个仅支持python的应用。(显然,如果你想要开发一个可替换的构建系统,请随意)
基本上,uwsgiconfig.py检测系统中的可用特性,并使用所谓的“构建配置文件”构建一个uwsgi二进制(及其插件)
构建配置文件¶
第一个例子¶
CC和CPP¶
这2个环境变量告诉uwsgiconfig.py使用一个替换的C编译器和C预处理器。
如果不定义它们,那么过程如下:
对于CC -> 尝试从运行uwsgiconfig.py的python二进制中获得CC config_var,回退到’gcc’
对于CPP -> 回退到’cpp’
例如,在一个同时拥有gcc和clang的系统上,你会得到
CC=clang CPP=clang-cpp python uwsgiconfig.py --build
CPUCOUNT¶
在“即使在生产系统上,也要轻松快速构建”的精神下,uwsgiconfig.py试着使用你所有的cpu核来生成c编译器的多个实例 (每个核一个)。
你可以使用CPUCOUNT环境变量来覆盖这个系统,强制已检测的CPU核数目 (设为1将会禁用并行构建)。
CPUCOUNT=2 python uwsgiconfig.py --build
UWSGI_FORCE_REBUILD¶
插件和uwsgiplugin.py¶
一个uWSGI插件是一个导出<name>_plugin符号的共享库。其中,<name>是插件名。
例如,psgi插件将会导出psgi_plugin符号,而pypy将会导出pypy_plugin符号,以此类推。
这个符号是一个uwsgi_plugin C结构,定义插件的钩子。
当你让uWSGI加载一个插件的时候,它简单调用dlopen(),并通过dlsym()获得uwsgi_plugin结构。
uWSGI项目的绝大部分是作为插件开发的,这保证了配置的模块化,以及明显理智的发展方式。
系统管理员可以随意嵌入每个插件在服务器二进制文件中,或将每个插件当成一个外部共享库构建。
已嵌入的插件在构建配置文件的’embedded_plugins’指令中定义。你可以使用UWSGI_EMBED_PLUGINS环境变量,从命令行添加更多嵌入插件(见下)。
事实上,如果你想要把一个插件作为共享库构建,那么只需带–plugin选项运行uwsgiconfig.py
python uwsgiconfig.py --plugin plugins/psgi
这将会构建plugins/psgi中的插件为psgi_plugin.so文件
要在构建插件的时候,指定一个构建配置文件,你可以把这个配置文件当成一个额外的参数传递
python uwsgiconfig.py --plugin plugins/psgi mybuildprofile
UWSGI_INCLUDES¶
- 这个已在1.9.13添加
在启动时,运行CPP二进制文件来检测默认的包含路径。你可以使用UWSGI_INCLUDES环境变量来添加更多路径
UWSGI_INCLUDES=/usr/local/include,/opt/dev/include python uwsgiconfig.py --build
UWSGI_EMBED_PLUGINS¶
UWSGI_EMBED_CONFIG¶
允许嵌入指定的.ini文件到服务器二进制文件中 (目前仅支持Linux)
在启动的时候,服务器立即解析嵌入的文件。
嵌入的配置中自定义的选项将如标准选项一般可用。
UWSGI_BIN_NAME¶
CFLAGS和LDFLAGS¶
用于插件的 UWSGICONFIG_*¶
libuwsgi.so¶
uwsgibuild.log¶
uwsgibuild.lastcflags¶
cflags和uwsgi.h magic¶
嵌入文件¶
fake make¶
管理uWSGI服务器¶
启动服务器¶
启动一个uWSGI服务器是系统管理员的角色,例如启动Web服务器。不应该由Web服务器来启动uWSGI服务器 —— 虽然如果这适合你的架构,你也能那样做。
如何在引导的时候最好的启动uWSGI服务取决于你使用的操作系统。
在现代系统中,下述应该行得通。在“传统的”操作系统上,你可以使用 init.d
/rc.d
脚本,或者诸如Supervisor, Daemontools 或 inetd/xinetd 这样的工具。
系统 | 方法 |
---|---|
Ubuntu | 通过Upstart运行uWSGI (the official uwsgi package, available since Ubuntu 12.04 provides an init.d based solution. Read the README.) |
Debian | Systemd |
Arch Linux | Systemd |
Fedora | Systemd |
OSX | launchd |
Solaris | SMF |
用来控制uWSGI的信号哦¶
你可以使用 safe-pidfile
选项,指示uWSGI将master进程的PID写入到一个文件中。
uWSGI对以下信号进行响应。
信号 | 描述 | 便捷命令 |
---|---|---|
SIGHUP | 优雅地重载所有的worker和master进程 | --reload |
SIGTERM | 粗鲁地重载所有的worker和master进程 | (使用 --die-on-term 以尊重关闭实例的传统) |
SIGINT | 立即杀死整个uWSGI栈 | --stop |
SIGQUIT | 立即杀死整个uWSGI栈 | |
SIGUSR1 | 打印统计数据 | |
SIGUSR2 | 打印worker状态或唤醒spooler | |
SIGURG | 恢复一个快照 | |
SIGTSTP | 暂停/挂起/恢复一个实例 | |
SIGWINCH | 唤醒一个在系统调用中阻塞的worker (内部使用) | |
SIGFPE | 生成C回溯 | |
SIGSEGV | 生成C回溯 |
注意:比起使用信号,有更好的管理你的实例的方式,例如,master-fifo就是一个更健壮的方式。
重载服务器¶
当运行在 master
进程模式下时,uWSGI服务器可以在无需关闭主socket的情况下优雅地重启。
这个功能允许你在无需关闭与web服务器的连接以及丢失请求的情况下修补/更新uWSGI服务器。
当你发送 SIGHUP 给主进程时,它会试着优雅地停止所有的worker,等待任何当前运行中的请求完成。
然后,它关闭所有与uWSGI无关的最终打开的文件描述符。
最后,它使用一个新的来二进制修补 (使用 execve()
) uWSGI进程镜像,继承所有之前的文件描述符。
服务器将会知道它是一个已重载的实例,并且会跳过所有的socket初始化,重用之前的。
注解
发送 SIGTERM 信号将会获得与优雅地重载相同的结果,但将不会等待运行中的请求的完成。
有多种方式来让uWSGI优雅地重启。
# using kill to send the signal
kill -HUP `cat /tmp/project-master.pid`
# or the convenience option --reload
uwsgi --reload /tmp/project-master.pid
# or if uwsgi was started with touch-reload=/tmp/somefile
touch /tmp/somefile
或在你的应用中,使用Python:
uwsgi.reload()
或者使用Ruby,
UWSGI.reload
停止服务器¶
如果出于某些原因,你让uWSGI进程在前台运行,那么使用CTRL+C就可以杀死它了。
在处理后台进程时,你将需要再次使用master pidfile。SIGINT信号将会杀死uWSGI.
kill -INT `cat /tmp/project-master.pid`
# or for convenience...
uwsgi --stop /tmp/project-master.pid
Master FIFO¶
自uWSGI 1.9.17起,添加了一个新的管理系统,它使用unix命名管道 (fifo): Master FIFO
支持的语言和平台¶
技术 | 初始可用版本 | 注意事项 | 状态 |
---|---|---|---|
Python | 0.9.1 | 第一个可用插件,支持WSGI (PEP 333, PEP 3333),
Web3 (from version 0.9.7-dev) 和Pump (自0.9.8.4起)。 与
Virtualenv ,多个Python解析器 Python3 配合良好,并且具有独特的特性,例如 别名化Python模块,
DynamicVirtualenv 和 uGreen – uWSGI绿色线程(green thread) 。源代码发布版本中有一个可用的uWSGI API模块导出
decorators 。自1.3,支持PyPy is supported 1.3添加了
Python Tracebacker 。 |
稳定, 100%的uWSGI API支持 |
Lua | 0.9.5 | 支持 LuaWSAPI ,协程和线程 |
稳定, 60%的uWSGI API支持 |
Perl | 0.9.5 | uWSGI Perl支持 (PSGI) (PSGI)支持。支持多种解析器,线程和异步模式 | 稳定, 60%的uWSGI API支持 |
Ruby | 0.9.7-dev | Ruby支持 支持。有一个用于 Ruby 1.9
fibers 的循环引擎,以及一个便捷的 DSL 模块可用。 |
稳定, 80%的uWSGI API支持 |
集成uWSGI和Erlang | 0.9.5 | 允许uWSGI和Erlang节点之间的消息交换。 | 稳定,没有uWSGI API支持 |
在uWSGI上运行CGI脚本 | 1.0-dev | Run CGI scripts | 稳定,没有uWSGI API支持 |
在uWSGI中运行PHP脚本 | 1.0-dev | 运行PHP脚本 | 自1.1起稳定,5%的uWSGI API支持 |
uWSGI Go支持 (只有1.4) | 1.4-dev | 允许与Go语言的集成 | 15%的uWSGI API支持 |
uWSGI服务器中的JVM (更新至1.9) | 1.9-dev | uWSGI和Java虚拟机 JWSGI 之间的集成,以及
|
稳定 |
Mono ASP.NET插件 | 0.9.7-dev | 允许uWSGI和Mono之间的集成,以及ASP.NET应用的执行。 | 稳定 |
uWSGI V8支持 | 1.9.4 | 运行uWSGI和V8 JavaScript引擎之间的集成 | 开发初期 |
支持的平台/系统¶
下面是官方支持的操作系统和平台列表。
- Linux 2.6/3.x
- FreeBSD >= 7
- NetBSD
- OpenBSD
- DragonFlyBSD
- Windows Cygwin
- Mac OSX
- Solaris >= 10
- NexentaOS
- SmartOS
- OpenSolaris
- OpenIndiana
- OmniOS
- Debian/kFreeBSD
- GNU/Hurd
Web服务器集成¶
uWSGI支持多种集成web服务器的方法。它自己也能够为HTTP请求服务。
Apache¶
参见
Apache2 mod_uwsgi 模块式第一个为uWSGI开发的web服务器集成模块。它稳定,但可以更好地与Apache API集成。
由Unbit提供商业支持。
自uWSGI 0.9.6-dev起,包含了一个名为 mod_Ruwsgi 的第二个Apache2模块。它更加Apache API友好。 Unbit未对mod_Ruwsgi提供商业支持。
在1.2开发周期中,添加了另一个名为 mod_proxy_uwsgi 模块。不久,这应该是基于Apache部署的最好的选择。
Lighttpd (试验性)¶
该模块是最新开发的,但是在官方的inclusion发布版本中其包含已经被拒了,因为主作者认为 uwsgi protocol 是“重复发明轮子” ,而建议使用一个FastCGI方法。我们尊重这种态度。这个模块会继续存在uWSGI源代码树中,但是当前未对其进行维护。
对于这个处理程序,当前并无商业支持。我们认为这个模块是“试验性的”。
Twisted¶
这是一个”商用”处理程序,主要在不安装一个完整的web服务器的情况下测试应用的时候有用。如果你想要开发一个uWSGI服务器,那么看看这个模块。 Twisted
.
CGI¶
CGI处理程序是用于“偷懒”安装的。不鼓励在生产环境上使用它们。
Cherokee (废弃)¶
参见
Cherokee web服务器官方支持uWSGI。 Cherokee是快而且轻量的,拥有一个漂亮的admin界面,以及一个不错的社区。从一开始,他们对uWSGI的支持就很棒,在大多数的情况下,我们都推荐使用它。 Cherokee uWSGI处理程序的用户群可能是最大的。Cherokee uWSGI处理程序由Unbit提供商业支持。
Mongrel2 (废弃)¶
自0.9.8-dev起,对 Mongrel2 Project 的支持通过 ZeroMQ
协议插件可用。
在我们的测试中,Mongrel2几乎通过了我们发送的所有负载。
非常棒和稳定的项目。试试吧 :)
常见问题 (FAQ)¶
我应该选择uWSGI吗?¶
因为你可以呀! :) uWSGI想要成为一个完整的web应用部署解决方案,包含以下助力:
ProcessManagement
- Management of long-running tasks
- uWSGI RPC栈
Clustering
LoadBalancing
Monitoring
ResourceLimiting
... 以及许多其他你必须委托给外部脚本和手工系统管理员任务的烦人日常任务。
如果你正为你的WSGI, PSGI或者Rack应用搜寻一个简单的服务器,那么uWSGI可能不适合你。不过,如果你正构建一个应用,它需要坚如磐石、快速、易于分发并且为各种负载进行优化,那么你很可能会发现自己需要uWSGI。
uWSGI最好的定义是“你的网络应用的瑞士军刀”。
协议是什么?¶
uwsgi (全部小写) 是从SCGI衍生而来的,但使用二进制字符串长度表示,以及一个包含var块(16位长)大小的4字节头部,和几个通用字节。我们不重新发明轮子。二进制管理比字符串解析容易且廉价得多,而每一位对我们的项目都是必须的。如果你需要证明,那么看看 official protocol documentation ,你将会明白,为什么需要一个新的协议。显然,你可以随意使用其他支持的协议。记住,如果在某些场景下,你不能使用uWSGI,那么这是一个uWSGI问题。
我可以将它用于集群环境中吗?¶
是哒,这是uWSGI的主要特性之一。你可以将多个实例绑定在不同的服务器上,而使用你的web服务器/代理/路由器的负载均衡功能,你可以分布负载。像 uWSGI RPC栈 这样的系统允许你快速调用远端节点的函数,而 uWSGI Legion子系统 允许你在多节点设置中选出一个master。
那么,为嘛会有所有那些超时配置标志?¶
理智选择超时时间是高可用性的关键。不要信任那些不允许你选择超时时间的网络应用。
我需要帮助!我该怎么做?¶
在uWSGI邮件列表上发布一条信息,其中包含你的
- 操作系统版本
- CPU架构
- 使用的web服务器(如果有的话)
- uWSGI版本
- uWSGI命令行或配置文件
你应该添加 –show-config 选项,然后把输出贴到消息中。这对找出uWSGI有啥问题将非常有用。你也可以使用调试符号进行 rebuild uWSGI
,并且将其运行在诸如 gdb 这样的调试器下。
uWSGI是一个浩大的工程,带有数百个选项。你应该做好准备,并不是所有的东西在第一次使用的时候都是对的。寻求帮助,寻求帮助,以及寻求帮助。如果你感到沮丧,那么不要把时间花在谴责和咆哮上 —— 相反,简单加入邮件列表,然后寻求帮助。它是开源的,如果你只是咆哮,那么只是无用功。
我不是系统管理员,也不是UNIX专家。我可以用uWSGI吗?¶
这是一个好问题 :) 但是忧伤的是,没有简单的答案。uWSGI在开发中并未考虑简单性,而是通用性。你可以通过其中一个快速入门来开始,如果有问题,只需在邮件列表,或者IRC频道中寻求帮助。
我可以如何为我的公司购买商业支持?¶
发送邮件到unbit.it上的info,并在主题中使用单词”uWSGI”。你发送的邮件应该包含你的公司信息以及你的具体要求。我们将尽快答复。
这会允许我在我古旧的ISP上运行我美妙的应用吗?¶
可能不行哦。uWSGI服务器要求一个现代的平台/环境。
基准在何处?¶
抱歉哈,我们只为回归测试进行“官方”基准。如果基准对你非常重要,那么你可以在邮件列表上搜索,自己做基准,或者Google一下。uWSGI赋予机器健康优先权,因此,不要期望你使用不切实际数目的并发连接数的 ab 测试无需调整就能完美进行管理。如果你想要进行有效的基准(并且避免有人在你的博客下咆哮),那么一些socket和网络知识是必须的。还要记得,uWSGI可以在各种模式下运行,因此,如果你不想看起来不可理喻,那么避免把preforking模式下配置的服务器与其他在非阻塞/异步模式下配置的服务器进行对比。
注解
如果你看到你的测试在更高的并发速率下失败了,那么你可能到达了你的OS socket backlog队列限制 (在Linux中最高是128个槽,可以通过 /proc/sys/net/somaxconn 和 /proc/sys/net/ipv4/tcp_max_syn_backlog 对TCP socket进行调整)。
你可以使用 listen 配置选项,在uWSGI中设置这个值。
哎呀!服务器XXX比uWSGI快!用那个!¶
如前所述,uWSGI不是灵丹妙药,它并不打算让整个世界都喜欢,而显然,它也不是世界上最快的服务器。它就是一个软件,遵循一个你可能不喜欢,又或者爱得要死的问题“解决方法”。使用的方法对于某些情况下会更好,而应该在每个应用自己的优点上进行分析,使用适当而准确的真实世界的基准。
‘Harakiri模式’是什么?¶
在Unbit,我们在服务器上托管了数百个不可靠的web应用。它们所有都运行在没有限制(内核级别)环境下,其中因为一个实现错误导致的进程阻塞将会导致整个站点挂掉。而harakiri模式有两种操作模式:
- 一个我们定义为“原始,并且有点靠不住”(用于不带进程管理器的简单设置)
- 另一个我们定义为“可靠的”,依赖于uWSGI进程管理器 (见
ProcessManagement
)。
第一个在每个请求的开始设置一个简单的告警。如果进程获得 SIGALRM 信号,那么它会终止。我们称其为不可靠的,因为你的应用或者你使用的一些模块可能会通过简单调用 alarm() ,从而覆盖或者简单取消告警。
第二个使用一个master进程共享内存区域 (通过 mmap),它维护池中每个worker上的统计信息。在每个请求的开始,worker设置一个时间戳,表示过了多久, 进程将在其专用区域被杀死。这个时间戳会在每次成功请求之后清零。如果master进程发现了一个带有过去时间戳的worker,那么它会毫不留情地杀死它。
用uWSGI会让我的应用运行得更快吗?¶
不可能。web应用部署中的最大瓶颈是应用本身。如果你想要一个更快的环境,那么优化你的代码,或者使用诸如集群、缓存这样的技术。我们之所以说uWSGI快,是因为它引入非常少等待开销到部署结构中。
uWSGI环境中,对于性能和健壮性,什么是最重要的选项?¶
默认情况下,是用合理的“几乎是会所有”的值来配置uWSGI的。但是如果以及当事情开始变得不可控制的时候,调整是必须的。
- 增加(或减少)超时时间是重要的,修改socket监听队列大小也是如此。
- 考虑线程。如果你不需要线程,那么不要启用它们。
- 如果你只是运行一个应用,那么你可以禁用多解释器。
- 总是记住在生产环境上启用master进程。见
ProcessManagement
。 - 增加worker不是意味着“增加性能”,因此,基于你的应用的性质,为 workers 选项选择一个合适的值 (受IO限制,受CPU限制,IO等待……)
为什么不简单地使用HTTP作为协议?¶
一个好问题,它有一个简单的答案:HTTP解析很慢,真的很慢。为嘛我们应该做一个复杂的任务两次呢?web服务器已经解析请求了! uwsgi protocol 对机器而言,是非常容易解析的,而HTTP对人类而言,是非常容易解析的。一旦人类被当成服务器使用,我们会放弃uwsgi协议,支持HTTP协议。这就是说,你也可以通过 原生HTTP支持, FastCGI
,
ZeroMQ
和其他协议使用uWSGI。
为嘛你支持配置的多种方式?¶
系统管理是件关于技能和品味的事。uWSGI试图为系统管理员提供尽可能多的选择来与任何已经能用的基础设施集成。拥有多种配置方法只是我们达成此目标的一种方式。
需要知道的事情 (最佳实践和“问题”) 必读!!!¶
http
和http-socket
选项是完全不同的。第一个生成一个额外的进程,转发请求到一系列的worker (将它想象为一种形式的盾牌,与apache或者nginx同级),而第二个设置worker为原生使用http协议。
TL/DR: 如果你计划直接将uWSGI公开,那么使用--http
,如果你想要在一个使用http与后端通信的web服务器后代理它,那么使用--http-socket
。 .. seealso:: 原生HTTP支持
直到uWSGI 2.1,默认情况下,发送
SIGTERM
信号给uWSGI意味着“粗鲁地重载栈”,然而习惯是用SIGTERM
信号来关闭一个应用。要关闭uWSGI,请使用SIGINT
或者SIGQUIT
代替。 如果你完全不能忍受uWSGI如此地不尊重SIGTERM
,那么务必启用die-on-term
选项。幸运的是,这个糟糕的选择已经在uWSGI 2.1修复了。如果你计划托管多个应用,那么就当做是帮你自己一个忙,看看 uWSGI Emperor —— 多应用部署 文档。
始终使用uwsgitop,通过 uWSGI Stats服务器 或一些类似的东东来监控你的应用健康度。
uWSGI可以在核心部分包含特性,也可以将特性作为插件加载。由操作系统发行版提供提供的uWSGI包倾向于模块化。在这样的步骤中,确保使用
plugins
选项加载你所需的插件。识别一个未加载的插件的一个好症状是日志中像”Unavailable modifier requested”这样的消息。如果你正使用由发行版提供的包,那么仔细检查你是否已经安装了用于你所选的语言的插件。配置文件支持一种有限形式的继承、变量、if构造和简单的循环。看看 配置逻辑 和 uWSGI是如何解析配置文件的 页面。
要路由请求到一个指定的插件,web服务器需要传递一个称为modifier的魔术数字到uWSGI实例。默认情况下,这个数字被设置为0,这会映射到Python。例如,路由一个请求到一个
PSGI
应用要求你设置modifier为5
- 或者可选地加载PSGI插件作为modifier0
。(这将意味着,所有的无modifier的请求将会被认为是Perl的。)没有设置要使用的进程数或线程数的魔法规则。它是灰常依赖于应用和系统的。简单的算术,例如
processes = 2 * cpucores
,并不够。你需要对多种步骤进行实验,并且准备好不断监控当你的应用。uwsgitop
会是一个帮你找到最佳值的好工具。如果一个HTTP请求有请求体 (例如由表单生成的POST请求),那么你 必须 在你的应用中读取 (消费) 它。如果你不这么做,那么通过你的web服务器的通信套接字可能会被重写。如果你懒癌犯了,那么你可以使用
post-buffering
选项,它会自动地为你读取数据。对于Rack
应用,这是自动启用的。经常检查你的应用的内存使用。
memory-report
选项会是你最好的伙伴。如果你计划使用UNIX socket (与TCP相对),那么记住,它们是标准的文件系统对象。这意味着,它们有权限,并且同样地,你的web服务器必须有对它们的写权限。
常识:不要以root用户运行uWSGI实例。你可以作为root启动你的uWSGI们,但是确保使用
uid
和gid
选项来移除权限。uWSGI试图在一切可能的情况下(滥)用 fork() 调用的写时拷贝语义。默认情况下,它会在加载你的应用之后进行fork,以尽可能的共享它们的内存。如果出于某些原因,不期望这个行为,那么使用
lazy-apps
选项。这将会指示uWSGI在每个worker的fork()
之后加载应用。注意,有一个更老的名为lazy
的选项,它是一种更侵入以及极度不推荐的方式 (它仍然能用,只是为了向后兼容)默认情况下,Python插件并不初始化GIL。这意味着你由应用生成的线程并不会运行。如果你需要线程,记得通过
enable-threads
启用它们。在多线程模式(使用threads
选项)下运行uWSGI将会自动启用线程支持。这种“奇怪的”默认行为是出于性能考量的,这并不可耻。如果你在请求期间生成一个新的进程,那么它户继承生成它的worker的文件描述符 - 包括连接到web服务器/路由器的socket。如果你并不想要这种行为,那么设置
close-on-exec
选项。Ruby垃圾回收器是默认配置的,它会在每个请求之后运行。这是一个激进的策略,可能会减缓一点你的应用 —— 但是CPU资源比内存便宜,特别是比耗尽内存便宜。要调整这个评论,则使用
ruby-gc <freq>
选项。在OpenBSD, NetBSD和FreeBSD < 9上,SysV IPC信号量被用作锁子系统。这些操作系统倾向于限制可分配信号量的数目为一个非常小的值。如果你计划运行超过1个uWSGI实例,那么你应该提高默认限制。FreeBSD 9有POSIX信号量,所以你无需为它费心。
不要使用与构建uWSGI二进制文件本身的配置文件不同的配置文件来构建插件 —— 除非你喜欢遭罪,或者 确切 知道你在干什么。
默认情况下,uWSGI为每个请求头分配一个非常小的缓存 (4096字节)。如果你的日志中开始收到”invalid request block size”,这可能意味着你需要更大的缓存了。通过``buffer-size`` 选项来增加它(最大值为65535)。
注解
如果你的日志中接收到了请求块大小为‘21573’,那这可能意味着你使用HTTP协议与一个使用uwsgi协议的实例进行通信。不要这样做。
如果你的 (Linux) 服务器似乎有大量的idle状态的worker,但性能仍然不佳,那么你或许想要看看
ip_conntrack_max
系统变量 (/proc/sys/net/ipv4/ip_conntrack_max
) 的值,然后增加它以看看是否有帮助。一些Linux发行版 (即,Debian 4 Etch, RHEL / CentOS 5)混合了较新的内核和非常老的用户空间。这种组合会让uWSGI构建系统显示错误 (在
unshare()
, pthread locking,inotify
...之上最值得注意)。你可以给’make’ (或者任何你用来构建它的方式) 加上前缀CFLAGS="-DOBSOLETE_LINUX_KERNEL"
,强制uWSGI对较老的系统进行配置默认情况下,在uWSGI启动的适合,stdin被重新映射到
/dev/null
。如果你需要一个有效的stdin (用于调试、管道等等),那么添加--honour-stdin
。你可以轻松地添加不存在的选项到你的配置文件中 (例如占位符、自定义选项或者应用相关的配置项)。这是一个非常方便的特性,但一旦有错别字,那就头疼了。strict模式 (
--strict
) 将禁用此特性,并且只允许有效的uWSGI选项。一些插件 (最明显的是Python和Perl) 具有代码自动重载机制。虽然这也许听起来很诱人,但是你必须只在开发阶段使用它们,因为它们真的很重。例如,Python的–py-autoreload选项将会在每个检查周期中扫描你整个模块树。
wsgi.file_wrapper
是WSGI标准的一个优化。在个别情况下,它会引发错误。例如,当在Python 3.5中返回一个内存中字节缓存 (io.Bytesio) 的时候。看看这个 issue 。你可以通过设置选项wsgi-disable-file-wrapper
为true
来禁用它。
配置uWSGI¶
可以使用一些不同的方法对uWSGI进行配置。在相同的uWSGI调用中,所有的配置方法或许是混合搭配的。
注解
一些配置方法或许需要特定的插件 (例如,sqlite和ldap)。
参见
配置系统是统一的,因此,每一个命令行选项与配置文件中的项1:1对应。
例子:
uwsgi --http-socket :9090 --psgi myapp.pl
可以写成
[uwsgi]
http-socket = :9090
psgi = myapp.pl
加载配置文件¶
除了简单的磁盘文件,uWSGI支持通过多种方式加载配置文件:
uwsgi --ini http://uwsgi.it/configs/myapp.ini # HTTP
uwsgi --xml - # standard input
uwsgi --yaml fd://0 # file descriptor
uwsgi --json 'exec://nc 192.168.11.2:33000' # arbitrary executable
注解
还能使用更多晦涩的文件源,例如 Emperor, 嵌入式结构 (两种), 动态库符号和ELF部分。
魔术变量¶
uWSGI配置文件可以包含“魔术”变量,以百分号%作为前缀。目前,定义了如下魔术变量 (你可以通过 uwsgi.magic_table
在Python中访问它们)。
%v | vassals目录 (pwd) |
%V | uWSGI版本 |
%h | 主机名 |
%o | 原始配置文件名,在命令行指定 |
%O | 与%o相同,但指的是第一个非模板配置文件 (1.9.18版本) |
%p | 配置文件的绝对路径 |
%P | 与%p相同,但指的是第一个非模板配置文件 ,但指的是第一个非模板配置文件 (1.9.18版本) |
%s | 配置文件名 |
%S | 与%s相同,但指的是第一个非模板配置文件 (1.9.18版本) |
%d | 包含配置文件的目录的绝对路径 |
%D | 与%d相同,但指的是第一个非模板配置文件 (1.9.18版本) |
%e | 配置文件的扩展名 |
%E | 与%e相同,但指的是第一个非模板配置文件 (1.9.18版本) |
%n | 不带扩展名的文件名 |
%N | 与%n相同,但指的是第一个非模板配置文件 (1.9.18版本) |
%c | 包含配置文件的目录名 (1.3+版本) |
%C | 与%c相同,但指的是第一个非模板配置文件 (1.9.18版本) |
%t | unix时间 (以秒为单位,在实例启动时收集) (1.9.20-dev+版本) |
%T | unix时间 (以微秒为单位,在实例启动时收集) (version 1.9.20-dev+) |
%x | 当前段的标识符,例如, config.ini:section (1.9-dev+版本) |
%X | 与%x相同,但指的是第一个非模板配置文件 (1.9.18版本) |
%i | 文件的inode数字 (2.0.1版本) |
%I | 与%i相同,但指的是第一个非模板配置文件 |
%0..%9 | 包含配置文件的目录的全路径的一个特定的组件 (1.3+版本) |
%[ | ANSI转义”\033” (对于打印颜色有用) |
%k | 检测到的cpu核数 (1.9.20-dev+版本) |
%u | 运行进程的用户的uid (2.0版本) |
%U | 运行进程的用户的用户名 (如果可用,否则回退到uid) (2.0版本) |
%g | 运行进程的用户的gid (2.0版本) |
%G | 运行进程的用户所在组名 (如果可用,否则回退到gid) (2.0版本) |
%j | 完整配置路径的djb33x哈希的HEX表示 |
%J | 与%j相同,但指的是第一个非模板配置文件 |
注意,它们大多数指的是它们所在的文件,即使那个文件被包含在其他文件中。
一个例外是大部分的大写版本,指的是加载的第一个非模板配置文件。这意味着,第一个配置文件并不是通过 --include
或者 --inherit
加载的,而是通过例如 --ini
, --yaml
或者 --config
加载的。这些是为了使用emperor,来指向用 --vassals-include
或者 --vassals-inherit
包含的实际的vassal配置文件,而不是模板。
例如,这里是 funnyapp.ini
。
[uwsgi]
socket = /tmp/%n.sock
module = werkzeug.testapp:test_app
processes = 4
master = 1
%n
将会使用配置文件的名字来替代,不加扩展名,因此,这个情况下的结果将会是
[uwsgi]
socket = /tmp/funnyapp.sock
module = werkzeug.testapp:test_app
processes = 4
master = 1
占位符¶
占位符是通过设置你自己发明的一个新的配置变量,在配置时间定义的自定义的魔术变量。
[uwsgi]
; These are placeholders...
my_funny_domain = uwsgi.it
set-ph = max_customer_address_space=64
set-placeholder = customers_base_dir=/var/www
; And these aren't.
socket = /tmp/sockets/%(my_funny_domain).sock
chdir = %(customers_base_dir)/%(my_funny_domain)
limit-as = %(max_customer_address_space)
可以直接指定占位符,或者使用 set-placeholder
/ set-ph
选项。后者在以下情况下有用:
- 更明显的表示出你在设置的是占位符,而不是常规选项。
- 在命令行设置选项,因为诸如
--foo=bar
这样的未知选项会被拒绝,但是--set-placeholder foo=bar
是可以的。 - 当启用strict模式的时候,设置占位符。
占位符是可访问的,像任何uWSGI选项一样,在你的应用代码中,通过 uwsgi.opt
进行访问。
import uwsgi
print uwsgi.opt['customers_base_dir']
这个特性可以被(滥)用来减少你的应用所需的配置文件的数目。
类似地,可以使用 $(ENV_VAR) 和 @(file_name) 语法来包含环境变量和外部文本文件的内容。又见 uWSGI是如何解析配置文件的.
占位符数学 (自uWSGI 1.9.20-dev起)¶
你可以使用这种特殊的语法,应用数学公式到占位符上:
[uwsgi]
foo = 17
bar = 30
; total will be 50
total = %(foo + bar + 3)
运算符之间不要忘了空格。
在管道中(而不是一般的数学格式)执行运算:
[uwsgi]
foo = 17
bar = 30
total = %(foo + bar + 3 * 2)
‘total’ 将会被计算为100:
(((foo + bar) + 3) * 2)
可以用递增和递减快捷方式
[uwsgi]
foo = 29
; remember the space !!!
bar = %(foo ++)
bar将会是30
如果在两个项之间你不指定一个运算符,那么会假设使用“字符串连接”:
[uwsgi]
foo = 2
bar = 9
; remember the space !!!
bar = %(foo bar ++)
头两个项将会被计算为‘29’ (不是11,因为未指定任何数学运算)
‘@’魔法¶
我们已经看到了可以使用@(filename)形式来包含一个文件的内容
[uwsgi]
foo = @(/tmp/foobar)
真相是,’@’可以读取来自所有支持的uwsgi方案的内容
[uwsgi]
; read from a symbol
foo = @(sym://uwsgi_funny_function)
; read from binary appended data
bar = @(data://0)
; read from http
test = @(http://example.com/hello)
; read from a file descriptor
content = @(fd://3)
; read from a process stdout
body = @(exec://foo.pl)
; call a function returning a char *
characters = @(call://uwsgi_func)
环境变量¶
当作为环境变量传递时,会把选项首字母大写,并加上 UWSGI_ 前缀,而破折号会被下划线替代。
注解
该方法并不支持相同配置变量的一些值。
例子:
UWSGI_SOCKET=127.0.0.1 UWSGI_MASTER=1 UWSGI_WORKERS=3 uwsgi
INI文件¶
.INI文件实际上是一种标准的配置格式,用在许多应用中。它由 [section]
和 key=value
对组成。
一个样例uWSGI INI配置:
[uwsgi]
socket = /tmp/uwsgi.sock
socket = 127.0.0.1:8000
workers = 3
master = true
默认情况下,uWSGI使用 [uwsgi]
段,但是你可以在使用 filename:section
语法加载INI文件的时候指定另一个段名,也就是:
uwsgi --ini myconf.ini:app1
作为选择,你可以通过省略文件名并只指定段名来从相同的文件中加载另一个段。注意,技术上来讲,这会从上个加载的.ini文件中加载命名段,而不是从当前的文件中加载,因此,当包含其他文件的时候,小心为上。
[uwsgi]
# This will load the app1 section below
ini = :app1
# This will load the defaults.ini file
ini = defaults.ini
# This will load the app2 section from the defaults.ini file!
ini = :app2
[app1]
plugin = rack
[app2]
plugin = php
- 行内空格是不重要的。
- 以分号 (
;
) 或者一个哈希/井号 (#
) 开头的行会被当成注释忽略。 - 可以在没有值那个部分的情况下设置布尔型的值。因此,简单的
master
等价于master=true
。这或许与其他INI解析器,例如paste.deploy
,并不兼容。 - 为了方便,uWSGI特殊识别裸
.ini
参数,因此,调用uwsgi myconf.ini
等价于uwsgi --ini myconf.ini
。
XML文件¶
根节点应该是 <uwsgi>
,而选项值是节点文本。
一个例子:
<uwsgi>
<socket>/tmp/uwsgi.sock</socket>
<socket>127.0.0.1:8000</socket>
<master/>
<workers>3</workers>
</uwsgi>
文件中还可以有多个 <uwsgi>
节,由不同的 id
属性标记。要选择使用的节,则在 xml
选项中的文件名之后指定其id,使用冒号作为分隔符。当使用这种 id 模式时,文件的根节点可以是任何你喜欢的。这会允许你将 uwsgi
配置节点嵌入到其他XML文件中。
<i-love-xml>
<uwsgi id="turbogears"><socket>/tmp/tg.sock</socket></uwsgi>
<uwsgi id="django"><socket>/tmp/django.sock</socket></uwsgi>
</i-love-xml>
- 可在没有文本值的情况下设置布尔类型的值。
- 为了方便起见,uWSGI特殊识别裸
.ini
参数,因此,调用uwsgi myconf.xml
等价于uwsgi --xml myconf.xml
。
JSON 文件¶
JSON应该使用一个键值对来表示一个对象,键是 “uwsgi” ,而值是配置变量的一个对象。支持原生JSON列表、布尔型和数字。
一个例子:
{"uwsgi": {
"socket": ["/tmp/uwsgi.sock", "127.0.0.1:8000"],
"master": true,
"workers": 3
}}
再次,可以使用文件名后冒号来加载一个命名段。
{"app1": {
"plugin": "rack"
}, "app2": {
"plugin": "php"
}}
然后这样加载:
uwsgi --json myconf.json:app2
YAML 文件¶
根元素应该是 uwsgi 。布尔型选项要设置成 true 或 1 。
一个例子:
uwsgi:
socket: /tmp/uwsgi.sock
socket: 127.0.0.1:8000
master: 1
workers: 3
另外,一个命名段可以使用文件名后的冒号进行加载。
app1:
plugin: rack
app2:
plugin: php
然后这样加载它:
uwsgi --yaml myconf.yaml:app2
SQLite配置¶
注解
构建中……
LDAP 配置¶
LDAP是集中uWSGI服务器大型集群配置的一种灵活的方式。配置它是一个负责的主题。见 使用LDAP配置uWSGI 以获得更多信息。
回退配置¶
(从1.9.15-dev起可用)
如果你需要一个”重置到出厂默认设置”,或者”在用户把配置弄得一团糟的时候显示一个欢迎页面”的场景,那么回退配置就是你的银弹。
简单的例子¶
一个非常普遍的问题是screwing-up the port on which the instance is listening.
为了模拟这样的错误,我们试着以非特权用户来绑定80端口:
uwsgi --uid 1000 --http-socket :80
uWSGI将会退出:
bind(): Permission denied [core/socket.c line 755]
在内部 (从内核的角度),实例退出,状态为1
现在,我们想让实例在用户提供的配置失败的时候自动绑定到端口8080.
让我们来定义一个回退配置 (你可以将其保存为safe.ini):
[uwsgi]
print = Hello i am the fallback config !!!
http-socket = :8080
wsgi-file = welcomeapp.wsgi
现在,我们可以重新运行(损坏的)实例:
uwsgi --fallback-config safe.ini --uid 1000 --http-socket :80
现在,错误将如下:
bind(): Permission denied [core/socket.c line 755]
Thu Jul 25 21:55:39 2013 - !!! /home/roberto/uwsgi/uwsgi (pid: 7409) exited with status 1 !!!
Thu Jul 25 21:55:39 2013 - !!! Fallback config to safe.ini !!!
[uWSGI] getting INI configuration from safe.ini
*** Starting uWSGI 1.9.15-dev-a0cb71c (64bit) on [Thu Jul 25 21:55:39 2013] ***
...
正如你所见,实例检测到退出码1,并且用一个新的配置对自身打了二进制补丁 (无需更改pid,或者调用fork())
已破坏应用¶
另一个常见的问题是不能加载应用,但并非让整个站点挂掉,而是想要加载一个备用应用:
uwsgi --fallback-config safe.ini --need-app --http-socket :8080 --wsgi-file brokenapp.py
这里,键是–need-app。如果实例一直未能加载至少一个应用,那么它将会调用exit(1)。
多种回退层次¶
你的回退配置文件也可以指定一个fallback-config指令,允许多种回退层次。当心循环!!!
它是如何工作的¶
目标是在进程本身被销毁之前捕获到其退出码 (不想要调用另一个fork(),或者销毁已打开的文件描述符)
uWSGI大量使用atexit()钩子,因此我们仅需将回退处理器作为第一个进行注册 (钩子是以相反的顺序执行的)。
除此之外,我们需要在我们的atexit()钩子中获取退出码,这个默认不支持 (on_exit()函数现在已弃用)。
解决方法是用uwsgi_exit(x)对exit(x)“打补丁”,它是一个简单的设置uwsgi.last_exit_code内存指针的封装器。
现在,钩子仅需检测uwsgi.last_exit_code == 1,并最终再次execve()二进制文件,将回退配置传递给它
char *argv[3];
argv[0] = uwsgi.binary_path;
argv[1] = uwsgi.fallback_config;
argv[2] = NULL;
execvp(uwsgi.binary_path, argv);
注意事项¶
试着尽可能快地将–fallback-config放到你的配置树中。在回退文件被注册之前,各种配置解析器可能会失败 (调用exit(1))
配置逻辑¶
自1.1起,可以使用某些逻辑构建。
目前支持以下语句:
for
..endfor
if-dir
/if-not-dir
if-env
/if-not-env
if-exists
/if-not-exists
if-file
/if-not-file
if-opt
/if-not-opt
if-reload
/if-not-reload
—— 未公开
以上每个语句都导出了一个上下文值,你可以通过特殊的占位符 %(_)
来访问它们。例如,”for”语句设置 %(_)
为当前的迭代值。
警告
不支持递归逻辑,它会导致uWSGI即刻退出。
for¶
for迭代由空格隔开的字符串。以下三个代码块是等价的。
[uwsgi]
master = true
; iterate over a list of ports
for = 3031 3032 3033 3034 3035
socket = 127.0.0.1:%(_)
endfor =
module = helloworld
<uwsgi>
<master/>
<for>3031 3032 3033 3034 3035</for>
<socket>127.0.0.1:%(_)</socket>
<endfor/>
<module>helloworld</module>
</uwsgi>
uwsgi --for="3031 3032 3033 3034 3035" --socket="127.0.0.1:%(_)" --endfor --module helloworld
注意,for循环是分别被应用到块中的每一行的,而不是应用到块整体上。例如,这个:
[uwsgi]
for = a b c
socket = /var/run/%(_).socket
http-socket = /var/run/%(_)-http.socket
endfor =
展开为:
[uwsgi]
socket = /var/run/a.socket
socket = /var/run/b.socket
socket = /var/run/c.socket
http-socket = /var/run/a-http.socket
http-socket = /var/run/b-http.socket
http-socket = /var/run/c-http.socket
if-env¶
检查是否定义了一个环境变量,将其值放在上下文占位符中。
[uwsgi]
if-env = PATH
print = Your path is %(_)
check-static = /var/www
endif =
socket = :3031
if-exists¶
检查一个文件或目录是否存在。设置上下文占位符为找到的文件名。
[uwsgi]
http = :9090
; redirect all requests if a file exists
if-exists = /tmp/maintenance.txt
route = .* redirect:/offline
endif =
注解
上述例子使用 uWSGI内部路由.
if-file¶
检查给定的路径是否存在,是否为一个常规的文件。设置上下文占位符为找到的文件名。
<uwsgi>
<plugins>python</plugins>
<http-socket>:8080</http-socket>
<if-file>settings.py</if-file>
<module>django.core.handlers.wsgi:WSGIHandler()</module>
<endif/>
</uwsgi>
if-dir¶
检查给定的路径是否存在,是否为一个目录。设置上下文占位符为找到的文件名。
uwsgi:
socket: 4040
processes: 2
if-dir: config.ru
rack: %(_)
endif:
if-opt¶
检查是否设置了给定选项,或者给定选项是否具有一个给定的值。设置上下文占位符为选项引用的值。
检查是否设置了一个选项,仅需将选项名传递给 if-opt
。
uwsgi:
cheaper: 3
if-opt: cheaper
print: Running in cheaper mode, with initially %(_) processes
endif:
要检查给定选项是否具有一个给定的值,则将
option-name=value
传递给 if-opt
。
uwsgi:
# Set busyness parameters if it was chosen
if-opt: cheaper-algo=busyness
cheaper-busyness-max: 25
cheaper-busyness-min: 10
endif:
由于uWSGI解析其配置文件的方式,你只能使用uWSGI之前解析到的选项。特别是,这意味着:
只考虑在
if-opt
选项之上设置的选项。这包含了任何由前面include
(或者特定类型的包含,例如ini
) 选项设置的任何选项,但不要包含由前面inherit
选项设置的选项)。if-opt
是在展开魔术变量之后,展开占位符和其他变量之前进行处理的。因此,如果你使用if-opt
来比较一个选项的值,那么只使用魔术变量来核对该值和配置文件中一致。如果你在
if-opt
块内使用上下文占位符%(_)
,那么应该没问题:任何占位符稍后将会被展开。如果多次指定一个选项,那么
if-opt
只能看到第一个的值。只会看到显式设置的值,而不会看到隐式默认值。
uWSGI选项¶
这是一份自动生成的uWSGI选项参考列表。
它与你可以通过 --help
选项获得的输出相同。
对于新手而言,本页可能是了解uWSGI最糟糕的方法。如果你还在学习该项目的工作方式,那么你应该阅读各种快速入门和教程。
每一个选项都有以下属性:
- argument: 它是结构选项 (由getopt()/getopt_long()使用) has_arg元素。可以是’必须的’, ‘无参数’或者’可选参数’
- shortcut: 有些选项可以用短形式指定 (一个破折号,后面跟着单个字母)
- parser: 这是uWSGI如何解析参数。有几十种方式,最常见的方式是’uwsgi_opt_set_str’ 何时接收简单的字符串,’uwsgi_opt_set_int’ 何时接收32位数字,’uwsgi_opt_add_string_list’ 何时可以指定该参数多次来构建一个列表。
- help: 帮助消息,与你从
uwsgi --help
获得的相同 - reference: 一个到让你更好理解并且提供选项上下文的文档页面
你可以添加更多详细信息到这个页面上,编辑https://github.com/unbit/uwsgi-docs/blob/master/optdefs.pl (拜托,在发送一个pull请求之前,再三检查)
uWSGI核心¶
uwsgi-socket¶
argument
: 必需参数
shortcut
: -s
parser
: uwsgi_opt_add_socket
help
: 使用uwsgi协议绑定到指定UNIX/TCP socket上
suwsgi-socket¶
argument
: 必需参数
parser
: uwsgi_opt_add_ssl_socket
help
: 通过SSL使用uwsgi协议绑定到指定UNIX/TCP socket上
ssl-socket¶
argument
: 必需参数
parser
: uwsgi_opt_add_ssl_socket
help
: 使用SSL之上的uwsgi协议绑定到指定的UNIX/TCP socket
http11-socket¶
argument
: 必需参数
parser
: uwsgi_opt_add_socket
help
: 使用HTTP 1.1 (Keep-Alive)协议绑定到指定UNIX/TCP socket上
fastcgi-nph-socket¶
argument
: 必需参数
parser
: uwsgi_opt_add_socket
help
: 使用FastCGI协议绑定到指定UNIX/TCP socket上 (nph模式)
scgi-nph-socket¶
argument
: 必需参数
parser
: uwsgi_opt_add_socket
help
: 使用SCGI协议绑定到指定UNIX/TCP socket上 (nph模式)
puwsgi-socket¶
argument
: 必需参数
parser
: uwsgi_opt_add_socket
help
: 使用持久性uwsgi协议(puwsgi)绑定到指定的UNIX/TCP socket上
thunder-lock¶
argument
: 无参数
parser
: uwsgi_opt_true
help
: 序列化accept()使用 (如果可能的话)
reference
: 序列化accept(), 亦称惊群效应,亦亦称Zeeg难题
no-harakiri-after-req-hook¶
argument
: 无参数
parser
: uwsgi_opt_true
help
: 在after-request-hook期间不启用harakiri
xmlconfig¶
argument
: 必需参数
shortcut
: -x
parser
: uwsgi_opt_load_xml
flags
: UWSGI_OPT_IMMEDIATE
help
: 从xml文件加载配置
xml¶
argument
: 必需参数
shortcut
: -x
parser
: uwsgi_opt_load_xml
flags
: UWSGI_OPT_IMMEDIATE
help
: 从xml文件加载配置
fallback-config¶
argument
: 必需参数
parser
: uwsgi_opt_set_str
flags
: UWSGI_OPT_IMMEDIATE
help
: 当退出码为1的时候,使用指定的配置重新执行uwsgi
set¶
argument
: 必需参数
shortcut
: -S
parser
: uwsgi_opt_set_placeholder
flags
: UWSGI_OPT_IMMEDIATE
help
: 设置一个占位符或者选项
set-placeholder¶
argument
: 必需参数
parser
: uwsgi_opt_set_placeholder
flags
: UWSGI_OPT_IMMEDIATE
help
: 设置一个占位符
declare-option¶
argument
: 必需参数
parser
: uwsgi_opt_add_custom_option
flags
: UWSGI_OPT_IMMEDIATE
help
: 声明一个新的uWSGI自定义选项
reference
: 为你的实例定义新选项
resolve¶
argument
: 必需参数
parser
: uwsgi_opt_resolve
flags
: UWSGI_OPT_IMMEDIATE
help
: 把dns查询的结果放到指定的占位符上,语法:placeholder=name (立即选项)
for-glob¶
argument
: 必需参数
parser
: uwsgi_opt_logic
flags
: UWSGI_OPT_IMMEDIATE
help
: (选择逻辑) for循环 (扩展glob)
for-times¶
argument
: 必需参数
parser
: uwsgi_opt_logic
flags
: UWSGI_OPT_IMMEDIATE
help
: (选择逻辑) for循环 (扩展指定的数字到一个从1开始的列表)
for-readline¶
argument
: 必需参数
parser
: uwsgi_opt_logic
flags
: UWSGI_OPT_IMMEDIATE
help
: (选择逻辑) for循环 (扩展指定文件到一个行列表)
if-not-hostname¶
argument
: 必需参数
parser
: uwsgi_opt_logic
flags
: UWSGI_OPT_IMMEDIATE
help
: (选择逻辑) 检查主机名
if-hostname-match¶
argument
: 必需参数
parser
: uwsgi_opt_logic
flags
: UWSGI_OPT_IMMEDIATE
help
: (选择逻辑) 尝试匹配主机名到一个正则表达式
if-not-hostname-match¶
argument
: 必需参数
parser
: uwsgi_opt_logic
flags
: UWSGI_OPT_IMMEDIATE
help
: (选择逻辑) 尝试匹配主机名到一个正则表达式
if-exists¶
argument
: 必需参数
parser
: uwsgi_opt_logic
flags
: UWSGI_OPT_IMMEDIATE
help
: (选择逻辑) 检查文件/目录存在性
if-not-exists¶
argument
: 必需参数
parser
: uwsgi_opt_logic
flags
: UWSGI_OPT_IMMEDIATE
help
: (选择逻辑) 检查文件/目录存在性
if-directory¶
argument
: 必需参数
parser
: uwsgi_opt_logic
flags
: UWSGI_OPT_IMMEDIATE
help
: (选择逻辑) 检查目录存在性
inject-before¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_IMMEDIATE
help
: 在配置文件之前注入一个文本文件 (高级模板)
inject-after¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_IMMEDIATE
help
: 在配置文件之后注入一个文本文件 (高级模板)
connect-and-read¶
argument
: 必需参数
parser
: uwsgi_opt_connect_and_read
flags
: UWSGI_OPT_IMMEDIATE
help
: 连接到一个socket,并且等待来自它的数据
extract¶
argument
: 必需参数
parser
: uwsgi_opt_extract
flags
: UWSGI_OPT_IMMEDIATE
help
: 抽取/转储任何支持的地址到标准输出
buffer-size¶
argument
: 必需参数
shortcut
: -b
parser
: uwsgi_opt_set_64bit
help
: 设置内部缓冲大小
设置请求的最大大小 (排除request-body),这一般映射到请求头的大小。默认情况下,它是4k。如果你接收到了一个更大的请求 (例如,带有大cookies或者查询字符串),那么你也许需要增加它。它也是一个安全度量,所以调整为你的应用需要,而不是最大输出。
abstract-socket¶
argument
: 无参数
shortcut
: -a
parser
: uwsgi_opt_true
help
: 强制在abstract中使用UNIX socket (仅Linux)
freebind¶
argument
: 无参数
parser
: uwsgi_opt_true
help
: 把socket置于freebind模式
为每个由uWSGI创建的socket设置IP_FREEBIND标识。这类socket可以绑定到不存在的ip地址上。它的主要目的是为了高可用性 (仅Linux)
procname-prefix¶
argument
: 必需参数
parser
: uwsgi_opt_set_str
flags
: UWSGI_OPT_PROCNAME
help
: 添加一个前缀到进程名
procname-prefix-spaced¶
argument
: 必需参数
parser
: uwsgi_opt_set_str_spaced
flags
: UWSGI_OPT_PROCNAME
help
: 添加一个空间前缀到进程名
procname-append¶
argument
: 必需参数
parser
: uwsgi_opt_set_str
flags
: UWSGI_OPT_PROCNAME
help
: 附加一个字符串到进程名
procname-master¶
argument
: 必需参数
parser
: uwsgi_opt_set_str
flags
: UWSGI_OPT_PROCNAME
help
: 设置master进程名
emperor¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
help
: 运行Emperor
reference
: uWSGI Emperor —— 多应用部署
Emperor是一个特殊的uWSGI实例,旨在管理其他uWSGI进程 (即:vassal)。默认情况下,配置它来监控一个包含有效uWSGI配置文件的目录,每当创建了一个文件,就会生成一个新的实例,当动了这个文件,相应的实例就会被重载,当这个文件被移除,相应的实例就会被销毁。可以扩展以支持更多的范例
emperor-tyrant-initgroups¶
argument
: 无参数
parser
: uwsgi_opt_true
help
: 在Tyrant模式下,通过initgroups()添加而外的组设置
emperor-throttle¶
argument
: 必需参数
parser
: uwsgi_opt_set_int
help
: 为糟糕的vassal设置节流层次 (以毫秒为单位) (默认是1000)
emperor-max-throttle¶
argument
: 必需参数
parser
: uwsgi_opt_set_int
help
: 为糟糕的vassal设置最大节流层次 (以毫秒为单位) (默认是3分钟)
emperor-on-demand-extension¶
argument
: 必需参数
parser
: uwsgi_opt_set_str
help
: 搜索包含按需socket名的文本文件 (vassal名+扩展名)
emperor-on-demand-ext¶
argument
: 必需参数
parser
: uwsgi_opt_set_str
help
: 搜索包含即需socket名的文本文件 (vassal名 + 扩展)
emperor-on-demand-directory¶
argument
: 必需参数
parser
: uwsgi_opt_set_str
help
: 在按需模式下启用,绑定到指定目录下名字像vassal + .socket的unix socket
emperor-on-demand-dir¶
argument
: 必需参数
parser
: uwsgi_opt_set_str
help
: 在按需模式下启用,绑定到指定目录下名字像vassal + .socket的unix socket
emperor-on-demand-exec¶
argument
: 必需参数
parser
: uwsgi_opt_set_str
help
: 使用指定命令的输出作为按需socket名 (传递vassal名作为唯一的参数)
emperor-extra-extension¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
help
: 允许在Emperor使用指定的扩展名(将会用–config调用vassal)
emperor-extra-ext¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
help
: 允许在Emperor使用指定的扩展名(将会用–config调用vassal)
emperor-use-clone¶
argument
: 必需参数
parser
: uwsgi_opt_set_unshare
help
: 使用clone()而不是fork()来传递指定的unshare()标志
emperor-use-fork-server¶
argument
: 必需参数
parser
: uwsgi_opt_set_str
help
: 对于新的vassal,连接到指定的fork服务器,而不是使用简单的fork()
vassal-fork-base¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
help
: 对指定的vassal使用简单的fork() (而不是一个fork服务器)
emperor-collect-attribute¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
help
: 收集来自帝国监控器的指定vassal属性
vassals-inherit¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
help
: 添加配置模板到vassal配置中 (使用–inherit)
vassals-include¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
help
: 包含配置模板到vassal配置 (使用–include而不是–inherit)
vassals-inherit-before¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
help
: 添加配置模板到vassal配置中 (使用–inherit,在vassal文件之前解析)
vassals-include-before¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
help
: 包含配置模板到vassal配置 (使用–include而不是–inherit,在vassal文件之前解析)
vassal-sos-backlog¶
argument
: 必需参数
parser
: uwsgi_opt_set_int
help
: 当backlog队列拥有比指定的值多的项的时候,要求emperor紧急救援
reload-mercy¶
argument
: 必需参数
parser
: uwsgi_opt_set_int
help
: 设置the maximum time (以秒为单位) we wait for workers and other processes to die during reload/shutdown
worker-reload-mercy¶
argument
: 必需参数
parser
: uwsgi_opt_set_int
help
: 设置一个worker重载/关闭可以花费的最大时间 (以秒为单位,默认是60)
mule-reload-mercy¶
argument
: 必需参数
parser
: uwsgi_opt_set_int
help
: 设置一个mule重载/关闭可以花费的最大时间 (以秒为单位,默认是60)
max-requests-delta¶
argument
: 必需参数
parser
: uwsgi_opt_set_64bit
help
: 添加(worker_id * delta)到每个worker的max_requests值
cache-udp-server¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MASTER
help
: 绑定缓存udp服务器 (只用于设置/更新/删除) 到指定socket
cache-udp-node¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MASTER
help
: 发送缓存更新/删除到指定的缓存udp服务器
cache-use-last-modified¶
argument
: 无参数
parser
: uwsgi_opt_true
help
: 在每次缓存项修改时更新last_modified_at时间戳 (默认禁止)
load-file-in-cache-gzip¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
help
: 使用gzip压缩加载缓存中的一个静态文件
spooler¶
argument
: 必需参数
shortcut
: -Q
parser
: uwsgi_opt_add_spooler
flags
: UWSGI_OPT_MASTER
help
: 在指定目录上运行一个spooler
spooler-external¶
argument
: 必需参数
parser
: uwsgi_opt_add_spooler
flags
: UWSGI_OPT_MASTER
help
: 映射spooler请求到由外部实例管理的spooler目录
spooler-processes¶
argument
: 必需参数
parser
: uwsgi_opt_set_int
flags
: UWSGI_OPT_IMMEDIATE
help
: 为spooler设置进程数
signal-timer¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MASTER
help
: 设置定时器 (语法: <signal> <seconds>)
timer¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MASTER
help
: 设置定时器 (语法: <signal> <seconds>)
signal-rbtimer¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MASTER
help
: 添加一个红黑定时器 (语法: <signal> <seconds>)
rbtimer¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MASTER
help
: 添加一个红黑定时器 (语法: <signal> <seconds>)
flock¶
argument
: 必需参数
parser
: uwsgi_opt_flock
flags
: UWSGI_OPT_IMMEDIATE
help
: 在启动前锁住指定文件,如果已经被锁了,则退出
flock-wait¶
argument
: 必需参数
parser
: uwsgi_opt_flock_wait
flags
: UWSGI_OPT_IMMEDIATE
help
: 在启动前锁住指定文件,如果已经被锁了,则等待
flock2¶
argument
: 必需参数
parser
: uwsgi_opt_set_str
flags
: UWSGI_OPT_IMMEDIATE
help
: 在日志记录/守护设置之后锁住指定文件,如果已经被锁了,则退出
flock-wait2¶
argument
: 必需参数
parser
: uwsgi_opt_set_str
flags
: UWSGI_OPT_IMMEDIATE
help
: 在日志记录/守护设置之后锁住指定文件,如果已经被锁了,则等待
pivot-root¶
argument
: 必需参数
parser
: uwsgi_opt_set_str
help
: pivot_root()到指定目录 (必须用一个空格来分隔new_root和put_old)
pivot_root¶
argument
: 必需参数
parser
: uwsgi_opt_set_str
help
: pivot_root()到指定目录 (必须用一个空格来分隔new_root和put_old)
immediate-uid¶
argument
: 必需参数
parser
: uwsgi_opt_set_immediate_uid
flags
: UWSGI_OPT_IMMEDIATE
help
: 立即setuid到指定的user/uid
immediate-gid¶
argument
: 必需参数
parser
: uwsgi_opt_set_immediate_gid
flags
: UWSGI_OPT_IMMEDIATE
help
: 立即setgid到指定的group/gid
setns-socket¶
argument
: 必需参数
parser
: uwsgi_opt_set_str
flags
: UWSGI_OPT_MASTER
help
: 公开一个返回来自/proc/self/ns的名字空间fds的 unix socket
jail-ip4¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
help
: add an ipv4 address to the FreeBSD jail
hook-accepting-once¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
help
: 在每个worker进入接收阶段之后运行指定的钩子 (每个实例一次)
hook-accepting1-once¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
help
: 在第一个worker进入接收阶段之后运行指定的钩子 (每个实例一次)
hook-touch¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
help
: 当碰了指定文件时运行指定的钩子 (语法:<file> <action>)
hook-emperor-reload¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
help
: 当Emperor发送一个重载消息时运行指定的钩子
hook-as-emperor¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
help
: 在启动vassal之后,在emperor中运行指定的钩子
hook-as-on-demand-vassal¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
help
: 每当vassal进入按需模式时运行指定的钩子
hook-as-on-config-vassal¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
help
: 每当vassal检测到按需vassal的配置改动时运行指定的钩子
hook-as-emperor-before-vassal¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
help
: 在生成新的vassal之前运行指定的钩子
hook-as-vassal-before-drop¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
help
: 在移除vassal特权之前,运行指定的钩子
hook-as-emperor-setns¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
help
: 在emperor中运行指定的钩子,进入vassal名字空间
mount-as-emperor¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
help
: 启动了vassal之后,在emperor中挂载文件系统
umount-as-emperor¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
help
: 启动了vassal之后,在emperor中取消挂载文件系统
wait-for-interface-timeout¶
argument
: 必需参数
parser
: uwsgi_opt_set_int
help
: 设置wait-for-interface的超时时间
call-as-vassal3¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
help
: 在exec() vassal之前调用指定函数(char *, uid_t, gid_t)
call-as-emperor¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
help
: 启动了vassal之后,在emperor中调用指定函数()
call-as-emperor1¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
help
: 启动了vassal之后,在emperor中调用指定函数
call-as-emperor2¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
help
: 启动了vassal之后,在emperor中调用指定函数(char *, pid_t)
call-as-emperor4¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
help
: 启动了vassal之后,在emperor中调用指定函数(char *, pid_t, uid_t, gid_t)
yaml¶
argument
: 必需参数
shortcut
: -y
parser
: uwsgi_opt_load_yml
flags
: UWSGI_OPT_IMMEDIATE
help
: 从yaml文件加载配置
yml¶
argument
: 必需参数
shortcut
: -y
parser
: uwsgi_opt_load_yml
flags
: UWSGI_OPT_IMMEDIATE
help
: 从yaml文件加载配置
json¶
argument
: 必需参数
shortcut
: -j
parser
: uwsgi_opt_load_json
flags
: UWSGI_OPT_IMMEDIATE
help
: 从json文件加载配置
js¶
argument
: 必需参数
shortcut
: -j
parser
: uwsgi_opt_load_json
flags
: UWSGI_OPT_IMMEDIATE
help
: 从json文件加载配置
reload-on-as¶
argument
: 必需参数
parser
: uwsgi_opt_set_megabytes
flags
: UWSGI_OPT_MEMORY
help
: 如果地址空间比指定百万字节高,那么重载
reload-on-rss¶
argument
: 必需参数
parser
: uwsgi_opt_set_megabytes
flags
: UWSGI_OPT_MEMORY
help
: 如果rss内存比指定百万字节高,那么重载
evil-reload-on-as¶
argument
: 必需参数
parser
: uwsgi_opt_set_megabytes
flags
: UWSGI_OPT_MASTER | UWSGI_OPT_MEMORY
help
: 如果worker的地址空间比指定百万字节高,那么强制master重载这个worker
evil-reload-on-rss¶
argument
: 必需参数
parser
: uwsgi_opt_set_megabytes
flags
: UWSGI_OPT_MASTER | UWSGI_OPT_MEMORY
help
: 如果worker的rss内存比指定百万字节高,那么强制master重载这个worker
reload-on-fd¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MASTER
help
: 如果指定的文件描述符准备好了,则重载
brutal-reload-on-fd¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MASTER
help
: 如果指定的文件描述符准备好了,则粗鲁重载
pcre-jit¶
argument
: 无参数
parser
: uwsgi_opt_pcre_jit
flags
: UWSGI_OPT_IMMEDIATE
help
: 启用pcre jit (如果可用)
touch-reload¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MASTER
help
: 如果修改/碰了指定的文件,那么重载uWSGI
touch-workers-reload¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MASTER
help
: 如果修改/碰了指定的文件,那么触发(仅)worker的重载
touch-chain-reload¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MASTER
help
: 如果修改/碰了指定的文件,那么触发链式重载
touch-logrotate¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MASTER | UWSGI_OPT_LOG_MASTER
help
: 如果修改/碰了指定的文件,那么触发日志循环
touch-logreopen¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MASTER | UWSGI_OPT_LOG_MASTER
help
: 如果修改/碰了指定的文件,那么触发日志的重新打开
touch-exec¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MASTER
help
: 当修改/碰了指定的文件时,运行命令 (语法:file command)
touch-signal¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MASTER
help
: 当修改/碰了指定的文件时,发送信号 (语法:file signal)
fs-reload¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MASTER
help
: 当修改了指定的文件系统对象时,优雅重载
fs-brutal-reload¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MASTER
help
: 当修改了指定的文件系统对象时,粗鲁重载
fs-signal¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MASTER
help
: 当修改了指定的文件系统对象时,引发一个uwsgi信号 (语法:file signal)
check-mountpoint¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MASTER
help
: 如果一个文件系统不再可达,则摧毁实例(对于可靠Fuse管理有用)
mountpoint-check¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MASTER
help
: 如果一个文件系统不再可达,则摧毁实例(对于可靠Fuse管理有用)
check-mount¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MASTER
help
: 如果一个文件系统不再可达,则摧毁实例(对于可靠Fuse管理有用)
mount-check¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MASTER
help
: 如果一个文件系统不再可达,则摧毁实例(对于可靠Fuse管理有用)
post-buffering-bufsize¶
argument
: 必需参数
parser
: uwsgi_opt_set_64bit
help
: 设置post buffering模式下,read()的缓冲大小
body-read-warning¶
argument
: 必需参数
parser
: uwsgi_opt_set_64bit
help
: 设置开始打印警告之前对请求体的内存分配允许量(以百万字节为单位)
reload-on-exception-type¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
help
: 当引发了一个指定的异常类型时,重载worker
reload-on-exception-value¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
help
: 当引发了一个指定的异常值时,重载worker
reload-on-exception-repr¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
help
: 当引发了一个指定的异常类型+值(语言特定)时,重载worker
exception-handler¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MASTER
help
: 添加一个异常处理器
metric¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_METRICS|UWSGI_OPT_MASTER
help
: 添加一个自定义的度量
metric-threshold¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_METRICS|UWSGI_OPT_MASTER
help
: 添加一个度量阈值/告警
metric-alarm¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_METRICS|UWSGI_OPT_MASTER
help
: 添加一个度量阈值/告警
alarm-metric¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_METRICS|UWSGI_OPT_MASTER
help
: 添加一个度量阈值/告警
metrics-dir¶
argument
: 必需参数
parser
: uwsgi_opt_set_str
flags
: UWSGI_OPT_METRICS|UWSGI_OPT_MASTER
help
: 将度量作为文本文件导出到指定目录
metrics-dir-restore¶
argument
: 无参数
parser
: uwsgi_opt_true
flags
: UWSGI_OPT_METRICS|UWSGI_OPT_MASTER
help
: 恢复从度量目录获取的最后值
metric-dir¶
argument
: 必需参数
parser
: uwsgi_opt_set_str
flags
: UWSGI_OPT_METRICS|UWSGI_OPT_MASTER
help
: 将度量作为文本文件导出到指定目录
metric-dir-restore¶
argument
: 无参数
parser
: uwsgi_opt_true
flags
: UWSGI_OPT_METRICS|UWSGI_OPT_MASTER
help
: 恢复从度量目录获取的最后值
metrics-no-cores¶
argument
: 无参数
parser
: uwsgi_opt_true
flags
: UWSGI_OPT_METRICS|UWSGI_OPT_MASTER
help
: 禁用核心相关度量的生成
reference
: 度量(Metrics)子系统
不要公开异步核心的度量。
stats-server¶
argument
: 必需参数
parser
: uwsgi_opt_set_str
flags
: UWSGI_OPT_MASTER
help
: 在指定的地址上启用统计信息服务器
stats-http¶
argument
: 无参数
parser
: uwsgi_opt_true
flags
: UWSGI_OPT_MASTER
help
: 给统计信息服务器的json输出加上http头部前缀
stats-push¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MASTER|UWSGI_OPT_METRICS
help
: 推送统计信息json到指定的目的地
stats-pusher-default-freq¶
argument
: 必需参数
parser
: uwsgi_opt_set_int
flags
: UWSGI_OPT_MASTER
help
: 设置统计信息推送器的默认频率
stats-pushers-default-freq¶
argument
: 必需参数
parser
: uwsgi_opt_set_int
flags
: UWSGI_OPT_MASTER
help
: 设置统计信息推送器的默认频率
stats-no-cores¶
argument
: 无参数
parser
: uwsgi_opt_true
flags
: UWSGI_OPT_MASTER
help
: 禁用核心相关的统计信息的生成
reference
: 度量(Metrics)子系统
不要在统计信息服务器中公开关于核心的信息。
stats-no-metrics¶
argument
: 无参数
parser
: uwsgi_opt_true
flags
: UWSGI_OPT_MASTER
help
: 不要在统计信息输出中包含度量
reference
: 度量(Metrics)子系统
一点也不要在统计信息服务器公开度量。
master-fifo¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MASTER
help
: 启用master fifo
subscription-notify-socket¶
argument
: 必需参数
parser
: uwsgi_opt_set_str
flags
: UWSGI_OPT_MASTER
help
: 为订阅设置通知socket
subscription-mountpoints¶
argument
: 无参数
parser
: uwsgi_opt_true
flags
: UWSGI_OPT_MASTER
help
: 为订阅系统启用挂载点支持
subscription-mountpoint¶
argument
: 无参数
parser
: uwsgi_opt_true
flags
: UWSGI_OPT_MASTER
help
: 为订阅系统启用挂载点支持
legion-mcast¶
argument
: 必需参数
parser
: uwsgi_opt_legion_mcast
flags
: UWSGI_OPT_MASTER
help
: 成为一个legion的成员 (multicast的快捷方式)
legion-node¶
argument
: 必需参数
parser
: uwsgi_opt_legion_node
flags
: UWSGI_OPT_MASTER
help
: 添加一个节点到legion
legion-tolerance¶
argument
: 必需参数
parser
: uwsgi_opt_set_int
flags
: UWSGI_OPT_MASTER
help
: 设置legion子系统的容许度
legion-death-on-lord-error¶
argument
: 必需参数
parser
: uwsgi_opt_set_int
flags
: UWSGI_OPT_MASTER
help
: 如果lord钩子中的一个失败的话,则宣告自身在指定秒数内都是一个死掉的节点
legion-skew-tolerance¶
argument
: 必需参数
parser
: uwsgi_opt_set_int
flags
: UWSGI_OPT_MASTER
help
: 设置legion子系统的时钟偏差容许度 (默认是30秒)
legion-lord¶
argument
: 必需参数
parser
: uwsgi_opt_legion_hook
flags
: UWSGI_OPT_MASTER
help
: Lord选举时要调用的动作
legion-unlord¶
argument
: 必需参数
parser
: uwsgi_opt_legion_hook
flags
: UWSGI_OPT_MASTER
help
: 开除Lord时要调用的动作
legion-setup¶
argument
: 必需参数
parser
: uwsgi_opt_legion_hook
flags
: UWSGI_OPT_MASTER
help
: legion设置时要调用的动作
legion-death¶
argument
: 必需参数
parser
: uwsgi_opt_legion_hook
flags
: UWSGI_OPT_MASTER
help
: legion死掉时要调用的动作 (实例的关闭)
legion-join¶
argument
: 必需参数
parser
: uwsgi_opt_legion_hook
flags
: UWSGI_OPT_MASTER
help
: legion加入时要调用的动作 (第一次到达法定数量时)
legion-node-joined¶
argument
: 必需参数
parser
: uwsgi_opt_legion_hook
flags
: UWSGI_OPT_MASTER
help
: 新节点加入legion时要调用的动作
legion-node-left¶
argument
: 必需参数
parser
: uwsgi_opt_legion_hook
flags
: UWSGI_OPT_MASTER
help
: 节点离开legion时要调用的动作
legion-quorum¶
argument
: 必需参数
parser
: uwsgi_opt_legion_quorum
flags
: UWSGI_OPT_MASTER
help
: 设置一个Legion的法定数量
legion-scroll¶
argument
: 必需参数
parser
: uwsgi_opt_legion_scroll
flags
: UWSGI_OPT_MASTER
help
: 设置一个legion的scroll
legion-scroll-list-max-size¶
argument
: 必需参数
parser
: uwsgi_opt_set_64bit
help
: 设置legion scroll列表缓冲的最大大小
subscriptions-sign-check¶
argument
: 必需参数
parser
: uwsgi_opt_scd
flags
: UWSGI_OPT_MASTER
help
: 为安全的订阅系统设置digest算法和证书目录
subscriptions-sign-check-tolerance¶
argument
: 必需参数
parser
: uwsgi_opt_set_int
flags
: UWSGI_OPT_MASTER
help
: 为安全的订阅系统设置时钟偏差的最大容忍度(以秒为单位)
subscriptions-sign-skip-uid¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MASTER
help
: 当使用unix socket凭证的时候,对指定的uid跳过签名检查
subscriptions-credentials-check¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MASTER
help
: 添加一个用以搜索订阅密钥凭证的目录
subscriptions-use-credentials¶
argument
: 无参数
parser
: uwsgi_opt_true
help
: 启用订阅UNIX socket中的SCM_CREDENTIALS管理
subscribe-to¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MASTER
help
: 订阅到指定的订阅服务器
subscribe¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MASTER
help
: 订阅到指定的订阅服务器
subscribe2¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MASTER
help
: 使用高级的键值语法订阅到指定的订阅服务器
subscribe-with-modifier1¶
argument
: 必需参数
parser
: uwsgi_opt_set_str
flags
: UWSGI_OPT_MASTER
help
: 订阅时,强制使用指定的modifier1
ssl-sessions-use-cache¶
argument
: 可选参数
parser
: uwsgi_opt_set_str
flags
: UWSGI_OPT_MASTER
help
: 将uWSGI缓存用于ssl会话存储
ssl-session-use-cache¶
argument
: 可选参数
parser
: uwsgi_opt_set_str
flags
: UWSGI_OPT_MASTER
help
: 将uWSGI缓存用于ssl会话存储
sni-dir¶
argument
: 必需参数
parser
: uwsgi_opt_set_str
help
: 检查指定目录中的cert/key/client_ca文件,并按需创建一个sni/ssl上下文
check-interval¶
argument
: 必需参数
parser
: uwsgi_opt_set_int
flags
: UWSGI_OPT_MASTER
help
: 设置master检查的间隔 (以秒为单位)
forkbomb-delay¶
argument
: 必需参数
parser
: uwsgi_opt_set_int
flags
: UWSGI_OPT_MASTER
help
: 当检查到一个forkbomb时,休眠指定秒数
privileged-binary-patch¶
argument
: 必需参数
parser
: uwsgi_opt_set_str
help
: 用一个新的命令给uwsgi二进制文件打补丁 (在移除特权之前)
unprivileged-binary-patch¶
argument
: 必需参数
parser
: uwsgi_opt_set_str
help
: 用一个新的命令给uwsgi二进制文件打补丁 (在移除特权之后)
privileged-binary-patch-arg¶
argument
: 必需参数
parser
: uwsgi_opt_set_str
help
: 用一个新的命令和参数给uwsgi二进制文件打补丁 (在移除特权之前)
unprivileged-binary-patch-arg¶
argument
: 必需参数
parser
: uwsgi_opt_set_str
help
: 用一个新的命令和参数给uwsgi二进制文件打补丁 (在移除特权之后)
disable-async-warn-on-queue-full¶
argument
: 无参数
parser
: uwsgi_opt_false
help
: 禁止打印’async queue is full’警告信息。
log-syslog¶
argument
: 可选参数
parser
: uwsgi_opt_set_logger
flags
: UWSGI_OPT_MASTER | UWSGI_OPT_LOG_MASTER
help
: 记录日志到syslog
log-socket¶
argument
: 必需参数
parser
: uwsgi_opt_set_logger
flags
: UWSGI_OPT_MASTER | UWSGI_OPT_LOG_MASTER
help
: 发送日志到指定的socket
req-logger¶
argument
: 必需参数
parser
: uwsgi_opt_set_req_logger
flags
: UWSGI_OPT_REQ_LOG_MASTER
help
: 设置/附加一个请求记录器
logger-req¶
argument
: 必需参数
parser
: uwsgi_opt_set_req_logger
flags
: UWSGI_OPT_REQ_LOG_MASTER
help
: 设置/附加一个请求记录器
logger¶
argument
: 必需参数
parser
: uwsgi_opt_set_logger
flags
: UWSGI_OPT_MASTER | UWSGI_OPT_LOG_MASTER
help
: 设置/附加一个记录器
threaded-logger¶
argument
: 无参数
parser
: uwsgi_opt_true
flags
: UWSGI_OPT_MASTER | UWSGI_OPT_LOG_MASTER
help
: 卸载日志写入到一个线程中
log-encoder¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MASTER | UWSGI_OPT_LOG_MASTER
help
: 添加一个项到日志编码器链中
log-req-encoder¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MASTER | UWSGI_OPT_LOG_MASTER
help
: 添加一个项到日志请求编码器链中
log-drain¶
argument
: 必需参数
parser
: uwsgi_opt_add_regexp_list
flags
: UWSGI_OPT_MASTER | UWSGI_OPT_LOG_MASTER
help
: 排除(不要显示)匹配到指定正则表达式的日志行
log-filter¶
argument
: 必需参数
parser
: uwsgi_opt_add_regexp_list
flags
: UWSGI_OPT_MASTER | UWSGI_OPT_LOG_MASTER
help
: 只显示匹配到指定正则表达式的日志行
log-route¶
argument
: 必需参数
parser
: uwsgi_opt_add_regexp_custom_list
flags
: UWSGI_OPT_MASTER | UWSGI_OPT_LOG_MASTER
help
: 如果应用到日志行的正则表达式匹配上的话,则记录日志到指定名字的记录器
log-req-route¶
argument
: 必需参数
parser
: uwsgi_opt_add_regexp_custom_list
flags
: UWSGI_OPT_REQ_LOG_MASTER
help
: 如果应用到日志行的正则表达式匹配上的话,则记录请求到指定名字的记录器
alarm¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MASTER
help
: 创建一个新的告警,语法:<alarm> <plugin:args>
alarm-fd¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MASTER
help
: 当一个fd准备好读取的时候,引发指定的告警 (默认情况下,它读取1个字节,为eventfd设置8)
alarm-segfault¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MASTER
help
: 当执行了段错误处理器时,引发指定的告警
segfault-alarm¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MASTER
help
: 当执行了段错误处理器时,引发指定的告警
alarm-backlog¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MASTER
help
: 当socket backlog队列满时,引发指定的告警
backlog-alarm¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MASTER
help
: 当socket backlog队列满时,引发指定的告警
lq-alarm¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MASTER
help
: 当socket backlog队列满时,引发指定的告警
alarm-lq¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MASTER
help
: 当socket backlog队列满时,引发指定的告警
alarm-listen-queue¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MASTER
help
: 当socket backlog队列满时,引发指定的告警
listen-queue-alarm¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MASTER
help
: 当socket backlog队列满时,引发指定的告警
log-alarm¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MASTER | UWSGI_OPT_LOG_MASTER
help
: 当日志行匹配到指定的正则表达式时,引发指定的告警,语法: <alarm>[,alarm...] <regexp>
alarm-log¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MASTER | UWSGI_OPT_LOG_MASTER
help
: 当日志行匹配到指定的正则表达式时,引发指定的告警,语法: <alarm>[,alarm...] <regexp>
not-log-alarm¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list_custom
flags
: UWSGI_OPT_MASTER | UWSGI_OPT_LOG_MASTER
help
: 当日志行匹配到指定的正则表达式时,跳过指定的告警,语法: <alarm>[,alarm...] <regexp>
not-alarm-log¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list_custom
flags
: UWSGI_OPT_MASTER | UWSGI_OPT_LOG_MASTER
help
: 当日志行匹配到指定的正则表达式时,跳过指定的告警,语法:<alarm>[,alarm...] <regexp>
log-master¶
argument
: 无参数
parser
: uwsgi_opt_true
flags
: UWSGI_OPT_MASTER|UWSGI_OPT_LOG_MASTER
help
: 委托日志记录给master进程
log-maxsize¶
argument
: 必需参数
parser
: uwsgi_opt_set_64bit
flags
: UWSGI_OPT_MASTER|UWSGI_OPT_LOG_MASTER
help
: 设置最大日志文件大小
log-x-forwarded-for¶
argument
: 无参数
parser
: uwsgi_opt_true
help
: 使用来自X-Forwarded-For头部而不是REMOTE_ADDR头部的ip
cheap¶
argument
: 无参数
parser
: uwsgi_opt_true
flags
: UWSGI_OPT_MASTER
help
: 设置cheap模式 (只在第一个请求后生成worker)
cheaper¶
argument
: 必需参数
parser
: uwsgi_opt_set_int
flags
: UWSGI_OPT_MASTER | UWSGI_OPT_CHEAPER
help
: 设置cheaper模式 (适应性进程生成)
cheaper-initial¶
argument
: 必需参数
parser
: uwsgi_opt_set_int
flags
: UWSGI_OPT_MASTER | UWSGI_OPT_CHEAPER
help
: 设置cheaper模式下生成的初始进程数
cheaper-algo¶
argument
: 必需参数
parser
: uwsgi_opt_set_str
flags
: UWSGI_OPT_MASTER
help
: 选择一个用于适应性进程生成的算法
cheaper-step¶
argument
: 必需参数
parser
: uwsgi_opt_set_int
flags
: UWSGI_OPT_MASTER | UWSGI_OPT_CHEAPER
help
: 在每次超载时生成的额外进程数
cheaper-overload¶
argument
: 必需参数
parser
: uwsgi_opt_set_64bit
flags
: UWSGI_OPT_MASTER | UWSGI_OPT_CHEAPER
help
: 在指定过载后增加worker
cheaper-idle¶
argument
: 必需参数
parser
: uwsgi_opt_set_int
flags
: UWSGI_OPT_MASTER | UWSGI_OPT_CHEAPER
help
: 在指定idle后减少worker (算法:spare2) (默认:10)
cheaper-rss-limit-soft¶
argument
: 必需参数
parser
: uwsgi_opt_set_64bit
flags
: UWSGI_OPT_MASTER | UWSGI_OPT_CHEAPER
help
: 如果所有worker总的常驻内存使用高于这个限制,则不要生成新的worker
cheaper-rss-limit-hard¶
argument
: 必需参数
parser
: uwsgi_opt_set_64bit
flags
: UWSGI_OPT_MASTER | UWSGI_OPT_CHEAPER
help
: 如果总的worker常驻内存使用更高,则试着停止worker
idle¶
argument
: 必需参数
parser
: uwsgi_opt_set_int
flags
: UWSGI_OPT_MASTER
help
: 设置idle模式 (在静止后,将uWSGI置于cheap模式)
worker-mount¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
help
: 在指定worker中,或者worker生成后,在挂载点下加载应用
threads¶
argument
: 必需参数
parser
: uwsgi_opt_set_int
flags
: UWSGI_OPT_THREADS
help
: 在prethreaded模式下运行每个worker,使用指定数目的线程
vhost-host¶
argument
: 无参数
parser
: uwsgi_opt_true
flags
: UWSGI_OPT_VHOST
help
: 启用虚拟主机模式 (基于HTTP_HOST变量)
response-route-user-agent¶
argument
: 必需参数
parser
: uwsgi_opt_add_route
help
: 基于HTTP_USER_AGENT添加一个响应路由
websockets-pong-tolerance¶
argument
: 必需参数
parser
: uwsgi_opt_set_int
help
: 设置websockets ping/pong子系统的容忍度 (以秒为单位)
websocket-pong-tolerance¶
argument
: 必需参数
parser
: uwsgi_opt_set_int
help
: 设置websockets ping/pong子系统的容忍度 (以秒为单位)
websockets-max-size¶
argument
: 必需参数
parser
: uwsgi_opt_set_64bit
help
: 设置websocket消息的最大允许大小 (以KB为单位,默认是1024)
websocket-max-size¶
argument
: 必需参数
parser
: uwsgi_opt_set_64bit
help
: 设置websocket消息的最大允许大小 (以KB为单位,默认是1024)
collect-header¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
help
: 存储指定的响应头部到一个请求变量中 (语法:header var)
response-header-collect¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
help
: 存储指定的响应头部到一个请求变量中 (语法:header var)
pull-header¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
help
: 存储指定的响应头部到一个请求变量中,并将其从响应中移除 (语法:header var)
check-static¶
argument
: 必需参数
parser
: uwsgi_opt_check_static
flags
: UWSGI_OPT_MIME
help
: 检查指定的目录中的静态文件
check-static-docroot¶
argument
: 无参数
parser
: uwsgi_opt_true
flags
: UWSGI_OPT_MIME
help
: 检查请求的DOCUMENT_ROOT中的静态文件
static-check¶
argument
: 必需参数
parser
: uwsgi_opt_check_static
flags
: UWSGI_OPT_MIME
help
: 检查指定的目录中的静态文件
static-map¶
argument
: 必需参数
parser
: uwsgi_opt_static_map
flags
: UWSGI_OPT_MIME
help
: 映射挂载点到静态目录(或者文件)
static-map2¶
argument
: 必需参数
parser
: uwsgi_opt_static_map
flags
: UWSGI_OPT_MIME
help
: 和static-map类似,但是完全附加请求资源到docroot
static-skip-ext¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MIME
help
: 静态文件检查时跳过指定的扩展名
static-index¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MIME
help
: 如果请求一个目录,则搜索指定的文件
static-safe¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MIME
help
: 如果文件位于指定路径下,则跳过安全性检查
static-cache-paths¶
argument
: 必需参数
parser
: uwsgi_opt_set_int
flags
: UWSGI_OPT_MIME|UWSGI_OPT_MASTER
help
: 将已解析的路径放到uWSGI缓存中,存储指定秒数
static-cache-paths-name¶
argument
: 必需参数
parser
: uwsgi_opt_set_str
flags
: UWSGI_OPT_MIME|UWSGI_OPT_MASTER
help
: 将指定缓存用于静态路径
mimefile¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MIME
help
: 设置mime types文件路径 (默认/etc/apache2/mime.types)
mime-file¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MIME
help
: 设置mime types文件路径 (默认/etc/apache2/mime.types)
mimefile¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MIME
help
: 设置mime types文件路径 (默认/etc/mime.types)
mime-file¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MIME
help
: 设置mime types文件路径 (默认/etc/mime.types)
static-expires-type¶
argument
: 必需参数
parser
: uwsgi_opt_add_dyn_dict
flags
: UWSGI_OPT_MIME
help
: 基于内容类型设置Expires头部
static-expires-type-mtime¶
argument
: 必需参数
parser
: uwsgi_opt_add_dyn_dict
flags
: UWSGI_OPT_MIME
help
: 基于内容类型和文件mtime设置Expires头部
static-expires¶
argument
: 必需参数
parser
: uwsgi_opt_add_regexp_dyn_dict
flags
: UWSGI_OPT_MIME
help
: 基于文件名正则表达式设置Expires头部
static-expires-mtime¶
argument
: 必需参数
parser
: uwsgi_opt_add_regexp_dyn_dict
flags
: UWSGI_OPT_MIME
help
: 基于文件名正则表达式和文件mtime设置Expires头部
static-expires-uri¶
argument
: 必需参数
parser
: uwsgi_opt_add_regexp_dyn_dict
flags
: UWSGI_OPT_MIME
help
: 基于REQUEST_URI正则表达式设置Expires头部
static-expires-uri-mtime¶
argument
: 必需参数
parser
: uwsgi_opt_add_regexp_dyn_dict
flags
: UWSGI_OPT_MIME
help
: 基于REQUEST_URI正则表达式和文件mtime设置Expires头部
static-expires-path-info¶
argument
: 必需参数
parser
: uwsgi_opt_add_regexp_dyn_dict
flags
: UWSGI_OPT_MIME
help
: 基于PATH_INFO正则表达式设置Expires头部
static-expires-path-info-mtime¶
argument
: 必需参数
parser
: uwsgi_opt_add_regexp_dyn_dict
flags
: UWSGI_OPT_MIME
help
: 基于PATH_INFO正则表达式和文件mtime设置Expires头部
static-gzip¶
argument
: 必需参数
parser
: uwsgi_opt_add_regexp_list
flags
: UWSGI_OPT_MIME
help
: 如果提供的正则表达式匹配静态文件转换,那么它将搜索一个gzip版本
static-gzip-all¶
argument
: 无参数
parser
: uwsgi_opt_true
flags
: UWSGI_OPT_MIME
help
: 检查所有请求的静态文件的gzip版本
static-gzip-dir¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MIME
help
: 检查指定目录/前缀中所有请求的静态文件的gzip版本
static-gzip-prefix¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MIME
help
: 检查指定目录/前缀中所有请求的静态文件的gzip版本
static-gzip-ext¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MIME
help
: 检查带指定扩展名/后缀的所有请求的静态文件的gzip版本
static-gzip-suffix¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MIME
help
: 检查带指定扩展名/后缀的所有请求的静态文件的gzip版本
file-serve-mode¶
argument
: 必需参数
parser
: uwsgi_opt_fileserve_mode
flags
: UWSGI_OPT_MIME
help
: 设置静态文件服务模式
fileserve-mode¶
argument
: 必需参数
parser
: uwsgi_opt_fileserve_mode
flags
: UWSGI_OPT_MIME
help
: 设置静态文件服务模式
close-on-exec¶
argument
: 无参数
parser
: uwsgi_opt_true
help
: 在连接socket上设置close-on-exec (对于在请求中生成进程可能是必须的)
close-on-exec2¶
argument
: 无参数
parser
: uwsgi_opt_true
help
: 在服务器socket上设置close-on-exec (对于在请求中生成进程可能是必须的)
early-envdir¶
argument
: 必需参数
parser
: uwsgi_opt_envdir
flags
: UWSGI_OPT_IMMEDIATE
help
: 尽快加载一个守护工具兼容的envdir
file-write¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
help
: 移除特权之前将指定内容写入到指定文件中 (语法:file=value)
tcp-fast-open¶
argument
: 必需参数
parser
: uwsgi_opt_set_int
help
: 在TCP socket上启用TCP_FASTOPEN标志,使用指定的qlen值
tcp-fastopen¶
argument
: 必需参数
parser
: uwsgi_opt_set_int
help
: 在TCP socket上启用TCP_FASTOPEN标志,使用指定的qlen值
tcp-fast-open-client¶
argument
: 无参数
parser
: uwsgi_opt_true
help
: 如果支持的话,使用sendto(..., MSG_FASTOPEN, ...)来代替connect()
tcp-fastopen-client¶
argument
: 无参数
parser
: uwsgi_opt_true
help
: 如果支持的话,使用sendto(..., MSG_FASTOPEN, ...)来代替connect()
zerg-server¶
argument
: 必需参数
parser
: uwsgi_opt_set_str
flags
: UWSGI_OPT_MASTER
help
: 在指定的UNIX socket上启用zerg服务器
cron2¶
argument
: 必需参数
parser
: uwsgi_opt_add_cron2
flags
: UWSGI_OPT_MASTER
help
: 新增一个cron任务 (key=val语法)
unique-cron¶
argument
: 必需参数
parser
: uwsgi_opt_add_unique_cron
flags
: UWSGI_OPT_MASTER
help
: 添加一个唯一的cron任务
legion-cron¶
argument
: 必需参数
parser
: uwsgi_opt_add_legion_cron
flags
: UWSGI_OPT_MASTER
help
: 新增一个cron任务,该任务只有在实例是一个指定legion的lord时才能运行
cron-legion¶
argument
: 必需参数
parser
: uwsgi_opt_add_legion_cron
flags
: UWSGI_OPT_MASTER
help
: 新增一个cron任务,该任务只有在实例是一个指定legion的lord时才能运行
unique-legion-cron¶
argument
: 必需参数
parser
: uwsgi_opt_add_unique_legion_cron
flags
: UWSGI_OPT_MASTER
help
: 新增一个唯一的cron任务,该任务只有在实例是一个指定legion的lord时才能运行
unique-cron-legion¶
argument
: 必需参数
parser
: uwsgi_opt_add_unique_legion_cron
flags
: UWSGI_OPT_MASTER
help
: 新增一个唯一的cron任务,该任务只有在实例是一个指定legion的lord时才能运行
attach-daemon¶
argument
: 必需参数
parser
: uwsgi_opt_add_daemon
flags
: UWSGI_OPT_MASTER
help
: 附加一个命令/守护进程到master进程(命令必须不跑到后台)
attach-control-daemon¶
argument
: 必需参数
parser
: uwsgi_opt_add_daemon
flags
: UWSGI_OPT_MASTER
help
: 附加一个命令/守护进程到master进程(命令必须不跑到后台),当守护进程死掉时,master也会死掉
smart-attach-daemon¶
argument
: 必需参数
parser
: uwsgi_opt_add_daemon
flags
: UWSGI_OPT_MASTER
help
: 附加一个命令/守护进程到由pid文件管理的master进程(命令必须被守护)
smart-attach-daemon2¶
argument
: 必需参数
parser
: uwsgi_opt_add_daemon
flags
: UWSGI_OPT_MASTER
help
: 附加一个命令/守护进程到由pid文件管理的master进程(命令必须不被守护)
legion-attach-daemon¶
argument
: 必需参数
parser
: uwsgi_opt_add_daemon
flags
: UWSGI_OPT_MASTER
help
: 与–attach-daemon相同,但是守护进程只在legion lord节点上运行
legion-smart-attach-daemon¶
argument
: 必需参数
parser
: uwsgi_opt_add_daemon
flags
: UWSGI_OPT_MASTER
help
: 与–smart-attach-daemon相同,但是守护进程只在legion lord节点上运行
legion-smart-attach-daemon2¶
argument
: 必需参数
parser
: uwsgi_opt_add_daemon
flags
: UWSGI_OPT_MASTER
help
: 与–smart-attach-daemon2相同,但是守护进程只在legion lord节点上运行
daemons-honour-stdin¶
argument
: 无参数
parser
: uwsgi_opt_true
flags
: UWSGI_OPT_MASTER
help
: 不要讲外部守护进程的标准输入修改为/dev/null
attach-daemon2¶
argument
: 必需参数
parser
: uwsgi_opt_add_daemon2
flags
: UWSGI_OPT_MASTER
help
: attach-daemon键值变体 (也支持smart模式)
need-plugins¶
argument
: 必需参数
parser
: uwsgi_opt_load_plugin
flags
: UWSGI_OPT_IMMEDIATE
help
: 加载uWSGI插件 (错误时退出)
need-plugin¶
argument
: 必需参数
parser
: uwsgi_opt_load_plugin
flags
: UWSGI_OPT_IMMEDIATE
help
: 加载uWSGI插件 (错误时退出)
plugins-dir¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_IMMEDIATE
help
: 新增一个目录到uWSGI插件搜索路径
plugin-dir¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_IMMEDIATE
help
: 新增一个目录到uWSGI插件搜索路径
binary-append-data¶
argument
: 必需参数
parser
: uwsgi_opt_binary_append_data
flags
: UWSGI_OPT_IMMEDIATE
help
: 返回一个资源的内容到标准输出,用于附加到一个uwsgi二进制文件中 (用于data://)
cflags¶
argument
: 无参数
parser
: uwsgi_opt_cflags
flags
: UWSGI_OPT_IMMEDIATE
help
: 报告uWSGI CFLAGS (对构建外部插件有用)
dot-h¶
argument
: 无参数
parser
: uwsgi_opt_dot_h
flags
: UWSGI_OPT_IMMEDIATE
help
: 转储用于构建核心的uwsgi.h (对构建外部插件有用)
config-py¶
argument
: 无参数
parser
: uwsgi_opt_config_py
flags
: UWSGI_OPT_IMMEDIATE
help
: 转储用于构建核心的uwsgiconfig.py (对构建外部插件有用)
build-plugin¶
argument
: 必需参数
parser
: uwsgi_opt_build_plugin
flags
: UWSGI_OPT_IMMEDIATE
help
: 为当前二进制文件构建一个uWSGI插件
plugin: airbrake¶
plugin: alarm_curl¶
plugin: alarm_speech¶
plugin: alarm_xmpp¶
plugin: asyncio¶
asyncio¶
argument
: 必需参数
parser
: uwsgi_opt_setup_asyncio
flags
: UWSGI_OPT_THREADS
help
: 一个快捷方式,启用带有指定异步核心数和优化参数的asyncio循环引擎
plugin: cache¶
plugin: carbon¶
carbon¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MASTER
help
: 推送统计信息到指定的carbon服务器
carbon-idle-avg¶
argument
: 必需参数
parser
: uwsgi_opt_set_str
help
: idle期间(无请求)的平均值,可以是”last”, “zero”, “none” (默认是last)
plugin: cgi¶
plugin: cheaper_backlog2¶
plugin: cheaper_busyness¶
plugin: clock_monotonic¶
plugin: clock_realtime¶
plugin: corerouter¶
plugin: coroae¶
coroae¶
argument
: 必需参数
parser
: uwsgi_opt_setup_coroae
help
: 一个快捷方式,启用 Coro::AnyEvent 循环引擎,带指定数量的异步核心和优化参数
plugin: cplusplus¶
plugin: curl_cron¶
curl-cron¶
argument
: 必需参数
parser
: uwsgi_opt_add_cron_curl
flags
: UWSGI_OPT_MASTER
help
: 新增一个cron任务,通过CURL调用指定的url
cron-curl¶
argument
: 必需参数
parser
: uwsgi_opt_add_cron_curl
flags
: UWSGI_OPT_MASTER
help
: 新增一个cron任务,通过CURL调用指定的url
legion-curl-cron¶
argument
: 必需参数
parser
: uwsgi_opt_add_legion_cron_curl
flags
: UWSGI_OPT_MASTER
help
: 新增一个cron任务,通过CURL调用指定的url,只有在实例是指定legion的lord时才可运行
legion-cron-curl¶
argument
: 必需参数
parser
: uwsgi_opt_add_legion_cron_curl
flags
: UWSGI_OPT_MASTER
help
: 新增一个cron任务,通过CURL调用指定的url,只有在实例是指定legion的lord时才可运行
curl-cron-legion¶
argument
: 必需参数
parser
: uwsgi_opt_add_legion_cron_curl
flags
: UWSGI_OPT_MASTER
help
: 新增一个cron任务,通过CURL调用指定的url,只有在实例是指定legion的lord时才可运行
cron-curl-legion¶
argument
: 必需参数
parser
: uwsgi_opt_add_legion_cron_curl
flags
: UWSGI_OPT_MASTER
help
: 新增一个cron任务,通过CURL调用指定的url,只有在实例是指定legion的lord时才可运行
plugin: dumbloop¶
plugin: dummy¶
plugin: echo¶
plugin: emperor_amqp¶
plugin: emperor_mongodb¶
plugin: emperor_pg¶
plugin: emperor_zeromq¶
plugin: example¶
plugin: exception_log¶
plugin: fastrouter¶
fastrouter¶
argument
: 必需参数
parser
: uwsgi_opt_corerouter
help
: 在指定的端口上运行fastrouter
reference
: uWSGI FastRouter
fastrouter-use-cache¶
argument
: 可选参数
parser
: uwsgi_opt_set_str
help
: 使用uWSGI缓存作为fastrouter的hostname->server映射器
fastrouter-use-pattern¶
argument
: 必需参数
parser
: uwsgi_opt_corerouter_use_pattern
help
: 将一个模式用于fastrouter hostname->server映射
fastrouter-use-base¶
argument
: 必需参数
parser
: uwsgi_opt_corerouter_use_base
help
: 将一个基本目录用于fastrouter hostname->server映射
fastrouter-use-code-string¶
argument
: 必需参数
parser
: uwsgi_opt_corerouter_cs
help
: 将code string当成fastrouter的hostname->server映射器
fastrouter-use-socket¶
argument
: 可选参数
parser
: uwsgi_opt_corerouter_use_socket
help
: 转发请求到指定的uwsgi socket
fastrouter-to¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
help
: 转发请求到指定的uwsgi服务器 (你可以多次指定它以实现负载均衡)
fastrouter-subscription-server¶
argument
: 必需参数
parser
: uwsgi_opt_corerouter_ss
help
: 在指定地址上运行fastrouter订阅服务器
fastrouter-post-buffering¶
argument
: 必需参数
parser
: uwsgi_opt_set_64bit
help
: 启用fastrouter post buffering
fastrouter-post-buffering-dir¶
argument
: 必需参数
parser
: uwsgi_opt_set_str
help
: 将fastrouter缓存的文件到指定的目录中 (空操作,使用TMPDIR env)
plugin: forkptyrouter¶
forkptyrouter-zerg¶
argument
: 必需参数
parser
: uwsgi_opt_corerouter_zerg
help
: 附加forkptyrouter到一个zerg服务器
plugin: gccgo¶
go-load¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
help
: 在进程地址空间中加载一个go共享库,最终为main.main和__go_init_main打补丁
gccgo-load¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
help
: 在进程地址空间中加载一个go共享库,最终为main.main和__go_init_main打补丁
goroutines¶
argument
: 必需参数
parser
: uwsgi_opt_setup_goroutines
flags
: UWSGI_OPT_THREADS
help
: 一个快捷方式,为基于goroutine的应用设置优化选项,接收要生成的最大goroutine数为参数
plugin: geoip¶
plugin: gevent¶
gevent¶
argument
: 必需参数
parser
: uwsgi_opt_setup_gevent
flags
: UWSGI_OPT_THREADS
help
: 一个快捷方式,启用gevent循环引擎,使用指定数目的异步核心数和优化参数
gevent-early-monkey-patch¶
argument
: 无参数
parser
: uwsgi_opt_true
help
: 在应用加载之前自动调用gevent.monkey.patch_all()
plugin: glusterfs¶
glusterfs-mount¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MIME
help
: 在一个uri上虚拟挂载指定的glusterfs卷
plugin: graylog2¶
plugin: gridfs¶
gridfs-mount¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MIME
help
: 在指定挂载点上挂载一个gridfs数据库
gridfs-debug¶
argument
: 无参数
parser
: uwsgi_opt_true
flags
: UWSGI_OPT_MIME
help
: 对每个请求报告gridfs挂载点和项名称 (调试)
plugin: http¶
http-to-https¶
argument
: 必需参数
parser
: uwsgi_opt_http_to_https
help
: 在指定地址上添加一个http路由器/服务器,并重定向所有请求到https
http-use-pattern¶
argument
: 必需参数
parser
: uwsgi_opt_corerouter_use_pattern
help
: 使用指定模式来映射请求到unix socket
http-use-base¶
argument
: 必需参数
parser
: uwsgi_opt_corerouter_use_base
help
: 将指定的base用于映射请求到unix socket
http-manage-expect¶
argument
: 可选参数
parser
: uwsgi_opt_set_64bit
help
: 管理Expect HTTP请求头部 (可选地检查Content-Length)
http-auto-chunked¶
argument
: 无参数
parser
: uwsgi_opt_true
help
: 在HTTP 1.1 keepalive期间,自动将输出转换成块编码 (如果需要的话)
http-auto-gzip¶
argument
: 无参数
parser
: uwsgi_opt_true
help
: 如果设置uWSGI-Encoding头部为gzip,则自动gzip压缩内容,但是不指定内容大小 (Content-Length/Transfer-Encoding)和Content-Encoding
http-use-code-string¶
argument
: 必需参数
parser
: uwsgi_opt_corerouter_cs
help
: 将code string作为hostname->server映射器,用于http路由器
http-stud-prefix¶
argument
: 必需参数
parser
: uwsgi_opt_add_addr_list
help
: 期待来自指定地址的连接的stud前缀 (1byte family + 4/16 bytes address)
http-server-name-as-http-host¶
argument
: 必需参数
parser
: uwsgi_opt_true
help
: 强制将SERVER_NAME作为HTTP_HOST使用
plugin: jvm¶
plugin: ldap¶
ldap-schema¶
argument
: 无参数
parser
: uwsgi_opt_ldap_dump
flags
: UWSGI_OPT_IMMEDIATE
help
: dump uWSGI ldap schema
ldap-schema-ldif¶
argument
: 无参数
parser
: uwsgi_opt_ldap_dump_ldif
flags
: UWSGI_OPT_IMMEDIATE
help
: 以ldif格式dump uWSGI ldap schema
plugin: legion_cache_fetch¶
plugin: libffi¶
plugin: libtcc¶
plugin: logcrypto¶
plugin: logfile¶
plugin: logpipe¶
plugin: logsocket¶
plugin: logzmq¶
log-zeromq¶
argument
: 必需参数
parser
: uwsgi_opt_set_logger
flags
: UWSGI_OPT_MASTER | UWSGI_OPT_LOG_MASTER
help
: 发送日志到一个zeromq服务器
plugin: lua¶
plugin: matheval¶
plugin: mongodb¶
plugin: mongodblog¶
plugin: mongrel2¶
plugin: mono¶
mono-gc-freq¶
argument
: 必需参数
parser
: uwsgi_opt_set_64bit
help
: 每<n>个请求运行一次Mono GC requests (默认:每次请求后运行)
plugin: msgpack¶
plugin: nagios¶
plugin: objc_gc¶
plugin: pam¶
plugin: php¶
php-app-qs¶
argument
: 必需参数
parser
: uwsgi_opt_set_str
help
: 当处于app模式的时候,强制对QUERY_STRING使用指定值+REQUEST_URI
early-php¶
argument
: 无参数
parser
: uwsgi_opt_early_php
flags
: UWSGI_OPT_IMMEDIATE
help
: 初始化由所有加载器共享的早期perl解释器
early-php-sapi-name¶
argument
: 必需参数
parser
: uwsgi_opt_set_str
flags
: UWSGI_OPT_IMMEDIATE
help
: hack sapi的名字 (启用zend opcode缓存需要)
plugin: ping¶
ping¶
argument
: 必需参数
parser
: uwsgi_opt_set_str
flags
: UWSGI_OPT_NO_INITIAL | UWSGI_OPT_NO_SERVER
help
: ping指定的uwsgi主机
plugin: psgi¶
perl-auto-reload¶
argument
: 必需参数
parser
: uwsgi_opt_set_int
flags
: UWSGI_OPT_MASTER
help
: 根据指定频率启用perl自动重载器
perl-auto-reload-ignore¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MASTER
help
: 当自动重载启用的时候,忽略指定文件
early-perl¶
argument
: 必需参数
parser
: uwsgi_opt_early_perl
flags
: UWSGI_OPT_IMMEDIATE
help
: 初始化一个由所有加载器共享的早期perl解释器
early-psgi¶
argument
: 必需参数
parser
: uwsgi_opt_early_psgi
flags
: UWSGI_OPT_IMMEDIATE
help
: uWSGI初始化后立即加载一个psgi应用
early-perl-exec¶
argument
: 必需参数
parser
: uwsgi_opt_early_exec
flags
: UWSGI_OPT_IMMEDIATE
help
: uWSGI初始化后立即加载一个perl脚本
plugin: pty¶
pty-connect¶
argument
: 必需参数
parser
: uwsgi_opt_set_str
flags
: UWSGI_OPT_NO_INITIAL
help
: 连接当前终端到pty服务器
pty-uconnect¶
argument
: 必需参数
parser
: uwsgi_opt_set_str
flags
: UWSGI_OPT_NO_INITIAL
help
: 连接当前终端到pty服务器 (使用uwsgi协议)
plugin: pypy¶
pypy-ini-paste¶
argument
: 必需参数
parser
: uwsgi_opt_pypy_ini_paste
flags
: UWSGI_OPT_IMMEDIATE
help
: 加载一个包含uwsgi节的paste.deploy配置文件
plugin: python¶
post-pymodule-alias¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
help
: uwsgi模块初始化hour,添加一个python模块别名
ini-paste¶
argument
: 必需参数
parser
: uwsgi_opt_ini_paste
flags
: UWSGI_OPT_IMMEDIATE
help
: 加载一个包含uwsgi节的paste.deploy配置文件
ini-paste-logged¶
argument
: 必需参数
parser
: uwsgi_opt_ini_paste
flags
: UWSGI_OPT_IMMEDIATE
help
: 加载一个包含uwsgi节的paste.deploy配置文件 (也加载记录器)
pyshell-oneshot¶
argument
: 可选参数
parser
: uwsgi_opt_pyshell
help
: 在uWSGI环境中运行一个python交互式python shell (一次性变体)
py-tracebacker¶
argument
: 必需参数
parser
: uwsgi_opt_set_str
flags
: UWSGI_OPT_THREADS|UWSGI_OPT_MASTER
help
: 启用uWSGI python tracebacker
py-auto-reload¶
argument
: 必需参数
parser
: uwsgi_opt_set_int
flags
: UWSGI_OPT_THREADS|UWSGI_OPT_MASTER
help
: 监控python模块mtime来触发重载 (只在开发时使用)
py-autoreload¶
argument
: 必需参数
parser
: uwsgi_opt_set_int
flags
: UWSGI_OPT_THREADS|UWSGI_OPT_MASTER
help
: 监控python模块mtime来触发重载 (只在开发时使用)
python-auto-reload¶
argument
: 必需参数
parser
: uwsgi_opt_set_int
flags
: UWSGI_OPT_THREADS|UWSGI_OPT_MASTER
help
: 监控python模块mtime来触发重载 (只在开发时使用)
python-autoreload¶
argument
: 必需参数
parser
: uwsgi_opt_set_int
flags
: UWSGI_OPT_THREADS|UWSGI_OPT_MASTER
help
: 监控python模块mtime来触发重载 (只在开发时使用)
py-auto-reload-ignore¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_THREADS|UWSGI_OPT_MASTER
help
: 自动重载扫描期间,忽略指定的模块 (可以多次指定)
wsgi-accept-buffer¶
argument
: 无参数
parser
: uwsgi_opt_true
help
: 除了string/byte外,接受CPython 兼容buffer对象作为WSGI响应
wsgi-accept-buffers¶
argument
: 无参数
parser
: uwsgi_opt_true
help
: 除了string/byte外,接受CPython 兼容buffer对象作为WSGI响应
early-python¶
argument
: 无参数
parser
: uwsgi_early_python
flags
: UWSGI_OPT_IMMEDIATE
help
: 尽快加载python VM (对fork服务器有用)
early-pyimport¶
argument
: 必需参数
parser
: uwsgi_early_python_import
flags
: UWSGI_OPT_IMMEDIATE
help
: 在早期导入一个python模块
early-python-import¶
argument
: 必需参数
parser
: uwsgi_early_python_import
flags
: UWSGI_OPT_IMMEDIATE
help
: 在早期导入一个python模块
early-pythonpath¶
argument
: 必需参数
parser
: uwsgi_opt_pythonpath
flags
: UWSGI_OPT_IMMEDIATE
help
: 添加目录(或者glob)到pythonpath (立即版本)
early-python-path¶
argument
: 必需参数
parser
: uwsgi_opt_pythonpath
flags
: UWSGI_OPT_IMMEDIATE
help
: 添加目录(或者glob)到pythonpath (立即版本)
plugin: pyuwsgi¶
plugin: rack¶
rails¶
argument
: 必需参数
parser
: uwsgi_opt_set_str
flags
: UWSGI_OPT_POST_BUFFERING
help
: 加载一个rails <= 2.x的应用
plugin: rados¶
rados-mount¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MIME
help
: 虚拟挂载uri中的指定rados卷
plugin: rawrouter¶
rawrouter-use-cache¶
argument
: 可选参数
parser
: uwsgi_opt_set_str
help
: 将uWSGI缓存作为hostname->server映射器用于rawrouter
rawrouter-use-pattern¶
argument
: 必需参数
parser
: uwsgi_opt_corerouter_use_pattern
help
: 将一个模式用于rawrouter hostname->server映射
rawrouter-use-base¶
argument
: 必需参数
parser
: uwsgi_opt_corerouter_use_base
help
: 将一个基本目录用于rawrouter hostname->server映射
rawrouter-use-code-string¶
argument
: 必需参数
parser
: uwsgi_opt_corerouter_cs
help
: 将code string作为hostname->server映射器,用于rawrouter
rawrouter-use-socket¶
argument
: 可选参数
parser
: uwsgi_opt_corerouter_use_socket
help
: 转发请求到指定的uwsgi socket
rawrouter-to¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
help
: 转发请求到指定的uwsgi服务器 (你可以为负载均衡多次指定它)
rawrouter-subscription-server¶
argument
: 必需参数
parser
: uwsgi_opt_corerouter_ss
help
: 在指定的地址上运行rawrouter订阅服务器
plugin: rbthreads¶
plugin: redislog¶
plugin: ring¶
plugin: router_access¶
plugin: router_basicauth¶
plugin: router_cache¶
plugin: router_expires¶
plugin: router_hash¶
plugin: router_http¶
plugin: router_memcached¶
plugin: router_metrics¶
plugin: router_radius¶
plugin: router_redirect¶
plugin: router_redis¶
plugin: router_rewrite¶
plugin: router_spnego¶
plugin: router_static¶
plugin: router_uwsgi¶
plugin: router_xmldir¶
plugin: rpc¶
plugin: rrdtool¶
rrdtool¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MASTER|UWSGI_OPT_METRICS
help
: 在指定的目录中存储rrd文件
plugin: rsyslog¶
rsyslog-packet-size¶
argument
: 必需参数
parser
: uwsgi_opt_set_int
help
: 设置syslog消息的最大包大小 (默认1024) 警告!使用包大小 > 1024会违反RFC 3164 (#4.1)
rsyslog-split-messages¶
argument
: 无参数
parser
: uwsgi_opt_true
help
: 如果大消息比允许的包大小大,则将它分割到多个块 (默认是false)
plugin: ruby19¶
plugin: servlet¶
plugin: signal¶
plugin: spooler¶
plugin: sqlite3¶
sqlite3¶
argument
: 必需参数
parser
: uwsgi_opt_load_sqlite3
flags
: UWSGI_OPT_IMMEDIATE
help
: 从文件加载配置sqlite3 db
sqlite¶
argument
: 必需参数
parser
: uwsgi_opt_load_sqlite3
flags
: UWSGI_OPT_IMMEDIATE
help
: 从文件加载配置sqlite3 db
plugin: ssi¶
plugin: sslrouter¶
sslrouter-use-cache¶
argument
: 可选参数
parser
: uwsgi_opt_set_str
help
: 将uWSGI缓存作为hostname->server映射器用于sslrouter
sslrouter-use-pattern¶
argument
: 必需参数
parser
: uwsgi_opt_corerouter_use_pattern
help
: 对sslrouter hostname->server映射使用一个模式
sslrouter-use-base¶
argument
: 必需参数
parser
: uwsgi_opt_corerouter_use_base
help
: 对sslrouter hostname->server映射使用一个基本目录
sslrouter-use-code-string¶
argument
: 必需参数
parser
: uwsgi_opt_corerouter_cs
help
: 将code string作为hostname->server映射器,用于sslrouter
sslrouter-use-socket¶
argument
: 可选参数
parser
: uwsgi_opt_corerouter_use_socket
help
: 转发请求到指定的uwsgi socket
sslrouter-to¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
help
: 转发请求到指定的uwsgi服务器 (你可以为负载均衡多次指定它)
sslrouter-subscription-server¶
argument
: 必需参数
parser
: uwsgi_opt_corerouter_ss
help
: 在指定地址上运行sslrouter订阅服务器
plugin: stats_pusher_file¶
plugin: stats_pusher_mongodb¶
plugin: stats_pusher_socket¶
plugin: stats_pusher_statsd¶
plugin: symcall¶
symcall¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
help
: 将指定的C符号作为symcall请求处理器 (也支持<mountpoint=func>)
symcall-register-rpc¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
help
: 将指定的C符号作为RPC函数加载 (语法:name function)
plugin: syslog¶
plugin: systemd_logger¶
plugin: tornado¶
tornado¶
argument
: 必需参数
parser
: uwsgi_opt_setup_tornado
flags
: UWSGI_OPT_THREADS
help
: 一个快捷方式,用指定核心数和优化参数启用tornado循环引擎
plugin: transformation_chunked¶
plugin: transformation_gzip¶
plugin: transformation_offload¶
plugin: transformation_template¶
plugin: transformation_tofile¶
plugin: transformation_toupper¶
plugin: tuntap¶
tuntap-router¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
help
: 运行tuntap路由器 (语法:<device> <socket> [stats] [gateway])
tuntap-device¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
help
: 添加一个tuntap设备到实例 (语法:<device>[ <socket>])
tuntap-use-credentials¶
argument
: 可选参数
parser
: uwsgi_opt_set_str
help
: 启用对tuntap客户端/服务器的SCM_CREDENTIALS检查
tuntap-router-firewall-in¶
argument
: 必需参数
parser
: uwsgi_tuntap_opt_firewall
help
: 添加一个防火墙规则到tuntap路由器 (语法:<action> <src/mask> <dst/mask>)
tuntap-router-firewall-out¶
argument
: 必需参数
parser
: uwsgi_tuntap_opt_firewall
help
: 添加一个防火墙规则到tuntap路由器 (语法:<action> <src/mask> <dst/mask>)
tuntap-router-route¶
argument
: 必需参数
parser
: uwsgi_tuntap_opt_route
help
: 新增一个路由规则到tuntap路由器 (语法:<src/mask> <dst/mask> <gateway>)
tuntap-device-rule¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
help
: 新增一个tuntap设备规则 (语法:<direction> <src/mask> <dst/mask> <action> [target])
plugin: ugreen¶
plugin: v8¶
plugin: webdav¶
webdav-mount¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MIME
help
: 映射一个文件系统目录作为webdav仓库
webdav-css¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MIME
help
: 为自动webdav目录列表添加一个css url
webdav-javascript¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MIME
help
: 为自动webdav目录列表添加一个javascript url
webdav-js¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MIME
help
: 为自动webdav目录列表添加一个javascript url
webdav-class-directory¶
argument
: 必需参数
parser
: uwsgi_opt_set_str
flags
: UWSGI_OPT_MIME
help
: 为自动webdav目录列表设置css目录类
webdav-div¶
argument
: 必需参数
parser
: uwsgi_opt_set_str
flags
: UWSGI_OPT_MIME
help
: 为自动webdav目录列表设置div id
webdav-lock-cache¶
argument
: 必需参数
parser
: uwsgi_opt_set_str
flags
: UWSGI_OPT_MIME
help
: 为webdav锁设置使用的缓存
webdav-principal-base¶
argument
: 必需参数
parser
: uwsgi_opt_set_str
flags
: UWSGI_OPT_MIME
help
: 使用指定的基础启用WebDAV当前主要扩展
webdav-add-option¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MIME
help
: 添加一个WebDAV标准到OPTIONS响应中
webdav-add-prop¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MIME
help
: 添加一个WebDAV属性到所有资源中
webdav-add-collection-prop¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MIME
help
: 添加一个WebDAV属性到所有集合中
webdav-add-object-prop¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MIME
help
: 添加一个WebDAV属性到所有对象中
webdav-add-prop-href¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MIME
help
: 添加一个WebDAV属性到所有资源中 (href值)
webdav-add-collection-prop-href¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MIME
help
: 添加一个WebDAV属性到所有集合中 (href值)
webdav-add-object-prop-href¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MIME
help
: 添加一个WebDAV属性到所有对象中 (href值)
webdav-add-prop-comp¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MIME
help
: 添加一个WebDAV属性到所有资源中 (xml值)
webdav-add-collection-prop-comp¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MIME
help
: 添加一个WebDAV属性到所有集合中 (xml值)
webdav-add-object-prop-comp¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MIME
help
: 添加一个WebDAV属性到所有对象中 (xml值)
webdav-add-rtype-prop¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MIME
help
: 添加一个WebDAV资源类型属性到所有资源中
webdav-add-rtype-collection-prop¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MIME
help
: 添加一个WebDAV资源类型属性到所有集合中
webdav-add-rtype-object-prop¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MIME
help
: 添加一个WebDAV资源类型属性到所有对象中
webdav-skip-prop¶
argument
: 必需参数
parser
: uwsgi_opt_add_string_list
flags
: UWSGI_OPT_MIME
help
: 如果指定的prop在资源xattr中可用的话,不要添加它
plugin: xattr¶
plugin: xslt¶
xslt-content-type¶
argument
: 必需参数
parser
: uwsgi_opt_set_str
help
: 为xslt结果设置ontent-type (默认是:text/html)
为你的实例定义新选项¶
有时,内置选项并不够。例如,你可能需要给予你的客户自定义选项,让他们在你的平台上配置他们的应用。或者,你需要配置辣么多的实例,因此你想要简化一些选项,例如per-datacenter或者per-server-type。为你的配置文件/命令行声明新的选项是达到这些目标的一个好方法。
要定义一个新选项,使用 --declare-option
:
--declare-option <option_name>=<option1=value1>[;<option2=value2>;<option3=value3>...]
一个有用的例子可以是定义一个”redirect”选项,使用内部路由子系统中的重定向插件:
--declare-option "redirect=route=\$1 redirect:\$2"
这将会声明一个新的选项,名为 redirect
,它接收2个参数。将会使用$-prefixed变量对那些参数进行扩展。就像shell脚本一样, 需要使用反斜杠,从而让你的shell不扩展这些值 。
现在,你就能在你的配置文件中定义重定向了:
uwsgi --declare-option "redirect=route=\$1 redirect:\$2" --ini config.ini
Config.ini:
[uwsgi]
socket = :3031
; define my redirects
redirect = ^/foo http://unbit.it
redirect = \.jpg$ http://uwsgi.it/test
redirect = ^/foo/bar/ /test
或者直接在命令行:
uwsgi --declare-option "redirect=route=\$1 redirect:\$2" --socket :3031 --redirect "^/foo http://unbit.it" --redirect "\.jpg$ http://uwsgi.it/test" --redirect "^/foo/bar/ /test"
更好玩:一堆快捷方式¶
现在,我们将为常用应用定义新选项。
Shortcuts.ini:
[uwsgi]
; let's define a shortcut for trac (new syntax: trac=<path_to_trac_instance>)
declare-option = trac=plugin=python;env=TRAC_ENV=$1;module=trac.web.main:dispach_request
; one for web2py (new syntax: web2py=<path_to_web2_py_dir>)
declare-option = web2py=plugin=python;chdir=$1;module=wsgihandler
; another for flask (new syntax: flask=<path_to_your_app_entry_point>)
declare-option = flask=plugin=python;wsgi-file=$1;callable=app
要把一个Trac实例连接到/var/www/trac/fooenv:
[uwsgi]
; include new shortcuts
ini = shortcuts.ini
; classic options
http = :8080
master = true
threads = 4
; our new option
trac = /var/www/trac/fooenv
一个用于Web2py的配置,以XML格式:
<uwsgi>
<!-- import shortcuts -->
<ini>shortcuts.ini</ini>
<!-- run the https router with HIGH ciphers -->
<https>:443,test.crt,test.key,HIGH</https>
<master/>
<processes>4</processes>
<!-- load web2py from /var/www/we2py -->
<web2py>/var/www/we2py</web2py>
</uwsgi>
Emperor的一个小技巧:自动为你的vassal导入快捷方式¶
如果你使用 Emperor 来管理你的客户/用户,那么你可以配置它来自动在每个vassal中导入你的快捷方式。
uwsgi --emperor /etc/uwsgi/vassals --vassals-include /etc/uwsgi/shortcuts.ini
对于多个快捷方式,使用:
uwsgi --emperor /etc/uwsgi/vassals --vassals-include /etc/uwsgi/shortcuts.ini --vassals-include /etc/uwsgi/shortcuts2.ini --vassals-include /etc/uwsgi/shortcuts3.ini
或者 (使用一点 configuration logic magic):
[uwsgi]
emperor = /etc/uwsgi/vassals
for = shortcuts shortcuts2 shortcuts3
vassals-include = /etc/uwsgi/%(_).ini
endfor =
一个绝招:嵌入快捷方式到你的uWSGI二进制文件中¶
uWSGI的构建系统允许你嵌入文件,无论是普通的文件还是配置,到服务器二进制文件中。尽情使用此特性将让你可以嵌入新的选项快捷方式到服务器二进制文件中,自动允许用户使用它们。要嵌入你的快捷方式文件,则编辑你的构建配置文件 (例如
buildconf/base.ini
) ,然后设置 embed_config
为快捷方式文件的路径。重新构建你的服务器,然后你新的选项就可以用了。
参见
BuildConf
uWSGI是如何解析配置文件的¶
直到uWSGI 1.1,解析顺序都是不“稳定”或者“可靠的”。
从uWSGI 1.1开始 (多亏了它新的选项子系统),我们有了一个一般的规则:从上到下,尽快扩展。
从上到下意味着选项根据它们解析的顺序内部派系,而“尽快扩展”表示注入一个请求的配置文件的选项,中断当前解析的配置文件:
注意, inherit
选项与其他包含选项不同:它是在变量扩展 之后 进行扩展的,因此,任何环境变量,外部文件和占位符并 未 被扩展。魔术变量 (例如 %n
) 会被正常进行扩展。
file1.ini (从命令行请求的那个文件)
[uwsgi]
socket = :3031
ini = file2.ini
socket = :3032
chdir = /var/www
file2.ini
[uwsgi]
master = true
memory-report = true
processes = 4
内部将会被组装成:
[uwsgi]
socket = :3031
ini = file2.ini
master = true
memory-report = true
processes = 4
socket = :3032
chdir = /var/www
一个更复杂的例子:
file1.ini (从命令行请求的那个文件)
[uwsgi]
socket = :3031
ini = file2.ini
socket = :3032
chdir = /var/www
file2.ini
[uwsgi]
master = true
xml = file3.xml
memory-report = true
processes = 4
file3.xml
<uwsgi>
<plugins>router_uwsgi</plugins>
<route>^/foo uwsgi:127.0.0.1:4040,0,0</route>
</uwsgi>
结果将会是:
[uwsgi]
socket = :3031
ini = file2.ini
master = true
xml = file3.xml
plugins = router_uwsgi
route = ^/foo uwsgi:127.0.0.1:4040,0,0
memory-report = true
processes = 4
socket = :3032
chdir = /var/www
扩展变量/占位符¶
在组装了内部配置树之后,将会应用变量和占位符替换。
第一步是用环境变量的值VALUE来替换所有出现的 $(VALUE) 。
[uwsgi]
foobar = $(PATH)
foobar值将会是shell的PATH变量的内容
第二步将会扩展在 @(FILENAME) 中括起来的文本文件。
[uwsgi]
nodename = @(/etc/hostname)
nodename值将会是/etc/hostname的内容
最后一步是占位符替换。一个占位符是另一个选项的引用:
[uwsgi]
socket = :3031
foobar = %(socket)
foobar的内容将会被映射为socket的内容。
魔术变量说明¶
配置文件,支持另一种形式的变量,被称为“魔术”变量。当它们指向配置文件本身的时候,它们将会被尽快解析:
[uwsgi]
my_config_file = %p
my_config_file的内容一旦被解析,它将会被设置为%p的值 (当前文件的绝对路径)。这意味着%p (或者任何你需要的魔术变量)在当前解析的配置文件中将永远是一致的。
uwsgi协议魔术变量¶
你可以通过使用web服务器(或一般使用一个uwsgi兼容的客户端)传递的专用的变量来动态调整或配置uWSGI服务器的各个方面。
- 对于Nginx,使用
uwsgi_param <name> <value>;
指令。 - 对于Apache,使用
SetEnv <name> <value>
指令。
UWSGI_SCHEME
¶
当不能可靠确定时,设置URL方案。例如,这可以用来强制使用HTTPS (使用值 https
)。
UWSGI_SCRIPT
¶
将指定的脚本作为一个映射到 SCRIPT_NAME
的新应用加载。该应用将明显只加载一次,而不是在每个请求都加载。
uwsgi_param UWSGI_SCRIPT werkzeug.testapp:test_app;
uwsgi_param SCRIPT_NAME /testapp;
UWSGI_MODULE
和 UWSGI_CALLABLE
¶
加载一个新的应用 (定义为 module:callable
),将其映射到 SCRIPT_NAME
.
uwsgi_param UWSGI_MODULE werkzeug.testapp;
uwsgi_param UWSGI_CALLABLE test_app;
uwsgi_param SCRIPT_NAME /testapp;
UWSGI_CHDIR
¶
在管理请求之前 chdir()
到指定的目录。
UWSGI_FILE
¶
将指定的文件作为一个新的动态应用加载。
UWSGI_TOUCH_RELOAD
¶
当指定的文件的修改时间自最后一个请求后发生改变时,重载uWSGI栈。
location / {
include uwsgi_params;
uwsgi_param UWSGI_TOUCH_RELOAD /tmp/touchme.foo;
uwsgi_pass /tmp/uwsgi.sock;
}
UWSGI_CACHE_GET
¶
参见
为特定的键查看uWSGI换成。如果找到该键,它将会作为原始的HTTP输出返回,而不是请求的一般处理。
location / {
include uwsgi_params;
uwsgi_param UWSGI_CACHE_GET $request_uri;
uwsgi_pass 127.0.0.1:3031;
}
UWSGI_SETENV
¶
为一个新的动态应用设置指定的环境变量。
注解
要在Python应用中使用这个功能,你需要启用 reload-os-env
uWSGI选项。
在不使用一个WSGI文件/模块的情况下动态加载一个Django应用:
location / {
include uwsgi_params;
uwsgi_param UWSGI_SCRIPT django.core.handlers.wsgi:WSGIHandler();
uwsgi_param UWSGI_CHDIR /mydjangoapp_path;
uwsgi_param UWSGI_SETENV DJANGO_SETTINGS_MODULE=myapp.settings;
}
UWSGI_APPID
¶
注解
自0.9.9起可用。
绕过 SCRIPT_NAME
和 VirtualHosting
,从而让用户在没有限制(或者不头疼)的情况下选择挂载点。
这个概念是非常通用的: UWSGI_APPID
是一个应用的标识符。如果在应用的内部列表中找不到它,那么要加载它。
server {
server_name server001;
location / {
include uwsgi_params;
uwsgi_param UWSGI_APPID myfunnyapp;
uwsgi_param UWSGI_FILE /var/www/app1.py
}
}
server {
server_name server002;
location / {
include uwsgi_params;
uwsgi_param UWSGI_APPID myamazingapp;
uwsgi_param UWSGI_FILE /var/www/app2.py
}
}
uwsgi协议¶
uwsgi协议是uWSGI服务器使用的本地协议。
它是一个二进制协议,可以携带任何类型的数据。一个uwsgi分组的头4个字节描述了这个分组包含的数据类型。
每个uwsgi请求生成一个uwsgi格式的响应。
甚至是web服务器处理程序也遵循这个规则,因为一个HTTP响应是一个有效的uwsgi包 (看下 modifier1
= 72)。
该协议主要通过TCP工作,但是master经常可以为 内嵌SNMP服务器 或者集群管理/消息请求绑定到一个UDP单播/组播。
SCTP支持正在开发中。
uwsgi包头¶
struct uwsgi_packet_header {
uint8_t modifier1;
uint16_t datasize;
uint8_t modifier2;
};
除非另有指定的 datasize
值包含了包体大小 (小端的16位)。
包描述¶
modifier1 |
datasize |
modifier2 |
packet type |
---|---|---|---|
0 | WSGI块变量大小 (HTTP请求体除外) | 0 | 标准WSGI请求,后面跟着HTTP请求体 |
1 | 为UNBIT保留 | ||
2 | 为UNBIT保留 | ||
3 | 为UNBIT保留 | ||
5 | PSGI 块变量 (HTTP请求体除外)的大小 | 0 | 标准 PSGI 请求,后面跟着HTTP请求体 |
6 | LUA WSAPI块变量 (HTTP请求体除外) 大小 | 0 | 标准LUA/WSAPI请求,后面跟着HTTP请求体 |
7 | RACK块变量 (HTTP请求体除外) 大小 | 0 | 标准RACK请求,后面跟着HTTP请求体 |
8 | JWSGI/Ring块变量 (HTTP请求体除外) 大小 | 0 | 用于 JWSGI接口 和 Clojure/Ring JVM请求处理器 的标准JVM请求,后面跟着HTTP请求体 |
9 | CGI块变量 (HTTP请求体除外) 大小 | 0 | 标准 在uWSGI上运行CGI脚本 请求,后面跟着HTTP请求体 |
10 | 块变量大小 | 0- 255 | 管理接口请求:由modifier2指定的setup标志。关于管理标志列表,查看ManagementFlag |
14 | CGI块变量 (HTTP请求体除外) 大小 | 0 | 标准 在uWSGI中运行PHP脚本 请求,后面跟着HTTP请求体 |
15 | Mono ASP.NET块变量 (HTTP请求体除外) 大小 | 0 | 标准 Mono ASP.NET插件 请求,后面跟着HTTP请求体 |
17 | Spooler块变量大小 | 0- 255 | uWSGI Spooler 请求,块变量会被转换成一个字典/哈希/表,然后被传递给spooler可调用对象。当前忽略第二个modifier. |
18 | CGI块变量的大小 | 0-255 | 直接调用到类C符号 |
22 | 代码字符串大小 | 0- 255 | 原始代码评估。由modifier2选定解释器。0是Python, 5是Perl。它不会返回一个有效的uwsgi响应,但会返回一个原始字符串 (可能是一个HTTP响应) |
23 | CGI变量大小 | 0- 255 | 调用 XSLT插件 |
24 | CGI变量大小 | 0- 255 | 调用 uWSGI V8支持 |
25 | CGI变量大小 | 0- 255 | 调用 GridFS插件 |
26 | CGI变量大小 | 0- 255 | 调用 GlusterFS插件 |
27 | 0 | 0- 255 | 调用modifier2字段指定的 FastFuncs |
28 | 0 | 0- 255 | 调用 RADOS插件 |
30 | WSGI块变量 (HTTP请求体除外) 大小 | 0 (如果定义,那么块变量的大小是24位,目前,没有web服务器处理器支持这个特性) | 标准WSGI请求,后面跟着HTTP请求体。会自动修改PATH_INFO,将SCRIPT_NAME从中删除 |
31 | 块变量大小 | 0- 255 | 一般消息传递 (保留) |
32 | 字符数组大小 | 0- 255 | 字符数组传递 (保留) |
33 | marshal对象大小 | 0- 255 | 编组/序列化对象传递 (保留) |
48 | snmp特定 | snmp特定 | 标识一个SNMP请求/响应 (主要通过UDP) |
72 | chr(TT) | chr(P) | 相对于’HTTP’字符串,标志这是一个原始的HTTP响应。 |
73 | 宣告消息大小 (完备性检查) | 宣告类型(0 = 主机名) | 宣告消息 |
74 | 多播消息大小 (完备性检查) | 0 | 字符数组;一个自定义的多播消息,由 uwsgi.multicast_manager 管理 |
95 | 集群成员字典大小 | action |
从一个机器添加/移除/启用/禁用节点。操作可以是 0 = 添加, 1 = 移除, 2 = 启用, 3 = 禁用。添加操作要求一个至少包含3个键的字典: hostname , address 和 workers |
96 | 日志消息大小 | 0 | 远程日志记录 (集群/多播/单播) |
97 | 0 | 0, 1 | 粗鲁重载请求 (0 请求 - 1 确认) |
98 | 0 | 0, 1 | 优雅重载请求 (0 请求 - 1 确认) |
99 | 选项字典的大小 (如果响应) | 0, 1 | 来自一个uwsgi节点的请求配置数据 (即使通过多播) |
100 | 0 | 0, 1 | PING- PONG 如果modifier2为0,那么它是一个PING请求,否则它是一个PONG (一个响应)。对于集群健康检查有用 |
101 | 包大小 | 0 | ECHO服务 |
109 | 干净的有效负荷的大小 | 0 to 255 | legion消息 (UDP, 请求体被加密) |
110 | 负载大小 | 0 to 255 | uwsgi_signal 框架 (有效负荷是可选的), modifier2是信号数字 |
111 | 包大小 | 0, 1, 2, 3 | 缓存操作。0: 读, 1: 写, 2: 删除, 3: 基于字典 |
123 | 包大小 | 用于通知核心路由器特殊条件的特殊modifier | |
173 | 包大小 | 0, 1 | RPC。包时一个uwsgi数组,其中,第一个项是函数名,而接下来是参数 (如果 modifier2 是1,那么RPC将会是’raw’,并且所有的响应将会被返回给应用,包含uwsgi头,如果可用的话。 |
200 | 0 | 0 | 用于持久性连接的关闭标志 |
224 | 包大小 | 0 | 订阅包。见SubscriptionServer |
255 | 0 | 0- 255 | 一般响应。请求相关。例如,一个spooler响应为一个失败的spool设置0,而为一个成功的spool设置1 |
uwsgi变量¶
uwsgi块变量表示一个字典/哈希。hash. 每个键值对都以这种方式进行编码:
struct uwsgi_var {
uint16_t key_size;
uint8_t key[key_size];
uint16_t val_size;
uint8_t val[val_size];
}
管理外部守护进程/服务¶
uWSGI可以轻松地监控外部进程,允许你提高你的多层应用的可靠性和可用性。例如,你可以管理诸如Memcached, Redis, Celery, Ruby delayed_job, 甚至是专门的PostgreSQL实例。
各种各样的服务¶
目前,uWSGI支持三种类型的进程:
--attach-daemon
– 直接附加到非守护式进程--smart-attach-daemon
– 管理pid文件 (同时包括前台和守护)--smart-attach-daemon2
– 带守护管理的管理pid文件
第一类允许你直接附加进程到uWSGI master。当master死掉或者重载时,会销毁这些进程。这对于那些每当重启应用时必须被清除的服务来说,是最好的选项。
管理pid文件进程可以在死亡或者master的重载下存活下来,只要它们的pid文件可用,并且包含的pid匹配到一个运行中的pid。这对那些要求更长的持久性的进程,以及粗鲁的杀死可能意味着诸如数据库数据的遗失的情况来说,这是最好的选择。
最后一个目录是第二个的一个超集。如果你的进程不支持守护,或者写入到一个pid文件中,那么你可以让master来管理。非常少的守护进程/应用需要这个特性,但是对于微小的原型应用,或者只是设计不佳的应用来说,会是有用的。
自uWSGI 2.0起,第四个选项, --attach-daemon2
已被添加来进行高级的配置(见下)。
例子¶
在’dumb’模式下管理一个 memcached 实例。每当停止或者重载uWSGI,就会销毁memcached。
[uwsgi]
master = true
socket = :3031
attach-daemon = memcached -p 11311 -u roberto
在’smart’模式下管理一个 memcached 实例。memcached会在uWSGI停止和重载下存活。
[uwsgi]
master = true
socket = :3031
smart-attach-daemon = /tmp/memcached.pid memcached -p 11311 -d -P /tmp/memcached.pid -u roberto
在smart模式下管理2个 mongodb 实例:
[uwsgi]
master = true
socket = :3031
smart-attach-daemon = /tmp/mongo1.pid mongod --pidfilepath /tmp/mongo1.pid --dbpath foo1 --port 50001
smart-attach-daemon = /tmp/mongo2.pid mongod --pidfilepath /tmp/mongo2.pid --dbpath foo2 --port 50002
管理 PostgreSQL 专用实例 (/db/foobar1中的集群):
[uwsgi]
master = true
socket = :3031
smart-attach-daemon = /db/foobar1/postmaster.pid /usr/lib/postgresql/9.1/bin/postgres -D /db/foobar1
管理 celery:
[uwsgi]
master = true
socket = :3031
smart-attach-daemon = /tmp/celery.pid celery -A tasks worker --pidfile=/tmp/celery.pid
管理 delayed_job:
[uwsgi]
master = true
socket = :3031
env = RAILS_ENV=production
rbrequire = bundler/setup
rack = config.ru
chdir = /var/apps/foobar
smart-attach-daemon = %(chdir)/tmp/pids/delayed_job.pid %(chdir)/script/delayed_job start
管理 dropbear:
[uwsgi]
namespace = /ns/001/:testns
namespace-keep-mount = /dev/pts
socket = :3031
exec-as-root = chown -R www-data /etc/dropbear
attach-daemon = /usr/sbin/dropbear -j -k -p 1022 -E -F -I 300
当使用namespace选项时,你可以附加一个dropbear守护进程,以允许直接访问指定名字空间内的系统。这要求 /dev/pts 文件系统被挂载在该名字空间内,并且你的worker运行时使用的用户可以访问该名字空间内的 /etc/dropbear 目录。
Legion支持¶
自uWSGI 1.9.9起,使用 uWSGI Legion子系统 子系统来进行守护进程管理成为了可能。Legion守护将只在legion lord节点上执行,因此在每个legion中,将总是只有单个守护实例运行。一旦lord死掉,就会在另一个节点上生成守护进程。要添加一个legion守护,请使用–legion-attach-daemon, –legion-smart-attach-daemon和 –legion-smart-attach-daemon2选项,它们拥有与普通的守护进程选项相同的语法。不同在于,需要添加legion名作为第一个参数。
例子:
管理 celery beat:
[uwsgi]
master = true
socket = :3031
legion-mcast = mylegion 225.1.1.1:9191 90 bf-cbc:mysecret
legion-smart-attach-daemon = mylegion /tmp/celery-beat.pid celery beat --pidfile=/tmp/celery-beat.pid
–attach-daemon2¶
已在uWSGI 2.0中添加这个选项,以允许高级配置。它是一个键值选项,接收以下键:
command
/cmd
/exec
: 要执行的命令行freq
: 在认为一个守护进程“损坏”之前的最大尝试次数pidfile
: 要检查的pid文件 (启用smart模式)control
: 如果设置了,那么守护进程则成为一个‘控制’者:如果它死掉了,整个uWSGI实例都会挂掉daemonize
/daemon
: 守护进程 (启用smart2模式)touch
用以检查的分号分隔带文件列表:每当‘碰了’它们,就会重启守护进程stopsignal
/stop_signal
: 当停止uWSGI时,发送给守护进程的信号数reloadsignal
/reload_signal
: 当重载uWSGI时,发送给守护进程的信号数stdin
: 如果设置了,文件描述符0将不会重新映射到/dev/nulluid
: 移除特权到指定uid (要求master作为root运行)gid
: 移除特权到指定gid (要求master作为root运行)ns_pid
: 在新的pid名字空间内生成进程 (要求master作为root运行,仅限Linux)chdir
: 运行命令之前,chdir()到指定的目录 (在uWSGI 2.0.6中添加)
例如:
[uwsgi]
attach-daemon2 = cmd=my_daemon.sh,pidfile=/tmp/my.pid,uid=33,gid=33,stopsignal=3
Master FIFO¶
自uWSGI 1.9.17起可用。
一般来说,使用UNIX信号来管理maser,但是我们正在用尽信号数,并且(更重要的是)不需要跟PID混在一起回极大地简化外部管理脚本的实现。
因此,取代信号,你可以告诉master创建一个UNIX命名管道 (FIFO),这样,你刻意用它来发布命令给master。
要创建一个FIFO,仅需添加 --master-fifo <filename>
,然后开始发布命令给它。
echo r > /tmp/yourfifo
你可以一次性发送多个命令。
# add 3 workers and print stats
echo +++s > /tmp/yourfifo
可用命令¶
- ‘0’ to ‘9’ - 设置fifo slot (见下文)
- ‘+’ - 在cheaper模式的时候增加worker数 (添加
--cheaper-algo manual
以获得完全控制) - ‘-‘ - 在cheaper模式的时候减少worker数 (添加
--cheaper-algo manual
以获得完全控制) - ‘B’ - 让Emperor进行加固 (broodlord模式,要求uWSGI >= 2.0.7)
- ‘C’ - 设置cheap模式
- ‘c’ - 出发链重载
- ‘E’ - 触发一次Emperor重新扫描
- ‘f’ - 重新fork master (危险,但很强大)
- ‘l’ - 重新打开日志文件 (需要 –log-master 和 –logto/–logto2)
- ‘L’ - 触发日志循环 (需要 –log-master 和 –logto/–logto2)
- ‘p’ - 暂定/恢复实例
- ‘P’ - 更新pidfile (在master重新fork之后会非常有用)
- ‘Q’ - 粗鲁地关闭实例
- ‘q’ - 优雅地关闭实例
- ‘R’ - 发送粗鲁重载
- ‘r’ - 发送优雅重载
- ‘S’ - 阻塞/消除阻塞订阅
- ‘s’ - 在日志中打印统计信息
- ‘W’ - 粗鲁地重载worker
- ‘w’ - 优雅地重载worker
FIFO slot¶
uWSGI支持多至10个不同的FIFO文件。默认情况下,绑定第一个指定的(映射为‘0’)。
在实例的生命周期中,你可以通过简单发送要使用的FIFO slot的数字来从一个FIFO改成另一个。
[uwsgi]
master-fifo = /tmp/fifo0
master-fifo = /tmp/fifo1
master-fifo = /var/run/foofifo
processes = 2
...
默认情况下,会分配 /tmp/fifo0
,但在发送以下命令之后:
echo 1 > /tmp/fifo0
将会绑定 /tmp/fifo1
文件。
当你(滥)用“fork master”命令(’f’)时,映射FIFO文件到特定的实例是非常有用的。
echo 1fp > /tmp/fifo0
在发送这个命令后,一个新的uWSGI实例(继承所有的绑定socket)将会生成,而老的将会被置为”暂停”模式(‘p’命令)。
如果我们在’f’和’p’之前已经发送了‘1’命令,那么老的实例现在将会接收在/tmp/fifo1 (slot 1)的命令,而新的将会使用默认的(‘0’)。
你可以做到很多技巧,也有很多方法尽情使用master的fork。
只需考虑到,极端情况可能会发生在任何地方,特别是如果你使用uWSGI最复杂的功能。
小抄¶
- FIFO是创建于非阻塞模式,并且在每次客户端断连的时候由master重新创建。
- 你可以通过插件或者C钩子,使用全局数组
uwsgi_fifo_table
来覆盖(或添加)命令。 - 只有运行master的uid才有fifo的写权限。
使用inetd/xinetd进行socket激活¶
Inetd和Xinetd是两个用来按需启动网络进程的守护进程。你也可以在uWSGI中使用。
Inetd¶
127.0.0.1:3031 stream tcp wait root /usr/bin/uwsgi uwsgi -M -p 4 --wsgi-file /root/uwsgi/welcome.py --log-syslog=uwsgi
通过这个配置,一旦进行了第一个连接,就会在端口3031上运行uWSGI。注意:第一个参数 (紧跟着/usr/bin/uwsgi后的那一个)会被映射到 argv[0]
。不要忘了这个 – 如果你想确定的话,总是将其设置为 uwsgi
。
Xinetd¶
service uwsgi
{
disable = no
id = uwsgi-000
type = UNLISTED
socket_type = stream
server = /root/uwsgi/uwsgi
server_args = --chdir /root/uwsgi/ --module welcome --logto /tmp/uwsgi.log
port = 3031
bind = 127.0.0.1
user = root
wait = yes
}
再次,你无需在uWSGI中指定socket,因为xinetd会将其传递给服务器。
通过Upstart运行uWSGI¶
Upstart是类Ubuntu发行版本的init系统。
它基于声明式配置文件,而非昔日的shell脚本,配置文件放在 /etc/init
目录中。
一个简单的脚本 (/etc/init/uwsgi.conf)¶
# simple uWSGI script
description "uwsgi tiny instance"
start on runlevel [2345]
stop on runlevel [06]
respawn
exec uwsgi --master --processes 4 --die-on-term --socket :3031 --wsgi-file /var/www/myapp.wsgi
使用Emperor¶
对于每个应用而言,比init文件更好的一种方法是通过Upstart只启动一个Emperor,然后让这个Emperor处理剩下的工作。
# Emperor uWSGI script
description "uWSGI Emperor"
start on runlevel [2345]
stop on runlevel [06]
respawn
exec uwsgi --emperor /etc/uwsgi
如果你想在master进程下运行Emperor(出于访问高级特性的目的),那么记住添加–die-on-term
# Emperor uWSGI script
description "uWSGI Emperor"
start on runlevel [2345]
stop on runlevel [06]
respawn
exec uwsgi --master --die-on-term --emperor /etc/uwsgi
什么是–die-on-term?¶
默认情况下,uWSGI将SIGTERM信号映射到一个“一个粗暴的加载过程”。
然而,Upstart使用SIGTERM来完全关闭进程。 die-on-term
反转了SIGTERM和SIGQUIT对uWSGI的意义。
前者将关闭整个栈,而后者将粗暴的加载它。
socket激活 (自Ubuntu 12.04起)¶
较新版本的Upstart具有一个类似于Inetd的功能,它在连接到特定socket的时候让进程启动。
只有当一个客户端(或者web服务器)第一次连接到uWSGI的时候,你才可以使用这个功能来启动uWSGI。
‘start on socket’指令将触发这种行为。
你无需在uWSGI中指定这个socket,因为Upstart自己将会传递它。
# simple uWSGI script
description "uwsgi tiny instance"
start on socket PROTO=inet PORT=3031
stop on runlevel [06]
respawn
exec uwsgi --master --processes 4 --die-on-term --wsgi-file /var/www/myapp.wsgi
Systemd¶
uWSGI是用于 systemd 的一个新型守护进程。
它可以通知状态改变和准备好使用。
当uWSGI检测到它正运行于systemd下时,启用通知系统。
添加Emperor到systemd¶
将uWSGI应用与你的初始化系统集成的一个方法事使用 Emperor 。
你的初始化系统将只会与统治所有的应用的Emperor进行通信。
创建一个systemd服务文件 (你可以将其保存为/etc/systemd/system/emperor.uwsgi.service)
注解
小心使用一些systemd版本 (例如,Debian Jessie的215),因为SIGQUIT信号将会毁掉systemd服务。在那里,使用KillSignal=SIGTERM + “die-on-term” UWSGI选项。
[Unit]
Description=uWSGI Emperor
After=syslog.target
[Service]
ExecStart=/root/uwsgi/uwsgi --ini /etc/uwsgi/emperor.ini
# Requires systemd version 211 or newer
RuntimeDirectory=uwsgi
Restart=always
KillSignal=SIGQUIT
Type=notify
StandardError=syslog
NotifyAccess=all
[Install]
WantedBy=multi-user.target
然后运行它
systemctl start emperor.uwsgi.service
检查它的状态。
systemctl status emperor.uwsgi.service
你会看到Emperor报告管理的vassal数目给systemd (和你)。
emperor.uwsgi.service - uWSGI Emperor
Loaded: loaded (/etc/systemd/system/emperor.uwsgi.service)
Active: active (running) since Tue, 17 May 2011 08:51:31 +0200; 5s ago
Main PID: 30567 (uwsgi)
Status: "The Emperor is governing 1 vassals"
CGroup: name=systemd:/system/emperor.uwsgi.service
├ 30567 /root/uwsgi/uwsgi --ini /etc/uwsgi/emperor.ini
├ 30568 /root/uwsgi/uwsgi --ini werkzeug.ini
└ 30569 /root/uwsgi/uwsgi --ini werkzeug.ini
你可以这样停止Emperor (以及它管理的所有应用)
systemctl stop emperor.uwsgi.service
一个简单的 emperor.ini
可以看起来像这样 (www-data只是一个匿名用户)
注意:不要守护Emperor (或者master),除非你知道你在干什么!!!
[uwsgi]
emperor = /etc/uwsgi/vassals
uid = www-data
gid = www-data
如果你想要允许每个vassal在不同的特权下允许,那么将 uid
和 gid
选项从emperor配置中移除 (请阅读Emperor文档!)
日志记录¶
使用前面的服务文件,所有的Emperor消息都会到syslog中。你可以通过移除 StandardError=syslog
指令来避免它。
如果你那样做,那么确保在你的Emperor配置中设置 --logto
选项,否则,你所有的日子都将会丢失!
将socket放到/run/¶
在一个现代系统上,/run/是作为一个tmpfs挂载的,并且是存放socket和pid文件的正确之地。要让systemd自动创建一个带有正确的user/group的/run/uwsgi/子目录,并且当守护进程停止的时候清理该目录,那么添加
RuntimeDirectory=uwsgi
到你的systemd uwsgi单元文件的[Service]部分。这个 RuntimeDirectory
参数要求systemd版本211或者以上。对于systemd较老的版本,创建一个systemd-tmpfiles配置文件 (你可以将其存为/etc/tmpfiles.d/emperor.uwsgi.conf):
d /run/uwsgi 0755 www-data www-data -
Socket激活¶
从uWSGI 0.9.8.3起,socket激活就可以用了。你可以设置systemd只在第一个socket连接之后才生成uWSGI实例。
创建所需的emperor.uwsgi.socket (在 /etc/systemd/system/emperor.uwsgi.socket
)。注意,.socket文件名必须匹配.service文件名。
[Unit]
Description=Socket for uWSGI Emperor
[Socket]
# Change this to your uwsgi application port or unix socket location
ListenStream=/tmp/uwsgid.sock
[Install]
WantedBy=sockets.target
然后禁用服务,并且启用socket单元。
# systemctl disable emperor.uwsgi.service
# systemctl enable emperor.uwsgi.socket
当使用Systemd socket激活的时候,你无需在你的uWSGI配置中指定任何socket;实例将会从Systemd继承socket。
要拥有Systemd socket激活之下的uWSGI服务的HTTP (取代二进制uwsgi协议),则设置 protocol
为 http
;例如,在一个INI中,这样:
[uwsgi]
protocol = http
wsgi = ...
...
在systemd中,每个应用一个服务¶
另一个方法是让systemd处理启动单独的应用,同时利用systemd模板单元文件,当然还有socket激活。每个应用将会在其自己的用户下允许。
/etc/systemd/system/uwsgi-app@.socket
:
[Unit]
Description=Socket for uWSGI app %i
[Socket]
ListenStream=/var/run/uwsgi/%i.socket
SocketUser=www-%i
SocketGroup=www-data
SocketMode=0660
[Install]
WantedBy=sockets.target
/etc/systemd/system/uwsgi-app@.service
:
[Unit]
Description=%i uWSGI app
After=syslog.target
[Service]
ExecStart=/usr/bin/uwsgi \
--ini /etc/uwsgi/apps-available/%i.ini \
--socket /var/run/uwsgi/%i.socket
User=www-%i
Group=www-data
Restart=on-failure
KillSignal=SIGQUIT
Type=notify
StandardError=syslog
NotifyAccess=all
现在,添加一个新的应用到你的系统中就只是创建合适的用户并启用socket和服务的事情了。例如,如果要配置cgit:
adduser www-cgit --disabled-login --disabled-password \
--ingroup www-data --home /var/lib/www/cgit --shell /bin/false
systemctl enable uwsgi-app@cgit.socket
systemctl enable uwsgi-app@cgit.service
systemctl start uwsgi-app@cgit.socket
然后配置ini文件 /etc/uwsgi/apps-available/cgit.ini
:
[uwsgi]
master = True
cheap = True
idle = 600
die-on-idle = True # If app is not used often, it will exit and be launched
# again by systemd requested by users.
manage-script-name = True
plugins = 0:cgi
cgi = /usr/lib/cgit/cgit.cgi
最后,如果适用,如往常一样配置你的HTTP服务器。
使用Circus运行uWSGI实例¶
Circus (https://circus.readthedocs.io/en/0.7/)是一个用Python写的进程管理器。它非常类似于像Supervisor这样的工程,但具有一些额外特性。虽然大部分,主要的功能在uWSGI中都能找到对应的,但是Circus可以被当成一个库使用,让你可以构建动态配置 (并且扩展uWSGI模式)。这个部分是非常重要的,并且可能就是Circus的真正卖点。
Socket激活¶
基于古老的inetd模式,Circu可以绑定到socket,并且将其传递给它的孩子。
从一个简单的Circus配置开始 (称之为circus.ini):
[circus]
endpoint = tcp://127.0.0.1:5555
pubsub_endpoint = tcp://127.0.0.1:5556
stats_endpoint = tcp://127.0.0.1:5557
[watcher:dummy]
cmd = uwsgi --http-socket fd://$(circus.sockets.foo) --wsgi-file yourapp.wsgi
use_sockets = True
send_hup = True
stop_signal = QUIT
[socket:foo]
host = 0.0.0.0
port = 8888
这样运行它
circusd circus.ini
(更好的) Socket激活¶
如果你想按需生成实例,那么你可能会想要在不再使用它们的时候将其关闭。要实现这点,则使用–idle uWSGI选项。
[circus]
check_delay = 5
endpoint = tcp://127.0.0.1:5555
pubsub_endpoint = tcp://127.0.0.1:5556
stats_endpoint = tcp://127.0.0.1:5557
[watcher:dummy]
cmd = uwsgi --master --idle 60 --http-socket fd://$(circus.sockets.foo) --wsgi-file yourapp.wsgi
use_sockets = True
warmup_delay = 0
send_hup = True
stop_signal = QUIT
[socket:foo]
host = 0.0.0.0
port = 8888
这一次,我们启用了master进程。它将会管理–idle选项,在实例不活跃时间超过60秒的时候关闭它。
在uWSGI中嵌入一个应用¶
从uWSGI 0.9.8.2开始,你可以在服务器二进制中嵌入文件。这些可以是任何文件类型,包括配置文件。你也可以嵌入目录,因此,通过拦截Python模块加载器,你也可以明确导入包。在这个例子中,我们将嵌入一个完整的Flask项目。
第1步:创建构建配置文件¶
我们假设你已经准备好了uWSGI源代码。
在 buildconf
目录下,定义你的配置文件 —— 让我们称之为flask.ini:
[uwsgi]
inherit = base
main_plugin = python
bin_name = myapp
embed_files = bootstrap.py,myapp.py
myapp.py
是一个简单的flask应用。
from flask import Flask
app = Flask(__name__)
app.debug = True
@app.route('/')
def index():
return "Hello World"
bootstrap.py
在源代码发布版中。它会扩展python的import子系统,来使用内嵌在uWSGI中的文件。
现在,编译你饱含应用的服务器。文件将会作为可执行符号潜入。文件中的点和连接号等会被转换成下划线。
python uwsgiconfig.py --build flask
由于 bin_name
是 myapp
,现在你可以运行
./myapp --socket :3031 --import sym://bootstrap_py --module myapp:app
``sym://`` 伪协议让uWSGI能够访问二进制文件的嵌入符号和数据,在这种情况下,直接从二进制镜像中导入bootstrap.py。
第2步:嵌入配置文件¶
我们想要让我们的二进制文件自动加载我们的Flask应用,而无需传递一个长长地命令行。
让我们创建配置 – flaskconfig.ini:
[uwsgi]
socket = 127.0.0.1:3031
import = sym://bootstrap_py
module = myapp:app
然后将其作为一个配置文件添加到构建配置文件中。
[uwsgi]
inherit = default
bin_name = myapp
embed_files = bootstrap.py,myapp.py
embed_config = flaskconfig.ini
然后,在你重新构建服务器后
python uwsgiconfig.py --build flask
你现在可以简单加载
./myapp
# Remember that this new binary continues to be able to take parameters and config files:
./myapp --master --processes 4
第3步:嵌入flask自身¶
现在,我们准备好玩转uWSGI的忍者神功了。我们想要单个二进制文件,它嵌入所有的Flask模块,包括Werkzeug和Jinja2,Flask的依赖。我们需要拥有这些包的目录,然后在构建配置文件中指定它们。
[uwsgi]
inherit = default
bin_name = myapp
embed_files = bootstrap.py,myapp.py,werkzeug=site-packages/werkzeug,jinja2=site-packages/jinja2,flask=site-packages/flask
embed_config = flaskconfig.ini
注解
这次,我们使用了”name=directory”形式,来强制符号使用指定的名字,以避免获得像 site_packages_flask___init___py
这样恶心的名字。
重新构建并重新运行。当运行以向你展示加载嵌入模块时,我们添加了–no-site。
python uwsgiconfig.py --build flask
./myapp --no-site --master --processes 4
第4步:添加模板¶
仍然不满意?好吧,你也不应该满意。
[uwsgi]
inherit = default
bin_name = myapp
embed_files = bootstrap.py,myapp.py,werkzeug=site-packages/werkzeug,jinja2=site-packages/jinja2,flask=site-packages/flask,templates
embed_config = flaskconfig.ini
模板将会被添加到二进制文件中……但是我们会需要通过创建一个自定义的Jinja2模板加载器,指示Flask如何从二进制镜像中加载模板。
from flask import Flask, render_template
from flask.templating import DispatchingJinjaLoader
class SymTemplateLoader(DispatchingJinjaLoader):
def symbolize(self, name):
return name.replace('.','_').replace('/', '_').replace('-','_')
def get_source(self, environment, template):
try:
import uwsgi
source = uwsgi.embedded_data("templates_%s" % self.symbolize(template))
return source, None, lambda: True
except:
pass
return super(SymTemplateLoader, self).get_source(environment, template)
app = Flask(__name__)
app.debug = True
app.jinja_env.loader = SymTemplateLoader(app)
@app.route('/')
def index():
return render_template('hello.html')
@app.route('/foo')
def foo():
return render_template('bar/foo.html')
POW! BIFF! NINJA AWESOMENESS.
日志记录¶
参见
基本的日志记录¶
uWSGI中最基本的日志记录的形式是将请求、错误和信息消息写到标准输出/标准错误。这是默认配置中使用的方式。日志重定向的最基本的形式是 --logto
/
--logto2
/ --daemonize
选项,它们允许你重定向日志到文件中。
基本记录日志到文件¶
要将日志写到文件中,而不是标准输出/标准错误,则使用 --logto
,或者同时守护uWSGI, --daemonize
。
./uwsgi -s :3031 -w simple_app --daemonize /tmp/mylog.log
./uwsgi -s :3031 -w simple_app --logto /tmp/mylog.log
# logto2 only opens the log file after privileges have been dropped to the specified uid/gid.
./uwsgi -s :3031 -w simple_app --uid 1001 --gid 1002 --logto2 /tmp/mylog.log
基本日志记录 (连接UDP模式)¶
使用UDP日志记录,你可以集中集群日志记录,或者重定向日志的持久化到其他机器上,以卸载磁盘I/O。UDP日志记录可以工作在守护模式和互动模式下。UDP日志记录是在连接socket模式下进行的,因此,UDP服务器必须在uWSGI启动前可用。对于更原始的方法(在未连接模式下工作),见socket日志记录部分。
要启用连接UDP模式,则传递UDP服务器的地址到 --daemonize
/--logto
选项:
./uwsgi -s :3031 -w simple_app --daemonize 192.168.0.100:1717
./uwsgi -s :3031 -w simple_app --logto 192.168.0.100:1717
这将会重定向所有的标准输出/标准错误数据到192.168.0.100,端口1717上的UDP socket。现在,你需要一个UDP服务器,它将管理你的UDP消息。你可以使用netcat,甚至是uWSGI:
nc -u -p 1717 -s 192.168.0.100 -l
./uwsgi --udp 192.168.0.100:1717
第二种方式更有用一点,因为它将打印每条消息的来源 (ip:port)。在多个uWSGI服务器将日志记录在同一个UDP服务器上的情况下,它将让你区分服务器。当然,你可以编写你自己的应用来管理/过滤/保存通过UDP接收到的日志。
可插拔记录器¶
uWSGI还支持可插拔记录器,这让你在何处以及如何记录更具灵活性。取决于于你的uWSGI构建的配置,一些记录器可能/可能没有用。一些也许要求作为插件加载。要找出你的构建中哪些插件可用,请带 --logger-list
调用uWSGI。要设置一个可插拔记录器,则使用 --logger
或者
--req-logger
选项。 --logger
将为每条消息设置一个记录器,而 --req-logger
将会为请求信息消息设置一个记录器。
这是语法:
--logger <plugin>[:options]
--logger "<name> <plugin>[:options]" # The quotes are only required on the command line -- config files don't use them
只要你喜欢,你可以设置尽可能多的记录器。命名插件用于日志路由,下面是使用纯文本文件分割请求/错误日志记录的一个非常简单的例子。
[uwsgi]
req-logger = file:/tmp/reqlog
logger = file:/tmp/errlog
日志路由¶
默认情况下,所有的日志行会被发送到所有声明的记录器。如果你不想要这样,那么你可以使用 --log-route
(以及用于请求记录器的 --log-req-route
),指定一个正则表达式来路由某些日志消息到不同的目的地。
例如:
[uwsgi]
logger = mylogger1 syslog
logger = theredisone redislog:127.0.0.1:6269
logger = theredistwo redislog:127.0.0.1:6270
logger = file:/tmp/foobar # This logger will log everything else, as it's not named
logger = internalservererror file:/tmp/errors
# ...
log-route = internalservererror (HTTP/1.\d 500)
log-route = mylogger1 uWSGI listen queue of socket .* full
这将会记录每个500错误到/tmp/errors,而监听队列满错误将会出现在/tmp/foobar中。这有点类似于 uWSGI告警子系统 (自1.3起) ,虽然告警通常更重,应该只用于紧急情况。
写日志到文件中¶
logfile
插件 —— 默认嵌入。
写日志到socket¶
logsocket
插件 —— 默认嵌入。
你可以使用 --logger socket:...
(或者 --log-socket ...
) 写日志到一个未连接UNIX/UDP socket。
uwsgi --socket :3031 --logger socket:/tmp/uwsgi.logsock
将会发送日志项到Unix socket /tmp/uwsgi.logsock
.
uwsgi --socket :3031 --logger socket:192.168.173.19:5050
将会发送日志数据报到UDP地址192.168.173.19,端口是5050.你也可以通过传递多播地址,多播日志到多个日志服务器上:
uwsgi --socket :3031 --logger socket:225.1.1.1:1717
写日志到syslog¶
logsyslog
插件 —— 默认嵌入。
logsyslog
插件路由日志到Unix标准的syslog中。你可以传递一个用于发送的可选的ID,作为日志项的”facility”参数。
uwsgi --socket :3031 --logger syslog:uwsgi1234
或者
uwsgi --socket :3031 --logger syslog:uwsgi1234,local6
发送到local6 facility
写日志到远程syslog¶
logrsyslog
插件 —— 默认嵌入。
logrsyslog
插件路由日志到位于远程服务器的Unix标准syslog上。除了远程syslog服务器的address+port外,你可以传递一个用于发送的可选的ID,作为日志项的”facility”参数。
uwsgi --socket :3031 --logger rsyslog:12.34.56.78:12345,uwsgi1234
Redis记录器¶
redislog
插件 —— 默认嵌入。
默认情况下, redislog
插件将会“发布”每个日志行到一个redis pub/sub队列中。该记录器插件的语法如下:
--logger redislog[:<host>,<command>,<prefix>]
默认, host
会被映射到 127.0.0.1:6379
, command
会被映射到”publish uwsgi” 并且 prefix
是空的。要发布到一个名为foobar的队列,则使用 redislog:127.0.0.1:6379,publish foobar
。Redis日记记录不只限于pub/sub。例如,你可以推送项到一个列表中,如下所示。
--logger redislog:/tmp/redis.sock,rpush foo,example.com
当将一个日志行写入到一个远程服务器的时候,一个错误的场景会导致阻塞master,好的办法是使用 --threaded-logger
来卸载日志写入到第二个线程中。
MongoDB记录器¶
mongodblog
插件 —— 默认嵌入。.
MongoDB日志记录(mongodblog
)的记录器语法是
--logger mongodblog[:<host>,<collection>,<node>]
其中, host
是MongoDB实例的地址 (默认 127.0.0.1:27017
), collection
命名要写入日志行的集合 (默认是 uwsgi.logs
),而 node
是用于实例发送日志的身份字符串 (默认是server hostname)。
--logger mongodblog
将会用默认值运行记录器,而
--logger mongodblog:127.0.0.1:9090,foo.bar
将会写入日志到mongodb服务器127.0.0.1:9090的集合 foo.bar
中,使用默认的节点名。就如Redis记录器一样,卸载日志写入到一个专用的线程是个好的选择。
[uwsgi]
threaded-logger = true
logger = mongodblog:127.0.0.1:27017,uwsgi.logs_of_foobar
# As usual, you could have multiple loggers:
# logger = mongodblog:192.168.173.22:27017,uwsgi.logs_of_foobar
socket = :3031
ZeroMQ日志记录¶
就如UDP日志记录一样,你可以通过ZeroMQ集中/分布日志记录。使用 ZMQ_PULL
socket构建你的日志记录守护程序:
import zmq
ctx = zmq.Context()
puller = ctx.socket(zmq.PULL)
puller.bind("tcp://192.168.173.18:9191")
while True:
message = puller.recv()
print message,
现在,运行你的uWSGI服务器:
uwsgi --logger zeromq:tcp://192.168.173.18:9191 --socket :3031 --module werkzeug.testapp:test_app
(--log-zeromq
是这个记录器的一个别名。)
Crypto记录器 (插件)¶
如果你在云服务商托管你的应用,并且不使用持久化存储,那么你也许想要发送你的日志到一个外部系统。然而,日志通常包含敏感信息,并应该将那些信息明文传输。
logcrypto
插件记录器试图通过在发送之前加密每个日志包,然后通过UDP发送到一个能够解密它的服务器,从而解决这个问题。下一个例子将会发送每个日志包到192.168.173.22:1717上的一个UDP服务器,每个日志包都会用CBC模式中的Blowfish算法,通过密钥 ciaociao
进行加密。
uwsgi --plugin logcrypto --logger crypto:addr=192.168.173.22:1717,algo=bf-cbc,secret=ciaociao -M -p 4 -s :3031
一个样例服务器如下: https://github.com/unbit/uwsgi/blob/master/contrib/cryptologger.rb
Graylog2记录器 (插件)¶
graylog2
插件 —— 非默认编译。
这个插件将会发送日志的到一个Graylog2服务器上,使用Graylog2的原生GELF格式。
uwsgi --plugin graylog2 --logger graylog2:127.0.0.1:1234,dsfargeg
Systemd记录器 (插件)¶
systemd_logger
插件 —— 非默认编译。
这个插件将会将日志项写入到Systemd journal中。
uwsgi --plugin systemd_logger --logger systemd
编写你自己的日志记录插件¶
这个插件, foolog.c
将会把你的消息写入到由–logto/–daemonize指定的文件中,带有一个简单前缀,并使用vector IO。
#include <uwsgi.h>
ssize_t uwsgi_foolog_logger(struct uwsgi_logger *ul, char *message, size_t len) {
struct iovec iov[2];
iov[0].iov_base = "[foo] ";
iov[0].iov_len = 6;
iov[1].iov_base = message;
iov[1].iov_len = len;
return writev(uwsgi.original_log_fd, iov, 2);
}
void uwsgi_foolog_register() {
uwsgi_register_logger("syslog", uwsgi_syslog_logger);
}
struct uwsgi_plugin foolog_plugin = {
.name = "foolog",
.on_load = uwsgi_foolog_register,
};
格式化uWSGI请求日志¶
uWSGI有一个 --logformat
选项,用来构建自定义的请求日志行。语法很简单:
[uwsgi]
logformat = i am a logline reporting "%(method) %(uri) %(proto)" returning with status %(status)
所有用 %()
标记的变量都可以使用特定的规则来替代。定义了三种日志变量 (“offsetof”, 函数和用户定义)。
offsetof¶
这些是一味从当前请求内部的 wsgi_request
结构获取的。
%(uri)
-> REQUEST_URI%(method)
-> REQUEST_METHOD%(user)
-> REMOTE_USER%(addr)
-> REMOTE_ADDR%(host)
-> HTTP_HOST%(proto)
-> SERVER_PROTOCOL%(uagent)
-> HTTP_USER_AGENT (自1.4.5起)%(referer)
-> HTTP_REFERER (自1.4.5起)
函数¶
这些是简单的函数,调用来生成日志变量值:
%(status)
-> HTTP响应状态码%(micros)
-> 响应时间,以微秒为单位%(msecs)
-> 响应时间,以毫秒为单位%(time)
-> 请求开始的时间戳%(ctime)
-> 请求开始的ctime%(epoch)
-> Unix格式的当前时间%(size)
-> 响应体大小 + 响应头大小 (自1.4.5起)%(ltime)
-> 人类可读(Apache风格)的请求时间 (自1.4.5起)%(hsize)
-> 响应头大小 (自1.4.5起)%(rsize)
-> 响应体大小 (自1.4.5起)%(cl)
-> 请求内容体大小 (自1.4.5起)%(pid)
-> 处理请求的worker的pid (自1.4.6起)%(wid)
-> 处理请求的worker的id (自1.4.6起)%(switches)
-> 异步切换数 (自1.4.6起)%(vars)
-> 请求中的CGI变量数 (自1.4.6起)%(headers)
-> 已生成的响应头数 (自1.4.6起)%(core)
-> 运行请求的核心 (自1.4.6起)%(vsz)
-> 地址空间/虚拟内存使用 (单位为字节) (自1.4.6起)%(rss)
-> RSS内存使用 (单位为字节) (自1.4.6起)%(vszM)
-> 地址空间/虚拟内存使用 (单位为MB) (自1.4.6起)%(rssM)
-> RSS内存使用 (单位为MB) (自1.4.6起)%(pktsize)
-> 内部的请求uwsgi包大小 (自1.4.6起)%(modifier1)
-> 请求的modifier1 (自1.4.6起)%(modifier2)
-> 请求的modifier2 (自1.4.6起)%(metric.XXX)
-> 访问XXX度量值 (见 度量(Metrics)子系统)%(rerr)
-> 请求的读错误数 (自1.9.21起)%(werr)
-> 请求的写错误数 (自1.9.21起)%(ioerr)
-> 请求的读写错误数 (自1.9.21起)%(tmsecs)
-> 请求开始时间戳,自纪元起,单位为毫秒 (自1.9.21起)%(tmicros)
-> 请求开始时间戳,自纪元起,单位为微秒 (自1.9.21起)%(var.XXX)
-> 请求变量XXX的内容 (例如var.PATH_INFO,自1.9.21起可用)
用户定义的日志变量¶
你可以在你的请求处理函数中定义日志变量。这些变量只存活在每个请求中。
import uwsgi
def application(env, start_response):
uwsgi.set_logvar('foo', 'bar')
# returns 'bar'
print uwsgi.get_logvar('foo')
uwsgi.set_logvar('worker_id', str(uwsgi.worker_id()))
...
使用以下日志格式,你将能够访问代码定义的日志变量:
uwsgi --logformat 'worker id = %(worker_id) for request "%(method) %(uri) %(proto)" test = %(foo)'
Apache风格相结合的请求日志记录¶
要生成兼容Apache的日志:
[uwsgi]
...
log-format = %(addr) - %(user) [%(ltime)] "%(method) %(uri) %(proto)" %(status) %(size) "%(referer)" "%(uagent)"
...
hack日志格式¶
(更新至1.9.21)
你可以这样注册新的”logchunk” (为每个日志格式符号调用的函数)
struct uwsgi_logchunk *uwsgi_register_logchunk(char *name, ssize_t (*func)(struct wsgi_request *, char **), int need_free);
name
– 符号名need_free
– 如果是1,表示由func
设置的指针必须释放(free())func
– 日志处理函数中调用的函数
static ssize_t uwsgi_lf_foobar(struct wsgi_request *wsgi_req, char **buf) {
*buf = uwsgi_num2str(wsgi_req->status);
return strlen(*buf);
}
static void register_logchunks() {
uwsgi_register_logchunk("foobar", uwsgi_lf_foobar, 1);
}
struct uwsgi_plugin foobar_plugin = {
.name = "foobar",
.on_load = register_logchunks,
};
现在,如果你加载foobar插件,那么,你将能够使用 %(foobar) 请求日志变量 (它会报告请求状态)。
日志编码器¶
uWSGI 1.9.16开始就有了“日志编码”功能。
一个编码器接收一个日志行,然后返回它的一个“转换”。
可以通过插件添加编码器,并可以链式(一个编码器的输出将是随后的编码器的输入,依次递推)启动编码器。
[uwsgi]
; send logs to udp address 192.168.173.13:1717
logger = socket:192.168.173.13:1717
; before sending a logline to the logger encode it in gzip
log-encoder = gzip
; after gzip add a 'clear' prefix to easy decode
log-encoder = prefix i am gzip encoded
...
使用这个配置,日志服务器将会接收 “i am gzip encoded”字符串,后面跟着以gzip编码的真正的日志消息
日志编码器的语法如下:
log-encoder = <encoder>[ args]
而args (如果有的话)由单个空格分隔
请求日志 VS 标准输出/标准错误¶
–log-encoder选项只编码标准输出/标准错误日志。
如果你想要编码请求日志,则使用–log-req-encoder:
[uwsgi]
; send request logs to udp address 192.168.173.13:1717
req-logger = socket:192.168.173.13:1717
; before sending a logline to the logger encode it in gzip
log-req-encoder = gzip
; after gzip add a 'clear' prefix to easy decode
log-req-encoder = prefix i am gzip encoded
...
路由编码器¶
日志路由允许基于正则表达式发送每个日志行到不同的日志引擎。你也可以带编码器使用相同的系统:
[uwsgi]
; by default send logs to udp address 192.168.173.13:1717
logger = socket:192.168.173.13:1717
; an alternative logger using the same address
logger = secondlogger socket:192.168.173.13:1717
; use 'secondlogger' for the logline containing 'uWSGI'
log-route = secondlogger uWSGI
; before sending a logline to the 'secondlogger' logger encode it in gzip
log-encoder = gzip:secondlogger
...
Core
编码器¶
在uwsgi的’core’中,由以下可用的编码器:
prefix
添加一个原始前缀到每个日志消息
suffix
添加一个原始后缀到每个日志消息
nl
添加一个换行符字符到每个日志消息
gzip
用gzip压缩每个消息 (需要zlib)
compress
用zlib压缩每个消息 (需要zlib)
format
应用指定的格式到每个日志消息:
[uwsgi]
...
log-encoder = format [FOO ${msg} BAR]
...
json
像 format
,但是每个变量都是json转义的
[uwsgi]
...
log-encoder = json {"unix":${unix}, "msg":"${msg}"}
...
可用以下变量 (用于format和json):
${msg}
原始日志消息 (移除换行符)
${msgnl}
原始日志消息 (带换行符)
${unix}
当前unix时间
${micros}
当前unix时间,以微秒为单位
${strftime:xxx}
strftime,使用xxx格式:
[uwsgi]
...
; we need to escape % to avoid magic vars nameclash
log-encoder = json {"unix":${unix}, "msg":"${msg}", "date":"${strftime:%%d/%%m/%%Y %%H:%%M:%%S}"}
...
``msgpack`` 编码器
这是官方添加到uWSGI源的第一个日志编码器插件。它允许以msgpack格式来编码日志行 (http://msgpack.org/)。
其语法是相当多变的,因为它被开发来添加任何信息到单一的包中
log-encoder = msgpack <format>
格式相当复杂,因为它是整个包中的单个项列表。
例如,如果你想要编码 {‘foo’:’bar’, ‘test’:17} 字典,那么需要这样读取它:
a map of 2 items | the string foo | the string bar | the string test | the integer 17
总共5项。
一个更复杂的结构 {‘boo’:30, ‘foo’:’bar’, ‘test’: [1,3,3,17.30,nil,true,false]}
将是
a map of 3 items | the string boo | the number 30| the string foo| the string bar | the string test | an array of 7 items | the integer 1 | the integer 3 | the integer 3 | the float 17.30 | a nil | a true | a false
<format>字符串是这种方式的一种表示:
map:2|str:foo|str:bar|str:test|int:17
|是每个项的分隔符。冒号前的字符串是项类型,后面跟着可选的参数
支持以下项类型:
map
一个字典,参数是项个数
array
一个数组,参数是项个数
str
一个字符串,参数是字符串本身
bin
一个字节数组,参数是二进制流本身
int
一个整型,参数是数字
float
一个浮点数,参数是数字
nil
undefined/NULL
true
布尔类型 TRUE
false
布尔类型 FALSE
除了msgpack类型之外,可以用一系列的动态类型:
msg
将日志行转换成移除换行符的msgpack字符串
msgbin
将日志行转换成移除换行符的msgpack字节数组
msgnl
将日志行转换成msgpack字符串 (包含换行符)
msgbin
将日志行转换成msgpack字节数组 (包含换行符)
unix
转换成unix时间的整数格式
micros
转换成unix时间的整数格式,以微秒为单位
strftime
使用strftime语法转换成一个字符串。strftime格式就是参数
下面是一个通过udp发送日志行到logstash服务器的例子:
(logstash调试配置):
input {
udp {
codec => msgpack {}
port => 1717
}
}
output {
stdout { debug => true }
elasticsearch { embedded => true }
}
[uwsgi]
logger = socket:192.168.173.13:1717
log-encoder = msgpack map:4|str:message|msg|str:hostname|str:%h|str:version|str:%V|str:appname|str:myapp
...
这将生成以下结构:
{
"message": "*** Starting uWSGI 1.9.16-dev-29d80ce (64bit) on [Sat Sep 7 15:04:32 2013] ***",
"hostname": "unbit.it",
"version": "1.9.16-dev",
"appname": "myapp"
}
它将被存储到elasticsearch
钩子(hook)¶
(更新至uWSGI 1.9.16)
uWSGI主要的指导思想是变得“模块化”。它大量的特性作为插件暴露出来,既允许用户优化其构建,又鼓励开发者对其进行扩展。
写插件可能是一件烦人的任务,特别是当你只需要改变/实现一个单一的功能的时候。
对于简单的任务,uWSGI公开了一个钩子API,你可以任性地使用它来修改uWSGI的内部行为。
“可用钩子”的uWSGI阶段¶
在准备管理请求之前,uWSGI经历各种“阶段”。你可以将一个或多个“钩子”附加到这些阶段。
每个阶段都可能是“致命的”,如果是这样,那么一个失败的钩子将意味着整个uWSGI实例的失败 (通常调用 exit(1)
)。
目前 (2013年9月),有以下阶段可供选择:
asap
在解析完配置文件之后,完成一切事情之前直接运行。它是致命的。pre-jail
在对放弃特权的任何尝试,或者将进程放到任何形式的jail之前运行。它是致命的。post-jail
在任何jail之后立即运行,但会在删除权限之前运行。如果jail需要fork(),那么由父进程运行这个阶段。这是致命的。in-jail
在jail之后立即运行,但在post-jail之后运行。如果jail需要fork(),那么由子进程运行这个阶段。这是致命的。as-root
在删除权限之前立即运行 (作为root运行的最后机会)。 这是致命的。as-user
在删除权限之后立即运行。这是致命的。pre-app
在应用加载之前立即运行。这是致命的。post-app
在应用加载之后立即运行。这是致命的。accepting
在每个worker开始接收请求之前运行 (从uWSGI 1.9.21开始可用)。accepting1
在第一个worker开始接收请求之前运行 (从uWSGI 1.9.21开始可用)。accepting-once
在每个worker开始接收请求之前运行 (从uWSGI 1.9.21开始可用,每个实例运行一次)。accepting1-once
在第一个worker开始接收请求之前运行 (从uWSGI 1.9.21开始可用,每个实例运行一次)。as-user-atexit
在实例开始关闭之前运行。这不是致命的。as-emperor
在Emperor进程中生成vassal之后立即运行。这不是致命的。as-vassal
执行uwsgi二进制文件之前在vassal中运行。这是致命的。
“硬编码”钩子¶
如前所述,钩子子系统的目的在于允许附加“钩子”到不同的uWSGI阶段。
有两种钩子。简单的那种是所谓的”硬编码”钩子。它们以多样性为代价,公开了常见的模式。
目前 (2013年九月),有以下可用的“硬编码”钩子 (它们按照它们在下面显示的顺序运行):
mount
—— 文件系统挂载¶
参数:<filesystem> <src> <mountpoint> [flags]
公开的标志是可用于操作系统的那些。例如,在Linux上,你会选择bind, recursive, readonly等等。
umount
—— 文件系统解除挂载¶
参数:<mountpoint> [flags]
exec
运行shell命令¶
参数:<command> [args...]
通过 /bin/sh
运行命令。
如果出于某些原因,你不想要使用 /bin/sh
作为运行的shell,那么你可以使用 --binsh
选项来覆盖它。你可以指定多个 --binsh
选项 —— 将会尝试使用它们,直到找到一个有效的shell。
call
在当前进程地址空间内调用函数¶
参数:<symbol> [args...]
一般来说,会忽略参数 (唯一例外是emperor/vassal阶段,见下),因为系统期望不带参数调用符号。
<symbol>
可以是当前在进程的地址空间中任何可用的符号。
当与 --dlopen
uWSGI选项结合使用的时候,可以实现一些有趣的技巧:
// foo.c
#include <stdio.h>
void foo_hello() {
printf("I am the foo_hello function called by a hook!\n");
}
将其作为共享库进行构建:
gcc -o foo.so -shared -fPIC foo.c
然后将其加载到uWSGI符号表里。
uwsgi --dlopen ./foo.so ...
从现在开始,”foo_hello”符号就存在uWSGI符号表里了,它准备好呗“call”钩子调用了。
警告
由于–dlopen是 dlopen()
函数的一个封装,因此谨防使用绝对路径和库搜索路径。如果你不想给自己找麻烦,那么在处理共享库的时候,总是使用绝对路径。
附加“硬编码”钩子¶
每个硬编码钩子都为每个阶段公开了一组选项 (有一些例外)。
每个选项都是由钩子的名字和它的阶段组成的,因此要在 as-root
阶段运行一个命令,使用 --exec-as-root
, 或者为 as-user
阶段使用 --exec-as-user
。
记住,你可以将所有你需要的钩子附加到一个钩子-阶段对。
[uwsgi]
...
exec-as-root = cat /proc/cpuinfo
exec-as-root = echo 1 > /proc/sys/net/ipv4/ip_forward
exec-as-user = ls /tmp
exec-as-user-at-exit = rm /tmp/foobar
dlopen = ./foo.so
call-as-user = foo_hello
...
这个规则的唯一例外是 as-emperor 和 as-vassal 阶段。由于种种原因,它们公开了一堆方便的变体 —— 见下。
“高级”钩子¶
一个限制硬编码钩子的多样性(uWSGI中的一个大大的禁忌)的问题是,你不能控制整个链的顺序 (每个阶段执行按类型分组的每个钩子)。如果你想获得更多的控制权,那么“高级”钩子是最好的选择。
每个阶段都有一个单一的链,你在其中指定调用的钩子和使用哪个处理器。
处理器指定了如何运行钩子。可以通过插件注册新的处理器。
目前,核心公开的处理器是:
exec
- 与’exec’硬编码选项相同call
- 调用指定的符号,忽略返回值callret
- 调用指定的符号,期望一个int类型的返回值。任何!= 0的返回都表示失败callint
- 调用指定的符号,将参数当成一个int来解析callintret
- 调用指定的符号,将参数当成一个int来解析,并期待返回一个int。mount
- 与’mount’硬编码选项相同umount
- 与’umount’硬编码选项相同cd
- 便捷处理器,与call:chdir <directory>
相同exit
- 便捷处理器,与callint:exit [num]
相同print
- 便捷处理器,与调用uwsgi_log
符号相同write
- (从uWSGI 1.9.21起),使用write:<file> <string>将一个字符串写入到指定文件中。writefifo
- (从uWSGI 1.9.21起),使用writefifo:<file> <string>将一个字符串写入到指定FIFO中。unlink
- (从uWSGI 1.9.21起), 取消链接指定的文件
[uwsgi]
...
hook-as-root = mount:proc none /proc
hook-as-root = exec:cat /proc/self/mounts
hook-pre-app = callint:putenv PATH=bin:$(PATH)
hook-post-app = call:uwsgi_log application has been loaded
hook-as-user-atexit = print:goodbye cruel world
...
覆盖worker¶
多亏了暴露给插件的”worker”钩子,你可以覆盖每个uWSGI worker运行的代码。
目前,python插件是唯一一个暴露它的插件:
[uwsgi]
; create a bunch of sockets
socket = 127.0.0.1:3031
socket = 127.0.0.1:3032
; spawn the master
master = true
; spawn 4 processes
processes = 4
; load a python script as the worker code
python-worker-override = aioserver.py
该python脚本可以访问uwsgi模块,因此它可以控制/改变内部逻辑。
下面的例子展示了aiohttp的使用 (需要python 3.5)
import asyncio
from aiohttp import web
import uwsgi
import socket
import sys
import signal
async def handle(request):
name = request.match_info.get('name', "Anonymous")
text = "Hello, " + name
return web.Response(body=text.encode('utf-8'))
async def wshandler(request):
ws = web.WebSocketResponse()
await ws.prepare(request)
async for msg in ws:
if msg.tp == web.MsgType.text:
ws.send_str("Hello, {}".format(msg.data))
elif msg.tp == web.MsgType.binary:
ws.send_bytes(msg.data)
elif msg.tp == web.MsgType.close:
break
return ws
async def init(loop, fd):
app = web.Application(loop=loop)
app.router.add_route('GET', '/echo', wshandler)
app.router.add_route('GET', '/{name}', handle)
srv = await loop.create_server(app.make_handler(),
sock=socket.fromfd(fd, socket.AF_INET, socket.SOCK_STREAM))
print("asyncio server started on uWSGI {0}".format(uwsgi.version))
return srv
def destroy():
print("destroy worker {0}".format(uwsgi.worker_id()))
sys.exit(0)
def graceful_reload():
print("graceful reload for worker {0}".format(uwsgi.worker_id()))
# TODO do somethign meaningful
sys.exit(0)
loop = asyncio.get_event_loop()
loop.add_signal_handler(signal.SIGINT, destroy)
loop.add_signal_handler(signal.SIGHUP, graceful_reload)
# spawn a handler for every uWSGI socket
for fd in uwsgi.sockets:
loop.run_until_complete(init(loop, fd))
uwsgi.accepting()
loop.run_forever()
在这个例子中 (来自官方的aiohttp文档),我们看到uwsgi.sockets列表 (保存uWSGI socket文件描述符列表),以及对SIGINT和SIGHUP的覆盖,以支持重新加载 (SIGHUP应该调整以支持等待所有的入队请求)
调用 uwsgi.accepting()
以通知master,worker正在接收请求,这对于touch-chain-reload正常工作是必须的。
应该扩展该脚本,在每个请求后调用uwsgi.log(...),以及(最后)更新一些度量
词汇表¶
- harakiri
- uWSGI的一个特性,中止那些过长时间处理请求的worker。使用选项的
harakiri
族来配置。每一个会耗费比harakiri timeout指定的秒数长的请求都将会被丢弃,而对应的worker会被回收。 - master
- uWSGI内置的预派生+线程 多worker管理模式,通过打开
master
开关激活。对于所有实际的服务部署,不 使用master模式,真的不是个好主意。
uWSGI第三方插件¶
以下插件 (除非另有特殊说明)非商业支持。
你可以随意通过发送pull请求给 uwsgi-docs
项目来添加你的插件到该列表。
uwsgi-wstcp¶
- 许可: MIT
- 作者: unbit
- 网站: https://github.com/unbit/uwsgi-wstcp
映射websocket到TCP连接 (适用于通过javascript的代理)。
uwsgi-pgnotify¶
- 许可: MIT
- 作者: unbit
- 网站: https://github.com/unbit/uwsgi-pgnotify
将PostgreSQL通知系统和uWSGI信号框架集成在一起。
uwsgi-eventfd¶
- 许可: MIT
- 作者: unbit
- 网站: https://github.com/unbit/uwsgi-eventfd
允许监控eventfd()对象 (例如由cgroup系统发送的事件)。
uwsgi-console-broadcast¶
- 许可: MIT
- 作者: unbit
- 网站: https://github.com/unbit/uwsgi-console-broadcast
公开用于发送广播消息给用户终端的钩子。
uwsgi-alarm-chain¶
- 许可: MIT
- 作者: unbit
- 网站: https://github.com/unbit/uwsgi-alarm-chain
虚拟告警处理器,将多个告警组合成一个。
uwsgi-consul¶
- 许可: MIT
- 作者: unbit, ultrabug
- 网站: https://github.com/unbit/uwsgi-consul
与consul代理集成 (consul.io)
uwsgi-influxdb¶
- 许可: MIT
- 作者: unbit
- 网站: https://github.com/unbit/uwsgi-influxdb
允许发送度量给InfluxDB时间序列数据库。
uwsgi-bonjour¶
- 许可: MIT
- 作者: unbit, 20tab
- 网站: https://github.com/unbit/uwsgi-bonjour
自动在OSX的Bonjour子系统中注册域名。
uwsgi-datadog¶
- 许可: MIT
- 作者: unbit
- 网站: https://github.com/unbit/uwsgi-datadog
自动发送度量给Datadog (https://www.datadoghq.com/)。
uwsgi-docker¶
- 许可: MIT
- 作者: unbit
- 网站: https://github.com/unbit/uwsgi-docker
允许docker化的 (https://docker.io) vassals。
教程¶
uWSGI缓存烹饪指南¶
这是使用 uWSGI内部路由, uWSGI缓存框架 和 uWSGI转换 的各种缓存技术的烹饪指南。
例子假设使用模块化uWSGI构建。如果你正使用单片构建,那么你可以忽略’plugins’选项。
菜谱是在uWSGI 1.9.7之上测试的。较老的版本可能不能用。
我们开始吧¶
这是一个简单的perl/PSGI Dancer应用,我们部署在一个http-socket上,使用4个进程。
use Dancer;
get '/' => sub {
"Hello World!"
};
dance;
这是uWSGI配置。注意log-micros指令。uWSGI内存中缓存的目标是在不到1毫秒的时间内生成一个响应 (是哒,这是真的),所以我们想让响应时间以微秒记录 (一毫秒的千分之一)。
[uwsgi]
; load the PSGI plugin as the default one
plugins = 0:psgi
; load the Dancer app
psgi = myapp.pl
; enable the master process
master = true
; spawn 4 processes
processes = 4
; bind an http socket to port 9090
http-socket = :9090
; log response time with microseconds resolution
log-micros = true
在你的终端中运行uWSGI实例,然后对其进行一堆请求。
curl -D /dev/stdout http://localhost:9090/
如果一切顺利,你应该会在你的uWSGI日志中看到类似的东东:
[pid: 26586|app: 0|req: 1/1] 192.168.173.14 () {24 vars in 327 bytes} [Wed Apr 17 09:06:58 2013] GET / => generated 12 bytes in 3497 micros (HTTP/1.1 200) 4 headers in 126 bytes (0 switches on core 0)
[pid: 26586|app: 0|req: 2/2] 192.168.173.14 () {24 vars in 327 bytes} [Wed Apr 17 09:07:14 2013] GET / => generated 12 bytes in 1134 micros (HTTP/1.1 200) 4 headers in 126 bytes (0 switches on core 0)
[pid: 26586|app: 0|req: 3/3] 192.168.173.14 () {24 vars in 327 bytes} [Wed Apr 17 09:07:16 2013] GET / => generated 12 bytes in 1249 micros (HTTP/1.1 200) 4 headers in 126 bytes (0 switches on core 0)
[pid: 26586|app: 0|req: 4/4] 192.168.173.14 () {24 vars in 327 bytes} [Wed Apr 17 09:07:17 2013] GET / => generated 12 bytes in 953 micros (HTTP/1.1 200) 4 headers in 126 bytes (0 switches on core 0)
[pid: 26586|app: 0|req: 5/5] 192.168.173.14 () {24 vars in 327 bytes} [Wed Apr 17 09:07:18 2013] GET / => generated 12 bytes in 1016 micros (HTTP/1.1 200) 4 headers in 126 bytes (0 switches on core 0)
而cURL将会返回:
HTTP/1.1 200 OK
Server: Perl Dancer 1.3112
Content-Length: 12
Content-Type: text/html
X-Powered-By: Perl Dancer 1.3112
Hello World!
一个进程上的第一个请求花费大概3毫秒 (这是正常的,因为对于第一个请求,会执行大量的代码),但接下来的请求则花费大概1毫秒。
现在,我们想要将响应存储在uWSGI缓存中。
第一份菜单¶
我们首先创建一个uWSGI缓存,命名为’mycache’,它带有100个槽,每个槽64 KiB (新选项位于配置的尾部),而对于每个对’/’的请求,我们在其中搜索一个名为’myhome’的特定的项。
这次,我们也加载 router_cache
插件 (虽然在单片服务器上,是默认内建它的)。
[uwsgi]
; load the PSGI plugin as the default one
plugins = 0:psgi,router_cache
; load the Dancer app
psgi = myapp.pl
; enable the master process
master = true
; spawn 4 processes
processes = 4
; bind an http socket to port 9090
http-socket = :9090
; log response time with microseconds resolution
log-micros = true
; create a cache with 100 items (default size per-item is 64k)
cache2 = name=mycache,items=100
; at each request for / check for a 'myhome' item in the 'mycache' cache
; 'route' apply a regexp to the PATH_INFO request var
route = ^/$ cache:key=myhome,name=mycache
重启uWSGI,然后用cURL重新运行前面的恶测试。忧伤的是,没有任何改变。为什么?
因为你并没有指示uWSGI去将插件响应存储到缓存中。你需要使用 cachestore
路由动作……
[uwsgi]
; load the PSGI plugin as the default one
plugins = 0:psgi,router_cache
; load the Dancer app
psgi = myapp.pl
; enable the master process
master = true
; spawn 4 processes
processes = 4
; bind an http socket to port 9090
http-socket = :9090
; log response time with microseconds resolution
log-micros = true
; create a cache with 100 items (default size per-item is 64k)
cache2 = name=mycache,items=100
; at each request for / check for a 'myhome' item in the 'mycache' cache
; 'route' apply a regexp to the PATH_INFO request var
route = ^/$ cache:key=myhome,name=mycache
; store each successful request (200 http status code) for '/' in the 'myhome' item
route = ^/$ cachestore:key=myhome,name=mycache
现在,重新运行测试,而你应该看到请求降到大概100-300微秒到范围内。增益取决于多种因素,但是你应该在响应时间上获得至少60%的增益。
日志行报告-1作为app id:
[pid: 26703|app: -1|req: -1/2] 192.168.173.14 () {24 vars in 327 bytes} [Wed Apr 17 09:24:52 2013] GET / => generated 12 bytes in 122 micros (HTTP/1.1 200) 2 headers in 64 bytes (0 switches on core 0)
这是因为,当从缓存提供响应的时候,并未碰到你的应用/插件 (在这种情况下,不涉及任何perl调用)。
你也会注意到更少的响应头:
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 12
Hello World!
这是因为只有响应体会被缓存。默认情况下,生成的响应会被设置为text/html,但是可以改动它,或者让MIME类型引擎为你做这个工作 (见下文)。
将它们统统都缓存起来!!!¶
我们想要缓存我们所有的请求。它们中的一些返回图像和css,而其他一些则总是返回text/html
[uwsgi]
; load the PSGI plugin as the default one
plugins = 0:psgi,router_cache
; load the Dancer app
psgi = myapp.pl
; enable the master process
master = true
; spawn 4 processes
processes = 4
; bind an http socket to port 9090
http-socket = :9090
; log response time with microseconds resolution
log-micros = true
; create a cache with 100 items (default size per-item is 64k)
cache2 = name=mycache,items=100
; load the mime types engine
mime-file = /etc/mime.types
; at each request starting with /img check it in the cache (use mime types engine for the content type)
route = ^/img/(.+) cache:key=/img/$1,name=mycache,mime=1
; at each request ending with .css check it in the cache
route = \.css$ cache:key=${REQUEST_URI},name=mycache,content_type=text/css
; fallback to text/html all of the others request
route = .* cache:key=${REQUEST_URI},name=mycache
; store each successful request (200 http status code) in the 'mycache' cache using the REQUEST_URI as key
route = .* cachestore:key=${REQUEST_URI},name=mycache
多缓存¶
你或许/需要存储项到不同的缓存中。我们可以修改前面的菜谱来为图像、css和html响应使用3个不同的缓存。
[uwsgi]
; load the PSGI plugin as the default one
plugins = 0:psgi,router_cache
; load the Dancer app
psgi = myapp.pl
; enable the master process
master = true
; spawn 4 processes
processes = 4
; bind an http socket to port 9090
http-socket = :9090
; log response time with microseconds resolution
log-micros = true
; create a cache with 100 items (default size per-item is 64k)
cache2 = name=mycache,items=100
; create a cache for images with dynamic size (images can be big, so do not waste memory)
cache2 = name=images,items=20,bitmap=1,blocks=100
; a cache for css (20k per-item is more than enough)
cache2 = name=stylesheets,items=30,blocksize=20000
; load the mime types engine
mime-file = /etc/mime.types
; at each request starting with /img check it in the 'images' cache (use mime types engine for the content type)
route = ^/img/(.+) cache:key=/img/$1,name=images,mime=1
; at each request ending with .css check it in the 'stylesheets' cache
route = \.css$ cache:key=${REQUEST_URI},name=stylesheets,content_type=text/css
; fallback to text/html all of the others request
route = .* cache:key=${REQUEST_URI},name=mycache
; store each successful request (200 http status code) in the 'mycache' cache using the REQUEST_URI as key
route = .* cachestore:key=${REQUEST_URI},name=mycache
; store images and stylesheets in the corresponding caches
route = ^/img/ cachestore:key=${REQUEST_URI},name=images
route = ^/css/ cachestore:key=${REQUEST_URI},name=stylesheets
重要的是,每个匹配上的’cachestore’将会重写前一个。因此,我们添加.*作为第一条规则。
更激进点,Expires HTTP头部¶
你可以为每个缓存项设置过期时间。如果一个项拥有一个过期时间,那么它将会被转换成HTTP Expires头部。这意味着,一旦你发送了一个缓存项给浏览器,它将不会再请求这个项,直到过期!
我们使用前一个菜谱,简单添加不同的过期时间给项。
[uwsgi]
; load the PSGI plugin as the default one
plugins = 0:psgi,router_cache
; load the Dancer app
psgi = myapp.pl
; enable the master process
master = true
; spawn 4 processes
processes = 4
; bind an http socket to port 9090
http-socket = :9090
; log response time with microseconds resolution
log-micros = true
; create a cache with 100 items (default size per-item is 64k)
cache2 = name=mycache,items=100
; create a cache for images with dynamic size (images can be big, so do not waste memory)
cache2 = name=images,items=20,bitmap=1,blocks=100
; a cache for css (20k per-item is more than enough)
cache2 = name=stylesheets,items=30,blocksize=20000
; load the mime types engine
mime-file = /etc/mime.types
; at each request starting with /img check it in the 'images' cache (use mime types engine for the content type)
route = ^/img/(.+) cache:key=/img/$1,name=images,mime=1
; at each request ending with .css check it in the 'stylesheets' cache
route = \.css$ cache:key=${REQUEST_URI},name=stylesheets,content_type=text/css
; fallback to text/html all of the others request
route = .* cache:key=${REQUEST_URI},name=mycache
; store each successful request (200 http status code) in the 'mycache' cache using the REQUEST_URI as key
route = .* cachestore:key=${REQUEST_URI},name=mycache,expires=60
; store images and stylesheets in the corresponding caches
route = ^/img/ cachestore:key=${REQUEST_URI},name=images,expires=3600
route = ^/css/ cachestore:key=${REQUEST_URI},name=stylesheets,expires=3600
图像和样式表会被缓存1个小时,而html响应会被缓存1分钟
存储一个对象的GZIP变体¶
回到第一个菜谱。我们或许想用存储一个响应的两份拷贝。”干净的”一份,和gzip压缩过的一个,后者用于支持gzip编码的客户端。
要启用gzip拷贝,你只需要为这个项选择一个名字,然后将其当成cachestore动作的‘gzip’选项传递。
然后检查HTTP_ACCEPT_ENCODING请求头部。如果它包含’gzip’一词,那么你可以发送给它gzip变体。
[uwsgi]
; load the PSGI plugin as the default one
plugins = 0:psgi,router_cache
; load the Dancer app
psgi = myapp.pl
; enable the master process
master = true
; spawn 4 processes
processes = 4
; bind an http socket to port 9090
http-socket = :9090
; log response time with microseconds resolution
log-micros = true
; create a cache with 100 items (default size per-item is 64k)
cache2 = name=mycache,items=100
; if the client support GZIP give it the gzip body
route-if = contains:${HTTP_ACCEPT_ENCODING};gzip cache:key=gzipped_myhome,name=mycache,content_encoding=gzip
; else give it the clear version
route = ^/$ cache:key=myhome,name=mycache
; store each successful request (200 http status code) for '/' in the 'myhome' item in gzip too
route = ^/$ cachestore:key=myhome,gzip=gzipped_myhome,name=mycache
将静态文件存储到缓存中,以便提供快速服务¶
你可以在服务器启动的时候用静态文件填充uWSGI缓存,以便提供快速服务。选项–load-file-in-cache是对于这项工作的正确工具
[uwsgi]
plugins = 0:notfound,router_cache
http-socket = :9090
cache2 = name=files,bitmap=1,items=1000,blocksize=10000,blocks=2000
load-file-in-cache = files /usr/share/doc/socat/index.html
route-run = cache:key=${REQUEST_URI},name=files
你可以指定所有你需要的–load-file-in-cache指令,但是一个更好的方法可能是
[uwsgi]
plugins = router_cache
http-socket = :9090
cache2 = name=files,bitmap=1,items=1000,blocksize=10000,blocks=2000
for-glob = /usr/share/doc/socat/*.html
load-file-in-cache = files %(_)
endfor =
route-run = cache:key=${REQUEST_URI},name=files
这会存储所有的html文件到/usr/share/doc/socat中。
存储项,并把路径当成键。
当请求一个不存在的项时,会关闭连接,而你应该会得到一个丑陋的
-- unavailable modifier requested: 0 --
这是因为内部路由系统无法管理请求,并且没有能够管理这个请求的请求插件。
你可以使用简单的“notfound”插件(它将总是返回404)来构建一个更好的基础设施
[uwsgi]
plugins = 0:notfound,router_cache
http-socket = :9090
cache2 = name=files,bitmap=1,items=1000,blocksize=10000,blocks=2000
for-glob = /usr/share/doc/socat/*.html
load-file-in-cache = files %(_)
endfor =
route-run = cache:key=${REQUEST_URI},name=files
你也可以使用–load-file-in-cache-gzip来将文件作为gzip存储到缓存中
这个选项不允许设置缓存项到名字,因此,要带以及不带gzip支持来支持客户端,我们可以使用2个不同的缓存
[uwsgi]
plugins = 0:notfound,router_cache
http-socket = :9090
cache2 = name=files,bitmap=1,items=1000,blocksize=10000,blocks=2000
cache2 = name=compressedfiles,bitmap=1,items=1000,blocksize=10000,blocks=2000
for-glob = /usr/share/doc/socat/*.html
load-file-in-cache = files %(_)
load-file-in-cache-gzip = compressedfiles %(_)
endfor =
; take the item from the compressed cache
route-if = contains:${HTTP_ACCEPT_ENCODING};gzip cache:key=${REQUEST_URI},name=compressedfiles,content_encoding=gzip
; fallback to the uncompressed one
route-run = cache:key=${REQUEST_URI},name=files
为已鉴权用户缓存¶
如果你通过http basic鉴权来鉴权用户,那么你可以使用${REMOTE_USER}请求变量来为每个区分缓存:
[uwsgi]
; load the PSGI plugin as the default one
plugins = 0:psgi,router_cache
; load the Dancer app
psgi = myapp.pl
; enable the master process
master = true
; spawn 4 processes
processes = 4
; bind an http socket to port 9090
http-socket = :9090
; log response time with microseconds resolution
log-micros = true
; create a cache with 100 items (default size per-item is 64k)
cache2 = name=mycache,items=100
; check if the user is authenticated
route-if-not = empty:${REMOTE_USER} goto:cacheme
route-run = break:
; the following rules are executed only if REMOTE_USER is defined
route-label = cacheme
route = ^/$ cache:key=myhome_for_${REMOTE_USER},name=mycache
; store each successful request (200 http status code) for '/'
route = ^/$ cachestore:key=myhome_for_${REMOTE_USER},name=mycache
基于Cookie的鉴权一般更复杂,但是绝大多数的时间,session id会作为cookie传递。
你或许想要将这个session_id作为键使用
[uwsgi]
; load the PHP plugin as the default one
plugins = 0:php,router_cache
; enable the master process
master = true
; spawn 4 processes
processes = 4
; bind an http socket to port 9090
http-socket = :9090
; log response time with microseconds resolution
log-micros = true
; create a cache with 100 items (default size per-item is 64k)
cache2 = name=mycache,items=100
; check if the user is authenticated
route-if-not = empty:${cookie[PHPSESSID]} goto:cacheme
route-run = break:
; the following rules are executed only if the PHPSESSID cookie is defined
route-label = cacheme
route = ^/$ cache:key=myhome_for_${cookie[PHPSESSID]},name=mycache
; store each successful request (200 http status code) for '/'
route = ^/$ cachestore:key=myhome_for_${cookie[PHPSESSID]},name=mycache
显然,恶意用户可以构建一个假的session id,然后也许会装满你的缓存。你应该总是检查这个session id。没有单个(好)方法,但是对于基于文件的php会话的一个好例子是下面这个:
[uwsgi]
; load the PHP plugin as the default one
plugins = 0:php,router_cache
; enable the master process
master = true
; spawn 4 processes
processes = 4
; bind an http socket to port 9090
http-socket = :9090
; log response time with microseconds resolution
log-micros = true
; create a cache with 100 items (default size per-item is 64k)
cache2 = name=mycache,items=100
; check if the user is authenticated
route-if-not = empty:${cookie[PHPSESSID]} goto:cacheme
route-run = break:
; the following rules are executed only if the PHPSESSID cookie is defined
route-label = cacheme
; stop if the session file does not exist
route-if-not = isfile:/var/lib/php5/sessions/sess_${cookie[PHPSESSID]} break:
route = ^/$ cache:key=myhome_for_${cookie[PHPSESSID]},name=mycache
; store each successful request (200 http status code) for '/'
route = ^/$ cachestore:key=myhome_for_${cookie[PHPSESSID]},name=mycache
缓存到文件¶
有时候,你想要存储静态文件,而不是缓存在内存中。
transformation_tofile插件让你在文件中存储响应:
[uwsgi]
; load the PHP plugin as the default one
plugins = 0:psgi,transformation_tofile,router_static
; load the Dancer app
psgi = myapp.pl
; enable the master process
master = true
; spawn 4 processes
processes = 4
; bind an http socket to port 9090
http-socket = :9090
; log response time with microseconds resolution
log-micros = true
; check if a file exists
route-if = isfile:/var/www/cache/${hex[PATH_INFO]}.html static:/var/www/cache/${hex[PATH_INFO]}.html
; otherwise store the response in it
route-run = tofile:/var/www/cache/${hex[PATH_INFO]}.html
hex[]路由变量接收一个请求变量内容,然后以十六进制对其进行编码。因为PATH_INFO倾向于包含/,因此它是比存储完整的路径名(或者使用其他编码方案,例如也可以包含斜杠的base64)更好的方法。
使用uWSGI和nginx来设置Django和你的web服务器¶
本教程针对那些想要设置一个生产web服务器的Django用户。它介绍了设置Django以使得其与uWSGI和nginx工作良好的必要步骤。它涵盖了所有三个组成部分,提供了一个web应用和服务器软件的完整栈。
Django 是一个高层次的Python Web框架,鼓励快速开发和干净实用的设计。
nginx (发音为 engine-x) 是一个免费开源并且高性能的HTTP服务器和反向代理,还是一个IMAP/POP3代理服务器。
本教程的一些注释¶
注意
这是一个 教程 。它并不打算提供一个参考指南,对于部署主题,不要想着有一个详尽的参考。
对于Django部署而言,nginx和uWSGI是不错的选择,但它们并非唯一的选择,也不是“官方”选择。对于它们两个,都有不错的替代品,因此鼓励你去详细研究一下。
我们这里部署Django的方式是种不错的方式,但它 不是 唯一 的方式; 对于某些目的,它甚至也许不是最好的方式。
然而,它是一种可靠而简单的方式,而这里所涉及的材料将会向你介绍无论你用什么软件来部署Django都会熟悉的概念和过程。通过为你提供一个可用的步骤,以及向你预演完成此目标的必须步骤,它将会为你提供探索其他做到这点的方法的基础。
注意
本教程对你所使用的系统做了一些假设。
假设你正使用类Unix系统,并且它有一个类似于包管理器的功能。然而,如果你需要问类似于“那么在Mac OS X上的等价物是啥呢”这样的问题,那么你将能够非常简单地找到帮助。
虽然这个教程假设你使用Django 1.4或更高的版本,这将会自动在你的新工程里创建一个wsgi模块,但是,这些指示对低于Django 1.4的版本同样适用。虽然,你需要自己获得Django wsgi模块,并且你可能会发现Django工厂目录结构有点不一样。
概念¶
一个web服务器面对的是外部世界。它能直接从文件系统提供文件 (HTML, 图像, CSS等等)。然而,它无法 *直接*与Django应用通信;它需要借助一些工具的帮助,这些东西会运行运用,接收来自web客户端(例如浏览器)的请求,然后返回响应。
一个Web服务器网关接口(Web Server Gateway Interface) - WSGI - 就是干这活的。 WSGI 是一种Python标准。
uWSGI是一种WSGI实现。在这个教程中,我们将设置uWSGI,让它创建一个Unix socket,并且通过WSGI协议提供响应到web服务器。最后,我们完整的组件栈看起来将是这样的:
the web client <-> the web server <-> the socket <-> uwsgi <-> Django
在你开始设置uWSGI之前¶
virtualenv¶
确保你正处在用来安装所需软件的虚拟机中 (稍后,我们将描述如何安装一个系统范围的uwsgi):
virtualenv uwsgi-tutorial
cd uwsgi-tutorial
source bin/activate
Django¶
将Django装到你的虚拟机中,创建一个新的项目,然后 cd
到该项目中:
pip install Django
django-admin.py startproject mysite
cd mysite
关于域名和端口¶
在这个教程中,我们将假设你的域名为 example.com
。用你自己的FQDN或者IP地址来代替。
从头到尾,我们将使用8000端口作为web服务器的公开端口,就像Django runserver默认的那样。当然,你可以使用任何你想要的端口,但是我已经选了这个,因此,它不会与web服务器可能已经选择的任何端口冲突。
基本的uWSGI安装和配置¶
把uWSGI安装到你的virtualenv中¶
pip install uwsgi
当然,有其他安装uWSGI的方式,但这种方式如其他方式一样棒。记住,你将需要安装Python开发包。对于Debian,或者Debian衍生系统,例如Ubuntu,你需要安装的是 pythonX.Y-dev
,其中,X.Y是你Python的版本。
基础测试¶
创建一个名为 test.py
文件:
# test.py
def application(env, start_response):
start_response('200 OK', [('Content-Type','text/html')])
return [b"Hello World"] # python3
#return ["Hello World"] # python2
注解
需要考虑到,对于Python 3,需要 bytes()
。
运行uWSGI:
uwsgi --http :8000 --wsgi-file test.py
选项表示:
http :8000
: 使用http协议,端口8000wsgi-file test.py
: 加载指定的文件,test.py
当浏览器访问8000端口时,这将直接提供一个’hello world’消息。 访问:
http://example.com:8000
来看一看。如果是这样,那么意味着以下的组件栈正常:
the web client <-> uWSGI <-> Python
测试你的Django项目¶
现在,我们想让uWSGI做同样的事,但是返回一个Django站点而不是
test.py
模块。
如果你还没有这样做,那么请确保你的 mysite
项目实际上正常工作:
python manage.py runserver 0.0.0.0:8000
而如果正常,则使用uWSGI来运行它:
uwsgi --http :8000 --module mysite.wsgi
module mysite.wsgi
: 加载指定的wsgi模块
将你的浏览器指向该服务器;如果站点出现,那么意味着uWSGI可以为你虚拟环境中的Django应用服务,而这个栈工作正常:
the web client <-> uWSGI <-> Django
现在,通常我们不会让浏览器直接与uWSGI通信。那是web服务器的工作,这是个穿针引线的活。
基本的nginx¶
安装nginx¶
sudo apt-get install nginx
sudo /etc/init.d/nginx start # start nginx
现在,通过在一个web浏览器上通过端口80访问它,来检查nginx是否正常 - 你应该会从nginx获得一个消息:”Welcome to nginx!”. 那意味着整个栈的这些模块都能一起正常工作:
the web client <-> the web server
如果有其他的东东已经提供端口80的服务了,并且你想要在那里使用nginx,那么你将必须重新配置nginx来提供另一个端口的服务。但是,在这个教程中,我们将使用端口8000。
为你的站点配置nginx¶
你会需要 uwsgi_params
文件,可用在uWSGI发行版本的 nginx
目录下,或者从
https://github.com/nginx/nginx/blob/master/conf/uwsgi_params
找到。
将其拷贝到你的项目目录中。一会儿,我们将告诉nginx引用它。
现在,创建一个名为mysite_nginx.conf的文件,然后将这个写入到它里面:
# mysite_nginx.conf
# the upstream component nginx needs to connect to
upstream django {
# server unix:///path/to/your/mysite/mysite.sock; # for a file socket
server 127.0.0.1:8001; # for a web port socket (we'll use this first)
}
# configuration of the server
server {
# the port your site will be served on
listen 8000;
# the domain name it will serve for
server_name .example.com; # substitute your machine's IP address or FQDN
charset utf-8;
# max upload size
client_max_body_size 75M; # adjust to taste
# Django media
location /media {
alias /path/to/your/mysite/media; # your Django project's media files - amend as required
}
location /static {
alias /path/to/your/mysite/static; # your Django project's static files - amend as required
}
# Finally, send all non-media requests to the Django server.
location / {
uwsgi_pass django;
include /path/to/your/mysite/uwsgi_params; # the uwsgi_params file you installed
}
}
这个配置文件告诉nginx提供来自文件系统的媒体和静态文件,以及处理那些需要Django干预的请求。对于一个大型部署,让一台服务器处理静态/媒体文件,让另一台处理Django应用,被认为是一种很好的做法,但是现在,这样就好了。
将这个文件链接到/etc/nginx/sites-enabled,这样nginx就可以看到它了:
sudo ln -s ~/path/to/your/mysite/mysite_nginx.conf /etc/nginx/sites-enabled/
部署静态文件¶
在运行nginx之前,你必须收集所有的Django静态文件到静态文件夹里。首先,必须编辑mysite/settings.py,添加:
STATIC_ROOT = os.path.join(BASE_DIR, "static/")
然后运行
python manage.py collectstatic
基本的nginx测试¶
重启nginx:
sudo /etc/init.d/nginx restart
要检查是否正确的提供了媒体文件服务,添加一个名为
media.png
的图像到 /path/to/your/project/project/media directory
中,然后访问http://example.com:8000/media/media.png - 如果这能正常工作,那么至少你知道nginx正在正确的提供文件服务。
值得不仅仅是重启nginx,而是实际停止然后再次启动它,这将会通知是否有问题,以及问题在哪里。
nginx和uWSGI以及test.py¶
让nginx对 test.py
应用说句”hello world”吧。
uwsgi --socket :8001 --wsgi-file test.py
这几乎与之前相同,除了这次有一个选项不同:
socket :8001
: 使用uwsgi协议,端口为8001
同时,已经配置了nginx在那个端口与uWSGI通信,而对外使用8000端口。访问:
来检查。而这是我们的栈:
the web client <-> the web server <-> the socket <-> uWSGI <-> Python
同时,你可以试着看看在http://example.com:8001的uswgi输出 - 但很有可能它不会正常工作,因为你的浏览器使用http,而不是uWSGI,但你应该能够在终端上看到来自uWSGI的输出。
使用Unix socket而不是端口¶
目前,我们使用了一个TCP端口socket,因为它简单些,但事实上,使用Unix socket会比端口更好 - 开销更少。
编辑 mysite_nginx.conf
, 修改它以匹配:
server unix:///path/to/your/mysite/mysite.sock; # for a file socket
# server 127.0.0.1:8001; # for a web port socket (we'll use this first)
然后重启nginx.
再次运行uWSGI:
uwsgi --socket mysite.sock --wsgi-file test.py
这次, socket
选项告诉uWSGI使用哪个文件。
在浏览器中尝试访问http://example.com:8000/。
如果那不行¶
检查nginx错误日志(/var/log/nginx/error.log)。如果你看到像这样的信息:
connect() to unix:///path/to/your/mysite/mysite.sock failed (13: Permission
denied)
那么可能你需要管理这个socket上的权限,从而允许nginx使用它。
尝试:
uwsgi --socket mysite.sock --wsgi-file test.py --chmod-socket=666 # (very permissive)
或者:
uwsgi --socket mysite.sock --wsgi-file test.py --chmod-socket=664 # (more sensible)
你可能还必须添加你的用户到nginx的组 (可能是 www-data),反之亦然,这样,nginx可以正确地读取或写入你的socket。
值得保留nginx日志的输出在终端窗口中滚动,这样,在解决问题的时候,你就可以容易的参考它们了。
使用uwsgi和nginx运行Django应用¶
运行我们的Django应用:
uwsgi --socket mysite.sock --module mysite.wsgi --chmod-socket=664
现在,uWSGI和nginx应该不仅仅可以为一个”Hello World”模块服务,还可以为你的Django项目服务。
配置uWSGI以允许.ini文件¶
我们可以将用在uWSGI上的相同的选项放到一个文件中,然后告诉 uWSGI使用该文件运行。这使得管理配置更容易。
创建一个名为 `mysite_uwsgi.ini`
的文件:
# mysite_uwsgi.ini file
[uwsgi]
# Django-related settings
# the base directory (full path)
chdir = /path/to/your/project
# Django's wsgi file
module = project.wsgi
# the virtualenv (full path)
home = /path/to/virtualenv
# process-related settings
# master
master = true
# maximum number of worker processes
processes = 10
# the socket (use the full path to be safe
socket = /path/to/your/project/mysite.sock
# ... with appropriate permissions - may be needed
# chmod-socket = 664
# clear environment on exit
vacuum = true
然后使用这个文件运行uswgi:
uwsgi --ini mysite_uwsgi.ini # the --ini option is used to specify a file
再次,测试Django站点是否如预期工作。
系统级安装uWSGI¶
目前,uWSGI只装在我们的虚拟环境中;出于部署需要,我们将需要让它安装在系统范围中。
停用你的虚拟环境:
deactivate
然后在系统范围中安装uWSGI:
sudo pip install uwsgi
# Or install LTS (long term support).
pip install http://projects.unbit.it/downloads/uwsgi-lts.tar.gz
uWSGI的wiki描述了几种 installation procedures. 在系统级安装 uWSGI之前,值得考虑下要选择哪个版本,以及最合适的安装方法。
再次检查你是否仍然能如之前那样运行uWSGI:
uwsgi --ini mysite_uwsgi.ini # the --ini option is used to specify a file
Emperor模式¶
uWSGI可以运行在’emperor’模式。在这种模式下,它会监控uWSGI配置文件目录,然后为每个它找到的配置文件生成实例 (‘vassals’)。
每当修改了一个配置文件,emperor将会自动重启 vassal.
# create a directory for the vassals
sudo mkdir /etc/uwsgi
sudo mkdir /etc/uwsgi/vassals
# symlink from the default config directory to your config file
sudo ln -s /path/to/your/mysite/mysite_uwsgi.ini /etc/uwsgi/vassals/
# run the emperor
uwsgi --emperor /etc/uwsgi/vassals --uid www-data --gid www-data
你或许需要使用sudo来运行uWSGI:
sudo uwsgi --emperor /etc/uwsgi/vassals --uid www-data --gid www-data
选项表示:
emperor
: 查找vassals (配置文件)的地方uid
: 进程一旦启动后的用户idgid
: 进程一旦启动后的组id
检查站点;它应该在运行。
系统启动时运行uWSGI¶
最后一步是让这一切在系统启动的时候自动发生。
对于许多系统来说,最简单 (如果不是最好的)的方式是使用 rc.local
文件。
编辑 /etc/rc.local
然后在”exit 0”行前添加:
/usr/local/bin/uwsgi --emperor /etc/uwsgi/vassals --uid www-data --gid www-data --daemonize /var/log/uwsgi-emperor.log
应该就这样!
进一步的配置¶
理解这就是一个让你开始的 教程 很重要。你 真的真的 需要读一读nginx和uWSGI文档,并在将部署到生产环境之前学习可用的选项。
nginx和uWSGI都从友好的社区中获益,这些社区都能提供关于配置和使用的宝贵建议。
nginx¶
然而,nginx的一般配置并不在这篇教程的范围之内,对于一个生产网站,你可能会想要让它监听80端口,而不是8000。
你应该也为提供非Django的文件服务配置一个独立的nginx location块。例如,通过uWSGI提供静态文件服务并不有效。相反,直接从Nginx对其提供服务,并且完全绕过uWSGI会更有效。
uWSGI¶
uWSGI支持多种配置方式。见 uWSGI’s documentation 和 examples.
在这篇教程中,已经提到了一些uWSGI选项;对于生产上的部署,其他你应该看一看的选项包括 (通过样例设置列在这里):
env = DJANGO_SETTINGS_MODULE=mysite.settings # set an environment variable
safe-pidfile = /tmp/project-master.pid # create a pidfile
harakiri = 20 # respawn processes taking more than 20 seconds
limit-as = 128 # limit the project to 128 MB
max-requests = 5000 # respawn processes after serving 5000 requests
daemonize = /var/log/uwsgi/yourproject.log # background the process & log
使用uWSGI在Heroku上运行python web应用¶
前提条件:一个Heroku账户 (在cedar平台上),git (本地系统跟上) 以及heroku toolbelt。
注意:需要uWSGI version >= 1.4.6来确保正确运行python应用。较老的版本可能可用,但并不支持。
准备环境¶
在你的本地系统上,为项目准备一个目录:
mkdir uwsgi-heroku
cd uwsgi-heroku
git init .
heroku create
最后一个命令将会创建一个新的heroku应用 (你可以在网页界面上检查它)。
在我们的例子中,我们会运行Werkzeug WSGI testapp,因此,除了uWSGI之外,我们还需要安装werkzeug包。
第一步是创建一个requirements.txt文件,然后用git来跟踪它。
该文件的内容很简单
uwsgi
werkzeug
现在,用git来跟踪
git add requirements.txt
创建uWSGI配置文件¶
现在,可以创建我们的uWSGI配置文件了。基本上,在heroku上可以使用所有特性
[uwsgi]
http-socket = :$(PORT)
master = true
processes = 4
die-on-term = true
module = werkzeug.testapp:test_app
memory-report = true
正如你所看到的,这是一个非常标准的配置。heroku必备的选项只用 –http-socket 和 –die-on-term。
需要第一个来将uWSGI socket绑定到由Heroku系统请求的端口 (通过环境变量PORT导出,我们可以通过$(PORT)访问)
需要第二个(–die-on-term)来修改uWSGI接收到一个SIGTERM时的默认行为 (粗暴的重载,而Heroku期望是关机)
memory-report选项 (因为我们处于内存受限的环境中)是个不错的东东。
记得跟踪这个文件
git add uwsgi.ini
准备第一次commit/push¶
现在,需要最后一步:创建Procfile。
Procfile是一个描述启动哪个命令的文件。一般来说(对于其他部署系统),对于每个由你的应用请求的额外进程,你都将使用它 (例如memcached, redis, celery...),但在uWSGI之下,你可以继续使用它的高级功能来管理它们。
因此,Procfile是需要启动你的uWSGI实例:
web: uwsgi uwsgi.ini
跟踪它:
git add Procfile
最后,提交所有东西:
git commit -a -m "first commit"
以及,push它 (即:部署)到Heroku:
git push heroku master
第一次的适合,将需要一点时间,因为需要准备你的virtualenv以及编译uWSGI。
之后的push将会快得多。
检查你的应用¶
运行 heroku logs
,你将能够访问uWSGI日志。你应该获取所有你熟悉的信息,以及发生问题的情况下的一些最终提示。
使用python的另一个版本¶
Heroku支持不同的python版本。默认情况下 (目前是2013年二月份),允许Python 2.7.3。
如果你需要另一个版本,那么在你的仓库中创建一个runtime.txt,然后在里面写上如下字符串:
python-2.7.2
来使用python 2.7.2
记得在仓库中add/commit它。
每当你的修改python版本,就会构建一个新的uWSGI二进制。
多进程还是多线程?¶
这显然取决于你的应用。但由于我们在一个内存受限的环境中,因此使用线程可以期望获得更好的内存使用。
除此之外,如果你计划将生产应用放在Heroku上,那么确保了解Dynos和它们的代理是如何工作的(这很重要。真的)
异步/绿色线程/协程?¶
像往常一样,不要相信那些让你总是使用某种类型的异步模式(例如gevent)的人。如果你的应用是异步友好型的,那么显然,你可以使用gevent (在近期的uWSGI发布版本中,会默认构建它),但如果你不知道,那么保持使用多进程(或多线程)。
Harakiri¶
如之前所述,如果你计划将生产应用放在heroku上,那么确保了解dynos和它们的代理是如何工作的。基于此,试着总是为你的应用将harakiri参数设置成一个不错的值。 (不要要求默认值,它取决于应用)
静态文件¶
一般来讲,在Heroku上提供静态文件并不是一个好主意 (主要从设计的角度来看)。你当然可以有此需求。在这种情况下,记得使用uWSGI功能,特别是卸载(offloading)是在提供大文件时留出worker的最好方法 (另外,记得必须使用git来跟踪你的静态文件)
自适应进程生成¶
对于Heroku方法,没有好的支持的算法,并且很可能,在这样一个平台上使用一个动态进程数并没有什么意义。
日志记录¶
如果你计划在生产环境上使用heroku,那么记住在一个外部服务器上(有持续存储)发送你的日志(例如,通过udp)。
检查uWSGI可用的记录器。当然,会有一个满足你的需要的。(重视安全性,因为日志会记录明文)。
更新:一个具有crypto特性的udp记录器正在开发中。
告警¶
所有的告警插件应该工作正常
Spooler¶
由于你的应用运行在一个非持久化的文件系统上,因此使用Spooler是个糟糕的主意 (你会很容易丢失任务)。
Mule¶
它们可以正常使用
信号 (定时器、文件监控器、cron……)¶
它们都能用,但不要依赖于cron功能,因为heroku每时每刻都能杀掉/摧毁/重启你的实例。
外部守护进程¶
–attach-daemon 选项及其 –smart 变量可以正常使用。只是记住,你处于一个不稳定的文件系统中,并且你无法任意如你所愿的绑定端口/地址
监控你的应用(高级/hack)¶
尽管Heroku和newrelic服务工作良好,但是你总是需要监控你的uWSGI实例内部。
一般来说,作为客户端,你使用诸如uwsgitop这样的工具启动stats子系统。
你可以简单的添加uwsgitop到你的requirements.txt中
uwsgi
uwsgitop
werkzeug
并在一个TCP端口上启动stats服务器 (unix socket将不能用,因为运行uwsgitop的实例并不在同一个服务器上!!!):
[uwsgi]
http-socket = :$(PORT)
master = true
processes = 4
die-on-term = true
module = werkzeug.testapp:test_app
memory-report = true
stats = :22222
现在,我们有个问题:如果访问我们的实例?
我们需要知道物理运行我们的实例的机器的LAN地址。要完成它,一个原始的技巧是在uWSGI启动的适合运行ifconfig:
[uwsgi]
http-socket = :$(PORT)
master = true
processes = 4
die-on-term = true
module = werkzeug.testapp:test_app
memory-report = true
stats = :22222
exec-pre-app = /sbin/ifconfig eth0
现在,有了 heroku logs
命令,你就可以知道你的stats服务器在哪里了
heroku run uwsgitop 10.x.x.x:22222
将x.x.x修改成发现的地址,然后记住,你不能绑定到端口22222上,因此,相应修改它。
为了监控,值得搞得一团糟吗?如果你在上线之前测试你的应用,那么这是一个好主意,但如果你计划购买更多的dynos,那么一切都变得太复杂,此时最好使用一些heroku相关技术 (如果有的话)
使用uWSGI在Heroku上运行Ruby/Rack¶
前提条件:一个Heroku账户 (在cedar平台上),git (本地系统跟上) 以及heroku toolbelt (或者老的/已弃用的heroku gem)
注意:需要uWSGI version >= 1.4.8来确保正确运行ruby/rack应用。较老的版本可能可用,但并不支持。
准备环境(一个Sinatra应用)¶
在你的本地系统上,为你的sinatra应用准备结构
mkdir uwsgi-heroku
cd uwsgi-heroku
git init .
heroku create --stack cedar
最后一个命令将会创建一个新的heroku应用 (你可以在网页界面上检查它)。
下一步是创建我们的Gemfile (这个文件包含应用所需的gem)
source 'https://rubygems.org'
gem "uwsgi"
gem "sinatra"
现在,需要运行 bundle install
来创建Gemfile.lock文件
用git来跟踪这两个:
git add Gemfile
git add Gemfile.lock
最后,创建一个包含Sinatra样例应用的config.ru文件
require 'sinatra'
get '/hi' do
return "ciao"
end
run Sinatra::Application
并且,跟踪它
git add config.ru
创建uWSGI配置文件¶
现在,准备好创建uWSGI配置了 (我们将使用.ini文件格式,称为uwsgi.ini)。
heroku的最小化步骤如下 (解释请查看文件中的注释)
[uwsgi]
; bind to the heroku required port
http-socket = :$(PORT)
; force the usage of the ruby/rack plugin for every request (7 is the official numbero for ruby/rack)
http-socket-modifier1 = 7
; load the bundler subsystem
rbrequire = bundler/setup
; load the application
rack = config.ru
; when the app receives the TERM signal let's destroy it (instead of brutal reloading)
die-on-term = true
但是,更好的安装将是
[uwsgi]
; bind to the heroku required port
http-socket = :$(PORT)
; force the usage of the ruby/rack plugin for every request (7 is the official numbero for ruby/rack)
http-socket-modifier1 = 7
; load the bundler subsystem
rbrequire = bundler/setup
; load the application
rack = config.ru
; when the app receives the TERM signal let's destroy it (instead of brutal reloading)
die-on-term = true
; enable the master process
master = true
; spawn 4 processes to increase concurrency
processes = 4
; report memory usage after each request
memory-report = true
; reload if the rss memory is higher than 100M
reload-on-rss = 100
跟踪它:
git add uwsgi.ini
部署到heroku¶
需要创建最后一个文件 (Heroku要求的)。它就是Procfile,用来指示Heroku系统为web应用启动哪个进程。
我们想使用uwsgi.ini配置文件来生成uwsgi (通过bundler作为gem安装)
web: bundle exec uwsgi uwsgi.ini
跟踪它:
git add Procfile
提交所有:
git commit -a -m "first attempt"
然后push到heroku:
git push heroku master
如果一切顺利,你将在/hi路径中的应用url下看到你的页面
记得运行 heroku logs
来检查看看是否一切正常。
fork()小白指南¶
uWSGI允许你选择在应用中如何使用fork() syscall。
默认情况下,办法是在master进程中加载进程,然后fork()到worker,这样,worker将会继承master进程的一个拷贝。
这个方法加速了启动,并且可能会消耗更少的内存。真相是你常常(对于ruby垃圾回收工作的方式)会获得更少的内存增益。真正的优势是在性能上,因为应用启动花费的大部分时间是花在了(缓慢地)文件搜索上。使用fork() 早期方法,你可以为worker避免重复一次那个缓慢的过程。
显然,uWSGI的准则是“做任何你需要做的事,如果不能,那这它就是一个uWSGI错误”,因此,如果你的应用不是fork()友好的,那你可以添加 lazy-apps = true
选项,这将会在每个worker中加载你的应用一次。
ruby GC¶
默认情况下,uWSGI在每次请求你后,调用ruby的垃圾收集器。这确保了内存的优化使用 (记住,在Heroku上,你的内存受限) 。你不应该动默认的方式,但如果性能下降,那么你或许想要使用 ruby-gc-freq = n
选项进行调试,这里,n是调用GC后的请求数。
并发性¶
尽管uWSGI支持并发性的大量不同的范例,但是对于大部分的ruby/rack应用来说,建议使用多进程。
基本上,所有流行的ruby框架的依赖于多进程。记住,你的应用受限,因此,生成许多进程会适合你的Heroku dyno。
自uWSGI 1.9.14起,添加了原生ruby 1.9/2.x线程支持。Rails4 (只在生产模式!!) 支持它们:
[uwsgi]
...
; spawn 8 threads per-process
threads = 8
; maps them as ruby threads
rbthreads = true
; do not forget to set production mode for rails4 apps !!!
env = RAILS_ENV=production
...
Harakiri¶
如果你计划将生产应用放在heroku上,那么确保了解dynos和它们的代理是如何工作的。基于此,试着总是为你的应用将harakiri参数设置成一个不错的值。 (不要要求默认值,它取决于应用)
Harakiri,是一个单一的请求在被master摧毁之前可以运行的最长时间
静态文件¶
一般来讲,在Heroku上提供静态文件并不是一个好主意 (主要从设计的角度来看)。你当然可以有此需求。在这种情况下,记得使用uWSGI功能,特别是卸载(offloading)是在提供大文件时留出worker的最好方法 (另外,记得必须使用git来跟踪你的静态文件)
避免在ruby/rack代码中提供静态文件。这将会非常慢(与使用uWSGI功能相比),并且还会让你的worker忙于传输文件
自适应进程生成¶
对于Heroku方法,没有好的支持的算法,并且很可能,在这样一个平台上使用一个动态进程数并没有什么意义。
日志记录¶
如果你计划在生产环境上使用heroku,那么记住在一个外部服务器上(有持续存储)发送你的日志(例如,通过udp)。
检查uWSGI可用的记录器。当然,会有一个满足你的需要的。(重视安全性,因为日志会记录明文)。
更新:一个具有crypto特性的udp记录器正在开发中。
告警¶
所有的告警插件应该工作正常
Spooler¶
由于你的应用运行在一个非持久化的文件系统上,因此使用Spooler是个糟糕的主意 (你会很容易丢失任务)。
Mule¶
它们可以正常使用
信号 (定时器、文件监控器、cron……)¶
它们都能用,但不要依赖于cron功能,因为heroku每时每刻都能杀掉/摧毁/重启你的实例。
外部守护进程¶
–attach-daemon 选项及其 –smart 变量可以正常使用。只是记住,你处于一个不稳定的文件系统中,并且你无法任意如你所愿的绑定端口/地址
为uWSGI的vassal可靠使用FUSE文件系统 (使用Linux)¶
要求:uWSGI 1.9.18, 带FUSE和名字空间支持的Linux内核。
FUSE是一门允许在用户空间实现文件系统的技术 (因此,名字是:Filesystem in Userspace,即用户空间中的文件系统)。 有很多高质量的FUSE文件系统,因此,让你的应用依赖于它们是一种普遍形势。
FUSE文件系统是正常的系统进程,所以就像系统中的任何进程一样,它们会崩溃 (或许也许你不自觉地杀死了它们)。除此之外,如果你托管多个应用,每个都要求一个FUSE挂载点,那么你也许想要避免污染主挂载点名字空间,并且更重要的是,避免系统中存在未使用的挂载点 (例如,一个实例已经被完全移除了,而你不需要它的FUSE挂载点在系统中仍然可用)。
这个教程的目的是配置一个Emperor和一系列的vassal,每个挂载一个FUSE文件系统。
Zip文件系统¶
fuse-zip 是一个FUSE进程,将一个zip文件作为文件系统公开。
我们的目标是把整个应用存储在一个zip归档文件中,并且指示uWSGI将其作为一个文件系统(通过FUSE)挂载在 /app
之下。
Emperor¶
[uwsgi]
emperor = /etc/uwsgi/vassals
emperor-use-clone = fs,pid
这里的技巧是使用Linux名字空间来在一个新的pid和文件名字空间中创建vassal。
第一个 (fs
) 允许由vassal创建的挂载点只对这个vassal有用 (而不会混淆主要系统),而 pid
允许uWSGI master成为这个vassal的“初始”进程 (pid 1)。成为”pid 1”意味着,当你死掉了,你所有的孩子也会死掉。在我们的场景之下 (其中,我们的vassal在启动的时候加载了一个FUSE进程),它意味着当这个vassal被销毁的时候,FUSE进程也会被销毁,同时它的挂载点也会被销毁。
一个Vassal¶
[uwsgi]
uid = user001
gid = user001
; mount FUSE filesystem under /app (but only if it is not a reload)
if-not-reload =
exec-as-user = fuse-zip -r /var/www/app001.zip /app
endif =
http-socket = :9090
psgi = /app/myapp.pl
这里,我们使用 fuse-zip
命令的 -r
选项来获得一个只读挂载。
监控挂载点¶
当前设置的问题是,如果 fuse-zip
进程死掉了,那么实例将不再能够访问 /app
,知道重新生成实例。
uWSGI 1.9.18添加了 --mountpoint-check
选项。它强制master不断地验证指定文件系统。如果它失败了,那么整个实例将会被粗鲁地销毁。由于我们处于Emperor之下,因此vassal被销毁后会立即以一种干净的状态重启 (允许再次启动FUSE挂载点)。
[uwsgi]
uid = user001
gid = user001
; mount FUSE filesystem under /app (but only if it is not a reload)
if-not-reload =
exec-as-user = fuse-zip -r /var/www/app001.zip /app
endif =
http-socket = :9090
psgi = /app/myapp.pl
mountpoint-check = /app
来点重金属:一个CoW rootfs (unionfs-fuse)¶
unionfs-fuse 是一个联合文件系统(union filesystem)的用户空间实现。一个联合文件系统是多个文件系统的堆栈,因此,具有相同名字的目录被合并成单个视图。
联合文件系统不止这些,其中一个最有效的特性是写时拷贝 (COW或者CoW)。启用CoW意味着你将有一个不可变的/只读的挂载点基础,所有对其修改将会指向另一个挂载点。
我们的目标是拥有一个由我们所有客户共享的只读rootfs,以及对每个客户有一个可写挂载点 (配置为CoW),其中,将会存储每个修改。
Emperor¶
可以使用前面的Emperor配置,但是我们需要准备我们的文件系统。
层次将是:
/ufs (where we initially mount our unionfs for each vassal)
/ns
/ns/precise (the shared rootfs, based on Ubuntu Precise Pangolin)
/ns/lucid (an alternative rootfs for old-fashioned customers, based on Ubuntu Lucid Lynx)
/ns/saucy (another shared rootfs, based on Ubuntu Saucy Salamander)
/ns/cow (the customers' writable areas)
/ns/cow/user001
/ns/cow/user002
/ns/cow/userXXX
...
创建我们的rootfs:
debootstrap precise /ns/precise
debootstrap lucid /ns/lucid
debootstrap saucy /ns/saucy
并且在每个中创建 .old_root
目录 (对 pivot_root
是必须的,见下):
mkdir /ns/precise/.old_root
mkdir /ns/lucid/.old_root
mkdir /ns/saucy/.old_root
确保安装所需的库到它们每一个中 (特别是你的语言所需的库)。
在这个rootfs中, uwsgi
二进制文件必须是可执行的,因此你必须花点时间在它上面 (一个好方法是对每个发行版编译一个语言插件,并且将其放公用目录中,例如,每个rootfs可以拥有一个 /opt/uwsgi/plugins/psgi_plugin.so
文件,以此类推)。
一个Vassal¶
这里,事情变得有点复杂了。我们需要加载unionfs进程 (以root用户,因为它必须是我们新的rootfs),然后调用 pivot_root
(Linux上可以用的一个更高级的 chroot
)。
钩子(hook) 是在各种uWSGI启动阶段运行自定义命令(或者函数)的最佳方式。
在我们的例子中,我们将在”pre-jail”阶段运行FUSE进程,然后在”as-root”阶段(在 pivot_root
之后)处理挂载点。
[uwsgi]
; choose the approach that suits you best (plugins loading)
; this will be used for the first run ...
plugins-dir = /ns/precise/opt/uwsgi/plugins
; and this after a reload (where our rootfs is already /ns/precise)
plugins-dir = /opt/uwsgi/plugins
plugin = psgi
; drop privileges
uid = user001
gid = user001
; chdir to / to avoid problems after pivot_root
hook-pre-jail = callret:chdir /
; run unionfs-fuse using chroot (it is required to avoid deadlocks) and cow (we mount it under /ufs)
hook-pre-jail = exec:unionfs-fuse -ocow,chroot=/ns,default_permissions,allow_other /precise=RO:/cow/%(uid)=RW /ufs
; change the rootfs to the unionfs one
; the .old_root directory is where the old rootfs is still available
pivot_root = /ufs /ufs/.old_root
; now we are in the new rootfs and in 'as-root' phase
; remount the /proc filesystem
hook-as-root = mount:proc none /proc
; bind mount the original /dev in the new rootfs (simplifies things a lot)
hook-as-root = mount:none /.old_root/dev /dev bind
; recursively un-mount the old rootfs
hook-as-root = umount:/.old_root rec,detach
; common bind
http-socket = :9090
; load the app (fix it according to your requirements)
psgi = /var/www/myapp.pl
; constantly check for the rootfs (seems odd but is is very useful)
mountpoint-check = /
如果你的应用会试着写入它的文件系统,那么你会看到,在它的 /cow
目录中,所有的已创建/已更新文件都能用。
注释¶
一些FUSE文件系统不会提交写入,直到它们取消挂载。在这样的情况下,在vassal关闭的时候取消挂载是个不错的技巧:
[uwsgi]
; vassal options ...
...
; umount on exit
exec-as-user-atexit = fusermount -u /app
使用RPC和内部路由构建一个动态代理¶
正在进行中 (要求uWSGI 1.9.14,我们使用PyPy作为引擎)
第一步:构建你的映射函数¶
使用hostname作为映射 (你可以使用任何你需要的)
import uwsgi
def my_mapper(hostname):
return "127.0.0.1:3031"
uwsgi.register_rpc('the_mapper', my_mapper)
将其保持为myfuncs.py
第二步:构建一个路由表¶
[uwsgi]
; enable the pypy engine
pypy-home = /opt/pypy
; execute the myfuncs.py file (the 'the_mapper' rpc function will be registered)
pypy-exec = myfuncs.py
; bind to a port
http-socket = :9090
; let's define our routing table
; at every request (route-run execute the action without making check, use it instead of --route .*) run the_mapper passing HTTP_HOST as argument
; and place the result in the MYNODE variable
route-run = rpcvar:MYNODE the_mapper ${HTTP_HOST}
; print the MYNODE variable (just for fun)
route-run = log:${MYNODE}
; proxy the request to the chosen backend node
route-run = http:${MYNODE}
; enable offloading for automagic non-blocking behaviour
; a good value for offloading is the number of cpu cores
offload-threads = 2
使用Metrics子系统,在Ubuntu上构建Graphite¶
本教程将指导你安装一个多应用服务器,其中,每个应用发送度量到一个中央graphite/carbon服务器。
Graphite可以在这里找到: http://graphite.wikidot.com/
uWSGI Metrics子系统的相关文档在这里 度量(Metrics)子系统
本教程假设是在amd64的Ubuntu Saucy (13.10)版本
而对于Graphite,我们将使用Ubuntu官方包,并且将从官方渠道下载和安装uWSGI内核和插件
安装Graphite及其他需要的包¶
sudo apt-get install python-dev ruby-dev bundler build-essential libpcre3-dev graphite-carbon graphite-web
python-dev和ruby-dev是必须的,因为我们想要同时支持WSGI和Rack应用。
pcre开发头文件运行你构建带内部路由支持(你总是想要的东东)的uWSGI
初始化Graphite¶
第一步僵尸启用Carbon服务器。
Graphite项目由三个子系统组成:whisper, carbon和前端
Whisper是一种数据存储格式(类似于rrdtool)
Carbon是一个服务器,用来收集度量并将其存储在whisper文件中 (好吧,它做的东西会更多些,但这是它的主要用途)
而web前端用来可视化根据carbon服务器收集到的数据构建的图表。
要启用carbon服务器,编辑 /etc/default/graphite-carbon
并且设置CARBON_CACHE_ENABLED为true
在启动carbon服务器之前,我们需要建立其搜索索引
仅需运行:
sudo /usr/bin/graphite-build-search-index
然后启动carbon服务器 (在下一次重启的时候,将会自动启动它)
sudo /etc/init.d/carbon-cache start
构建和安装uWSGI¶
下载最新的稳定的uWSGI安装包
wget http://projects.unbit.it/downloads/uwsgi-latest.tar.gz
解压缩并在创建的目录下运行:
python uwsgiconfig.py --build core
这将会构建uWSGI “core”二进制文件。
现在,我们想要构建python, rack和carbon插件:
python uwsgiconfig.py --plugin plugins/python core
python uwsgiconfig.py --plugin plugins/rack core
python uwsgiconfig.py --plugin plugins/carbon core
现在,我们有了 uwsgi
, python_plugin.so
, rack_plugin.so
和 carbon_plugin.so
将它们拷贝到系统目录下:
sudo mkdir /etc/uwsgi
sudo mkdir /usr/lib/uwsgi
sudo cp uwsgi /usr/bin/uwsgi
sudo cp python_plugin.so /usr/lib/uwsgi
sudo cp rack_plugin.so /usr/lib/uwsgi
sudo cp carbon_plugin.so /usr/lib/uwsgi
设置uWSGI Emperor¶
创建一个upstart配置文件,用以启动 uWSGI Emperor —— 多应用部署
# Emperor uWSGI script
description "uWSGI Emperor"
start on runlevel [2345]
stop on runlevel [06]
exec /usr/bin/uwsgi --emperor /etc/uwsgi
将其保存为 /etc/init/emperor.conf
,然后启动Emperor:
start emperor
从现在起,要启动uWSGI实例,仅需把它们的配置文件放到/etc/uwsgi中
生成Graphite web界面¶
在启动graphite web界面 (它是一个Django应用)前,我们需要初始化它的数据库。
运行:
sudo graphite-manage syncdb
这是manage.py的一个标准的django syncdb命令。仅需回答问题来创建一个admin用户即可。
现在,我们准备好创建一个uWSGI vassal了:
[uwsgi]
plugins-dir = /usr/lib/uwsgi
plugins = python
uid = _graphite
gid = _graphite
wsgi-file = /usr/share/graphite-web/graphite.wsgi
http-socket = :8080
将其保存为 /etc/uwsgi/graphite.ini
_graphite 用户 (和组)由graphite ubuntu包创建。我们的uWSGI vassal将会运行在此权限下运行。
web界面将会监听服务器的8080端口,使用HTTP。如果你更喜欢代理,那么仅需修改 http-socket
为 http
,或者将其放在一个诸如nginx这样的完整的web服务器之后 (本教程中并不涵盖此步骤)
生成vassals,用以发送度量给Graphite¶
现在,已经准备好发送应用的度量给carbon/graphite服务器了。
对于/etc/uwsgi中的每一个vassal文件,仅需确保添加以下选项:
[uwsgi]
...
plugins = carbon
enable-metrics = true
carbon-use-metrics = true
carbon-id = %n
carbon = 127.0.0.1:2003
...
``carbon-id`` 给每个度量设置了一个有意义的前缀 (%n自动转换成不带扩展名的vassal文件名)。
``carbon`` 选项设置carbon服务器的地址以接收度量值 (默认情况下,carbon服务器绑定在2003端口,但是你可以通过编辑 ``/etc/carbon/carbon.conf`` 然后重启carbon服务器来修改它)
将Graphiti (基于Ruby/Sinatra) 作为替代前端¶
Graphiti是来自Graphite的一个替代web面板/前端,用Sinatra (一个Ruby/Rack框架)编写。
Graphiti需要redis,因此,确保你的系统中运行着一个redis服务器。
运行:
sudo apt-get install redis-server
就够了
第一步是clone这个graphiti应用 (将其放在任何你想要/需要的地方):
git clone https://github.com/paperlesspost/graphiti.git
然后运行bundler工具 (如果你对ruby世界没信心,那么(我告诉你),它是一个用来管理依赖的工具)
bundle install
注解
如果eventmachine gem安装失败,那么在Gemfile中添加”gem ‘eventmachine’”以作为第一个gem,然后运行bundle update。这会确保安装最新的eventmachine版本
在bundle安装了所有的gem之后,你必须拷贝graphiti样例配置:
cp config/settings.yml.example config/settings.yml
编辑它,设置graphite_base_url为graphite web界面(django那个)运行的url。
现在,我们可以在uWSGI上部署它了
[uwsgi]
plugins-dir = /usr/lib/uwsgi
plugins = rack
chdir = <path_to_graphiti>
rack = config.ru
rbrequire = bundler/setup
http-socket = :9191
uid = _graphite
gid = _graphite
将其保存为 /etc/uwsgi/graphiti.ini
,让Emperor部署它
现在,你可以连接到9191端口来管理你收集的度量了。
与往常一样,你可以随意将实例放在代理之后。
小抄¶
默认情况下,carbon服务器监听公有地址。除非你清楚你正在做什么,否则你应该将其指向一个本地地址 (例如127.0.0.1)
uWSGI导出大量的度量 (更多是有计划的),不要害怕使用它们
应用和carbon服务器之间不具备安全性,任何应用都能写入度量值。如果你托管不信任的应用,那么最好使用其他方法 (例如,给系统中的每个用户一个graphite实例)
对于redis同样适用,如果你允许不受信任的应用,那么从安全的角度来看,一个共享的redis实例绝对不是一个好的选择。
文章¶
序列化accept(), 亦称惊群效应,亦亦称Zeeg难题¶
UNIX世界的一个历史问题是“惊群效应(thundering herd)”。
它是什么呢?
比方说,一个进程绑定到一个网络地址 (它可能是 AF_INET
,
AF_UNIX
或者任意你想要的) ,然后fork自己:
int s = socket(...)
bind(s, ...)
listen(s, ...)
fork()
在多次fork自己之后,每个进程一般将会开始阻塞在 accept()
上
for(;;) {
int client = accept(...);
if (client < 0) continue;
...
}
这个有趣的问题是,在较老的/古典的UNIX上,每当socket上尝试进行一个连接,阻塞在 accept()
上的每个进程的 accept()
都会被唤醒。
只有其中一个进程能够真正接收到这个连接,而剩余的进程将会获得一个无聊的 EAGAIN
。
这导致了大量的CPU周期浪费 (内核调度器必须把控制权交给所有等待那个socket的休眠中的进程)。
当你使用线程来代替进程的时候,这种行为 (出于各种原因) 会被放大 (因此,你有多个阻塞在 accept()
的线程)。
实际解决方法是把一个锁放在 accept()
调用之前,来序列化它的使用:
for(;;) {
lock();
int client = accept(...);
unlock();
if (client < 0) continue;
...
}
对于线程来说,处理锁一般更容易,但是对于进程,你必须利用系统特定的解决方案,或者回退到历史悠久的SysV ipc 子系统 (稍后会有更多关于这个的信息)。
到了近代,绝大多数的UNIX系统都已经进化了,现在,内核(或多或少)确保在一个连接事件上,只有一个进程/线程会被唤醒。
好啦,问题已解决,那我们在谈论什么呢?
select()/poll()/kqueue()/epoll()/...¶
在前1.0时代,uWSGI比目前的形式简单得多 (并且更无趣)。它并没有信号框架,并且不能够监听多个地址;为此,它的循环引擎只在每个进程/线程中调用
accept()
,因此惊群效应 (多亏了现代内核) 并不是一个问题。
进化是有代价的,因此不久,uWSGI进程/线程的标准循环引擎从
for(;;) {
int client = accept(s, ...);
if (client < 0) continue;
...
}
移到了一个更复杂的:
for(;;) {
int interesting_fd = wait_for_fds();
if (fd_need_accept(interesting_fd)) {
int client = accept(interesting_fd, ...);
if (client < 0) continue;
}
else if (fd_is_a_signal(interesting_fd)) {
manage_uwsgi_signal(interesting_fd);
}
...
}
问题现在是 wait_for_fds()
样例函数:它会调用某些例如 select()
, poll()
或者更现代的 epoll()
和
kqueue()
方法。
这些类型的系统调用是文件描述符的“监控器”,并且它们会在所有等待同一个文件描述符的进程/线程中被唤醒。
在你开始指责你的内核开发者之前,你应该知道,这是正确的方法,因为内核并不能知道你是在等待那些文件描述符来调用
accept()
,还是做些更有趣的事。
所以,欢迎再次回到惊群效应。
应用服务器 VS web服务器¶
流行的、久经检验的稳定的多进程参考web服务器是Apache HTTPD.
它在IT演化的几十年中生存了下来,并且仍然是驱动整个互联网的最重要的技术之一。
天生仅多进程的特性,Apache必须始终处理惊群效应问题,并且它们用SysV ipc信号量来解决它。
(注:对于此,Apache真的很智能,当它仅需等待单个文件描述符时,它只调用 accept()
,利用现代内核的反惊群效应策略的优势)
(更新:Apache 2.x甚至允许你选择使用哪个锁技术,包括用于非常古老的系统的flock/fcntl,但是在绝大多数的系统上,当处于多进程模式的时候,它会使用sysv信号里)
甚至在当代的Apache版本上,strace它的一个进程 (绑定到多个接口),你会看到像这样的东东 (它是一个Linux系统):
semop(...); // lock
epoll_wait(...);
accept(...);
semop(...); // unlock
... // manage the request
SysV信号量保护你的epoll_wait免受惊群效应之扰。
所以,另一个问题解决了,这个世界真是一个辣么美好的地方……,但是……
SysV IPC对于应用服务器并不好 :(*
“应用服务器”的定义是非常通用的,在这种情况下,我们指的是,一个或多个有非特性(非root)用户生成的进程,绑定到一个或多个网络地址上,允许自定义的、高度不确定的代码。
即使你对SysV IPC是如何工作的有最小/基本的了解,你都会知道它的每个部件都是系统中的受限资源 (而在现代的BSD中,这些限制被设置成低的离谱的值,PostgreSQL FreeBSD用户对这个问题深有感触)。
仅需在终端允许’ipcs’,来获取你的内核中的分配对象的列表。是哒,在你的内核中。SysV ipc对象是持久化资源,它们需要由用户手动移除。与那些可以分配数以百计的那样的对象,并且填充你的受限SysV IPC内存的相同的用户。
Apache世界中由SysV ipc使用引发的最常见的问题之一是当你粗暴地杀死Apache实例时引发的泄漏 (是哒,你永远不应该这样做,但是如果勇敢/傻得在你的web服务器进程中托管不可靠的PHP应用的话,那么你别无选择)。
要更好地理解它,请生成Apache,然后 killall -9 apache2
。重新生成它,然后运行’ipcs’,你将每次都会获得一个新的信号量对象。你知道问题所在了吧? (给Apache大师:是哒,我知道有hack技巧来避免,但是这是默认的行为)
Apache一般是一个系统服务,由有意识的系统管理员管理,因此,除少数情况外,你可以继续信任它几十年,即使是在它决定使用更多的SysV ipc对象的情况下 :)
可悲的是,你的应用服务器是由不同类型的用户管理的,从最熟练的到那种应该立即换工作的,到那种站点被想要控制你的服务器的白痴破解的。
应用服务器并不危险,用户才是。而应用服务器是由用户运行的。这个世界就是如此的丑陋。
应用服务器开发者是如何解决它的¶
快速回答:他们一般不解决/在乎它
注意:我们谈论的是多进程,我们已经看到多线程是很容易解决的。
提供静态文件或者代理 (一个web服务器的主要活动) 一般是一种快速非阻塞 (在各个观点下非常确定) 的活动。相反,web应用则是更慢更重的方式,因此,即使在中等负载的网站上,休眠进程的数量一般都是低的。
在高负载站点上,你会祈祷空闲进程,而在无负载的站点上,惊群效应问题是完全不相干的(除非你在386上运行你的站点)。
鉴于你通常分配给应用服务器相对较少数量的进程,我们可以说惊群效应不是个问题。
另一个方法是动态进程生成。如果你确定你的应用服务器总是运行最少必须数量的进程,那么你会高度减少惊群效应问题。 (看看–cheaper uWSGI选项家族)
没问题???所以,再次,我们在谈论什么?¶
我们在谈论的是“常见情况”,而对于常见情况,有过多有效选择 (明显,不是uWSGI) ,而我们在谈论的大多数问题是不存在的。
由于uWSGI项目的开始,是🈶️托管公司开发的,其中,“常见情况”并不存在,因此我们很多关注的是极端情况问题,奇怪的设置,以及那些绝大多数的用户绝不需要关心的问题。
除此之外,uWSGI支持只在一般用途的web服务器,例如Apache(我不得不说,Apache可能是唯一一个一般用途的web服务器,因为它以一种相对安全和稳定的方式,允许在其进程空间中基本任何操作),才常见的/可用的操作模式 ,因此大量的与用户不良行为合并的新问题出现了。
uWSGI最具挑战的开发阶段之一是添加多线程。线程是强大的,但是真多很难以正确的方式进行管理。
线程是种比进程便宜的方式,因此,一般来说,你会为你的应用分配数十个线程 (记住,未使用内存就是浪费的内存)。
几十 (或几百) 个等待同组文件描述符的线程把我们带回了惊群效应问题 (除非你所有的线程都在不断地被使用)。
出于这样的理由,当你在uWSGI中启用多线程时,会分配一个pthread互斥锁,在每个线程中序列化epoll()/kqueue()/poll()/select()...使用。
另一个问题解决啦 (并且对于uWSGI来说很奇怪,并不需要使用选项 ;)
但是……
Zeeg难题:带多线程的多进程¶
在2013年6月27日,David Cramer写了一篇有趣的博文 (你可能不同意它的结论,但是现在这没关系,你可以继续安全地痛恨 uWSGI,或者嘲笑其命名选择,又或者是它选项的数目)。
http://cramer.io/2013/06/27/serving-python-web-applications
David面临的问题是,惊群效应如此强大,以致于它的响应时间被它破坏了 (非恒定性能是其测试的主要结果)。
为什么它会发生呢?uWSGI分配的互斥锁不是已经解决了这个问题了吗?
David运行的uWSGI有10个进程,每个进程有10个线程:
uwsgi --processes 10 --threads 10 ...
虽然互斥锁保护单一进程中的每个线程在同个请求上调用 accept()
,但是并没有这样的机制(或者更好的,并不是默认启用它的,见下)保护多进程不受其害,因此给定可用于管理请求的线程数(100),单个进程不可能完全阻塞 (说明:它所有的10歌线程都阻塞在一个请求中),所以,欢迎回到惊群效应。
David是如何解决它的?¶
uWSGI是一个有争议的软件,这并不可耻。有些用户极度痛恨它,而有些则病态地热爱它,但是所有人都同意文档可能更好 ([OT] 当所有的人都同意某事的时候是不错的,但是uwsgi-docs上的pull请求数低得囧囧有神,并且所有都来自相同的人……来吧,帮帮我们!!!)
David使用了一个经验方法,发现它的问题,然后决定通过运行绑定在不同socket上的独立uwsgi进程,并配置nginx在它们之间轮询来解决它。
这是一个非常优雅的方法,但它有一个问题:nginx不能知道发送请求的进程是否所有的线程都处在忙碌状态。这有用,但是是一个次优解。
最好的方法是有一个内部进程锁住 (就像Apache),同时在线程和进程中序列化所有的 accept()
uWSGI文档太糟糕了: –thunder-lock¶
Michael Hood (你也会在David博文的评论中发现他的名字)在前段时间于uWSGI的邮件列表/问题跟踪器中标记了这个问题,他甚至出了一个以 --thunder-lock
选项为结果的初始补丁 (这就是为什么开源更好 ;)
--thunder-lock
自uWSGI 1.4.6起可用,但从未在文档中记录过 (任何形式)
只有那些关注邮件列表 (或者面对具体问题) 的人才知道它。
SysV IPC信号量不好,你如何解决它?¶
进程间锁自uWSGI 0.0.0.0.0.1起就是个问题了,但是我们在项目的第一个公开版本 (in 2009) 中解决了它。
我们基本上检查每个操作系统功能,然后选择它们提供的最好/最快的ipc锁,给我们的代码填充了数十个 #ifdef。
当你启动uWSGI时,你应该可以在它的日志中看到已选择了哪个“锁引擎”。
支持它们中的许多种:
- _PROCESS_SHARED 和 _ROBUST属性的pthread互斥锁 (现代Linux和Solaris)
- 带_PROCESS_SHARED的pthread互斥锁 (较老的Linux)
- OSX自旋锁 (MacOSX, Darwin)
- Posix信号量 (FreeBSD >= 9)
- Windows互斥锁 (Windows/Cygwin)
- SysV IPC信号量 (对所有其他系统回退)
对于uWSGI特有的特性,例如缓存、rpc,都需要它们的使用,而所有那些特性都要求改变共享内存结构 (通过 mmap() + _SHARED分配)
每个引擎彼此间不同,而处理它们已经很痛苦,而(更重要的是)它们有些并不“健壮”。
“健壮”一词是从phread借来的。如果一个锁是”健壮“的,那么意味着如果锁住的进程死掉,那么会释放该锁。
你可能会认为所有的锁引擎都有这个特点,但遗憾的是,只有少数几个可靠工作。
出于这个原因,uWSGI master进程必须分配一个额外的线程 (“死锁”检测器) 来不断地检测映射到死掉的进程的非健壮的未释放锁。
这很痛苦,但是,任何告诉你IPC锁容易的人都应该加入JEDI学校……
uWSGI开发者特么是懦夫¶
David Cramer和Graham Dumpleton (是哒,他是mod_wsgi的作者,但是在很大程度上促成了uWSGI以及其他WSGI服务器的发展,这就是为什么开源更好的另一个理由) 都问为什么当请求多进程+多线程时, --thunder-lock
并不是默认项。
这是一个不错的问题,它有一个简单的回答:我们是只在乎钱的懦夫。
uWSGI完全开源,但是它的开发是由使用它的公司和Unbit.it客户赞助的(以多种方式)。
为一个“常见的”使用(例如多进程+多线程)启用“高风险”特性对我们而言太难了,除此之外,库/内核不兼容的情况(特别是在Linux上)真多很痛苦。
例如,要拥有健壮的pthread互斥锁,你需要一个带有现代glibc的现代内核,但是,常用的发行版 (像centos家族) 混合了较老的内核,具有较新的glibc以及较老的glibc。这导致了不能正确检测对于一个平台而言,哪个是最好的锁引擎,因此,当uwsgiconfig.py脚本不能肯定时,它退到最安全的方式 (例如Linux上的非健壮pthread互斥锁)。
死锁检测器应该让你免于大部分的问题,但是“应该”这一词是关键。在这种代码上编写测试套件(或者甚至是单个单元测试)基本上不可能 (呃,至少对我而言),所以我们不能确保所有都是正确的 (而报告线程错误对于用户,以及熟练的开发者而言都很难,除非你工作在pypy上 ;)
Linux pthread健壮的互斥锁是可靠的,我们“相当”肯定,因此你应该能够在现代Linux系统上99.999999%成功启用 --thunder-lock
,但是,我们更愿意(目前)让用户自觉启用它。
当SysV IPC信号量是一个更好的选择时¶
是哒,有些情况下,SysV IPC信号量比系统特定的特性带给你更好的结果。
Booking.com的Marcin Deranek已经考验uWSGI多月了,并且帮助我们修复极端情况(甚至在锁领域)。
他指出,系统特定的锁引擎倾向于内核调度 (当unlock之后选择哪个进程赢得下一个错的时候) ,而不是轮询分布。
对于他们那种在进程之间等量分布请求会更好的特定需求 (他们通过perl使用uWSGI,因此没有任何线程,但是会生成大量的进程),他们(当前)选择使用”ipcsem” 锁引擎:
uwsgi --lock-engine ipcsem --thunder-lock --processes 100 --psgi ....
(这个时候)有趣的是,你可以很容易测试锁是否运行良好。仅需开始压测服务器,你将会看到请求日志中报告pid是怎样每次不同的,而使用系统特定的锁,pid是相当随机的,并且带有喜欢最后使用的进程的严重倾向。
够有趣的是,他们面对的第一个问题是ipcsem泄漏(当你处在紧急情况下,优雅重载/停止就是你的敌人,而kill -9将是你的银弹)
要解决这个问题,可以用–ftok选项,允许你分配一个唯一的id给信号量对象,并且在它从前一个允许中可用的情况下重用它:
uwsgi --lock-engine ipcsem --thunder-lock --processes 100 --ftok /tmp/foobar --psgi ....
–ftok接收一个文件作为参赛,它将使用它来构建唯一的id。常见的模式是为它使用pidfile
其他可移植的锁引擎又如何?¶
除了”ipcsem”外,uWSGI 也(在可用之处) 添加了”posixsem”。
只在FreeBSD >= 9时默认使用它们,但在Linux上也可用。
它们并不“健壮”,但是它们并不需要内核资源,因此如果你相信我们的死锁检测器,那么它们就是一个相当棒的方法。 (注意:Graham Dumpleton 像我指出一个事实,它们也可以在Apache 2.x上启用)
福利部分:以uWSGI友好的方式使用Zeeg方法¶
我必须承认,我并不是supervisord的忠实粉丝。毫无疑问,它是一个好软件,但我认为Emperor和–attach-daemon设施是部署问题的一个更好的方法。除此之外,如果你想要一个“脚本化”/“可扩展”的进程监管,那么我认为Circus (https://circus.readthedocs.io/) 更加有趣,更加可行 (我在uWSGI Emperor中实现了socket激活之后做的第一件事就是为Circus中的相同特性进行pull请求 [已合并,如果你关心的话])。
显然,supervisord能用,并且很多人使用它,但是,作为一个重度uWSGI用户,我倾向于尽可能地使用它的特性来完成。
我可以用的第一个方法是绑定到10个不同的端口,然后将其每个映射到一个指定的进程:
[uwsgi]
processes = 5
threads = 5
; create 5 sockets
socket = :9091
socket = :9092
socket = :9093
socket = :9094
socket = :9095
; map each socket (zero-indexed) to the specific worker
map-socket = 0:1
map-socket = 1:2
map-socket = 2:3
map-socket = 3:4
map-socket = 4:5
现在,你有了一个监控5个进程的master,每个进程绑定到一个不同的地址 (无需 --thunder-lock
)
对于Emperor粉丝,你可以做一个这样的模板 (称之为foo.template):
[uwsgi]
processes = 1
threads = 10
socket = :%n
现在,对每个你想要生成的实例+端口进行符号链接:
ln -s foo.template 9091.ini
ln -s foo.template 9092.ini
ln -s foo.template 9093.ini
ln -s foo.template 9094.ini
ln -s foo.template 9095.ini
ln -s foo.template 9096.ini
福利部分2: 安全的SysV IPC semaphores¶
我司的托管平台重度基于Linux cgroups和名字空间。
第一个 (cgroups) 被用于限制/负责资源使用,而第二个(名字空间)被用于向用户提供一个“分离”系统视图 (例如看到专用的主机名或者根文件系统)。
因为我们允许用户在他们的账号中生成PostgreSQL实例,因此我们需要限制SysV对象。
幸运的是,现代的Linux内核有一个用于IPC的名字空间,因此调用 unshare(CLONE_NEWIPC) 将会创建一个完整的新IPC对象集合 (与其他分离)。
在客户专用的Emperor中调用 --unshare ipc
是一个常见的方法。当与内存cgroup结合在一起的时候,你将得到一个相当安全的设置。
优雅重载的艺术¶
作者:Roberto De Ioris
以下文章是语言无关的,而虽然有些是uWSGI特有的,但是它的一些初始考虑也适用于其他的应用服务器和平台。
所有所述技术都假设使用的是一个现代的 (>= 1.4) uWSGI发行版本,并且启用了master进程
什么是“优雅重载”?¶
在你web应用的生命周期里,你会重载它上百次。
你需要为代码更新重载,你需要为uWSGI配置文件的变化重载,你需要为重置应用状态重载。
基本上,重载是你每次做的最简单,最常的 最危险 的操作之一。
那么,为什么是“优雅的”?
使用一种传统的(和高度建议的)架构:一个代理/负载均衡器 (例如nginx) 将请求转发到一个或多个监听各个地址的uWSGI守护程序。
如果你把重载当成“停止实例,启动实例”,那么这两个阶段之间的时间片将给你的客户带来粗鲁的无服务。
避免这种事情发生的主要窍门是:不要关闭映射到uWSGI守护进程地址的文件描述符,并且尽情使用Unix的 fork()
行为 (默认继承文件描述符) 来再次 exec()
uwsgi
二进制文件。
结果就是,你的代理将请求排列到socket中,直到后者能够再次 accept()
它们,而用户/客户只会看到在第一个请求中有些许的减速 (需要时间来完全再次加载应用)。
优雅重载的另一个重要步骤是避免破坏仍然在管理请求的worker/线程。显然,请求可能被卡住,因此,你应该为运行中的worker配置一个超时时间 (在uWSGI中,称之为“worker的宽恕期”,默认值为60秒)。
这类招数是很容易做到的,并且基本上所有的服务器/应用服务器都这样做 (或多或少)。
但是,一如既往,这个世界很丑陋,会有许多问题出现,而“继承socket”方法往往是不够的。
出问题了¶
我们已经看到了,保持uWSGI socket活跃允许代理web服务器在不向客户端喷错误的情况下将请求入队。这只有当你的应用快速重启的时候才会发生,而不幸的是,这并不总是发生。
如Ruby on Rails或者Zope这样的框架默认情况下启动得相当慢,你的应用本身也可能启动得很慢,或者你的机器太过载了,以致于每个进程生成 (fork()
) 都需要时间。
除此之外,你的站点可能非常有名,以致于即使应用载几秒钟内重启,socket队列也可能爆满,迫使代理服务器产生一个错误。
不要忘了,你那仍然载运行请求的worker/线程可能会阻塞重载(出于各种原因),以致于超过了代理服务器容忍的时间。
最后,也有可能在你刚刚提交的代码中有一个应用错误,因此uWSGI无法启动,或者开始发送错误的东东或错误……
重载(粗鲁或者优雅)很容易失败。
监听队列¶
让我们从每个web应用开发者的梦想开始: 成功.
数以千计的客户访问你的应用,显然,你靠它赚钱。不幸的是,这是一个非常复杂的应用,它需要10秒预热。
在优雅重载期间,你希望新客户等待10秒 (最好的情况下) 然后才开始看到内容,但是,不幸的是,你有上百个并发请求,因此,头100个用户将在服务器预热时等待,而其他用户则得到来自代理的一个错误。
因为uWSGI默认队列是100,因此这才发生。在你问问题之前,你得知道,这是选自你的内核默认允许的最大值的平均值。
每个操作系统都有一个默认的限制 (例如,Linux是128),因此,在增加它之前,你还需要增加你的内核限制。
因此,一旦准备好你的内核,你就可以增加监听队列的大小至期望在重载期间入队的最大用户数。
要增加监听队列,使用 --listen <n>
选项,其中
<n>
是队列位置的最大数。
要提高内核限制,你应该检查你的操作系统文档。一些简单的例子:
- FreeBSD上的sysctl
kern.ipc.somaxconn
- Linux 上的
/proc/sys/net/core/somaxconn
注解
调整监听队列只是其中一个原因,不要盲目地以将其设置为巨大值作为提高可用性的方法。
代理超时¶
如果你的重载花费大量的时间,那么这就是另一个你需要检查的东西了。
一般来说,代理允许你设置两种超时:
- connect
- 代理将等待成功连接的最大时间数。
- read
- 服务器在放弃之前能够等待数据的最大时间数。
调整重载的时候,只有”connection”超时有关系。这个超时会进入uWSGI绑定到接口(或者它的继承)和调用 accept()
之间的时间片的活动之中。
等待而不是报错是不错的,而无错误无等待甚至更不错¶
这是本文的重点。我们已经看到了如何提高应用服务器重载期间代理的容忍性。客户将会等待,而不是获得可怕的错误,但我们都希望赚钱,因此,何必强迫他们等待呢?
我们想要零停机时间以及零等待。
Preforking VS lazy-apps VS lazy¶
这是uWSGI项目具有争议的选择之一。
默认情况下,uWSGI在第一个进程中加载整个应用,然后在加载完应用之后,会多次 fork()
自己。这是常见的Unix模式,它可能会大大减少应用的内存使用,允许很多好玩的技巧,而在一些语言上,可能会让带给你很多烦恼。
尽管它的名声如此,但是uWSGI是作为一个Perl应用服务器 (它不叫做 uWSGI,并且它也并不开源) 诞生的,而在Perl的世界里,preforking一般是一种受到祝福的方式。
然而,对于许多其他的语言、平台和框架来说,这并不是真的,因此,在开始处理uWSGI之前,你应该选择在你的栈中如何管理 fork()
。
而从“优雅重载”的角度来看,preforking极大的提高了速度:只加载你的应用一次,而生成额外的worker将会非常快。避免栈中的每个worker都访问磁盘会降低启动时间,特别是对于那些花费大量时间访问磁盘以查找模块的框架或者语言。
不幸的是,每当你的修改代码时,preforking方法迫使你重载整个栈,而不是只重载worker。
除此之外,你的应用可能需要preforking,或者由于其开发的方式,可能完全因其崩溃。
取而代之的是,lazy-apps模式会每个worker加载你的应用一次。它将需要大约O(n)次加载 (其中,n是worker数),非常有可能会消耗更多内存,但会运行在一个更加一致干净的环境中。
记住:lazy-apps与lazy不同,前者只是指示 uWSGI对于每个worker加载应用一次,而后者更具侵略性些 (一般不提倡),因为它改变了大量的内部默认行为。
下面的方法将会向你展示如何在reforking和lazy模式下完成零停机时间/零等待的重载。
注解
每种方式都有其利弊,请谨慎选择。
标准的 (默认/无趣的) 的优雅重载 (又名 SIGHUP
)¶
要触发它,你可以:
- 发送
SIGHUP
到master - 将
r
写入到 Master FIFO - 使用
--touch-reload
选项 - 调用
uwsgi.reload()
API.
在preforking和lazy-apps模式下,它会:
- 等待正在运行的worker。
- 关闭除了映射到socket之外的所有文件描述符。
- 在自身上调用
exec()
。
在lazy模式,它会:
- 等待正在运行的worker。
- 重启它们所有 (这意味着你不能在这类重载期间修改uWSGI选项)。
警告
不鼓励lazy模式!
优点:
- 易于管理
- 无边缘情况问题
- 无不一致状态
- 基本上对实例完全重置。
缺点:
- the ones we seen before
- listen queue filling up
- stuck workers
- potentially long waiting times.
在lazy-apps模式重载worker¶
需要 --lazy-apps
选项。
要触发它:
- 将
w
写入到 Master FIFO - 使用
--touch-workers-reload
选项。
它将会等待运行中的worker,然后重启它们中的每一个。
优点:
- 避免重启整个实例。
缺点:
- 相较于标准的优雅重载,并无用户体验的提高,它只是代码更新不意味着实例重新配置的情况下的一种快捷方式。
链式重载 (lazy apps)¶
需要 --lazy-apps
选项。
要触发它:
- 将
c
写入到 Master FIFO - 使用
--touch-chain-reload
选项。
这是第一个提高用户体验的方式。当被触发的时候,它会重启一个worker,而后面的worker将会不会被重载,直到前一个准备好接收新请求为止。
优点:
- 潜在极大地降低客户端的等待时间
- 减少重载期间机器的负载 (没有多个处理器加载相同的代码)。
缺点:
- 只对代码更新有用
- 你需要一个不错的worker数,才能获得更好的用户体验。
Zerg模式¶
要求zerg服务器或者zerg池。
要触发它,请以zerg模式运行实例。
这是第一种使用相同应用的多个实例来提高用户体验的方法。
Zerg模式是通过利用古老的“通过Unix socket传递文件描述符”技术来工作的。
基本上,一个外部进程 (zerg服务器/池) 绑定到你的应用所需的各个socket上。你的uWSGI实例,并不是通过自身绑定,而是要求zerg服务器/池传给它文件描述符。这意味着,多个无关实例可以请求相同的文件描述符,并且一起工作。
Zerg模式生来就是为了提高自动扩展性的,但很快就成为了用于零停机时间的重载的最受喜爱的方式之一。
现在,例子来了。
生成一个zerg池,它将 127.0.0.1:3031
暴露给Unix socket
/var/run/pool1
:
[uwsgi]
master = true
zerg-pool = /var/run/pool1:127.0.0.1:3031
现在,生成一个或多个绑定到这个zerg池的实例:
[uwsgi]
; this will give access to 127.0.0.1:3031 to the instance
zerg = /var/run/pool1
当你想要更新代码或者选项的时候,只需生成一个附加到这个zerg的新的实例,然后在新的实例准备好接收请求的时候关闭旧的实例。
所谓的”zerg之舞”是一种这类型重载的自动化的把戏。有多种方式来完成它,目标是在新的实例完全准备好并且能够接收新的请求的时候,自动“停止”或者“销毁”旧的实例。更多相关信息,见下。
优点:
- 潜在的银弹
- 允许使用不同选项的实例为相同的应用协作。
缺点:
- 要求一个额外的进程
- 可能难以掌控
- 重载要求对整个uWSGI栈进行拷贝。
Zerg之舞: 暂停实例¶
我们都会犯错,系统管理员需要提高他们快速进行灾难恢复的能力。着眼于规避它们是在浪费时间。不幸的是,我们都是人类(难免会犯错误)。
回滚部署可能是你的救生员。
我们已经看到了zerg模式是如何让我们在相同的socket上有多个实例的。在上一节中,我们用它来生成一个与旧的实例一起工作的新实例。现在,为什么不用“暂停”来取代旧实例的关闭呢?一个已暂停的实例就像你的TV的待机模式。它消耗很少的资源,但是你可以快速的把它唤醒。
“Zerg之舞”是在重载期间不断的实例交换过程的战斗名。每一个重载的结果是一个“休眠中的”实例以及一个运行中的实例。接下来的重载销毁旧的休眠中的实例,然后将旧的运行中的实例转换成休眠中的实例,以此类推。
字面上理解,有几十种方式完成”Zerg之舞”,你可以很容易地在你的重载过程中使用脚本这一事实使得这个方法非常强大以及可定制。
这里,我们将看到一种需要零脚本的方式,它可能功能较少 (并且需要至少是uWSGI 1.9.21),但是应该是这种改善的一个不错的起点。
Master FIFO 是管理实例,而不是依赖于Unix信号的最好的方式。基本上,你写入单字符命令来管理实例。
关于Master FIFO的一个有趣的事情是,你可以为你的实例配置很多个,并且用一个交换另一个是非常容易的。
下面举例说明。
我们生成一个带有3个Master FIFO的实例:新的 (默认),运行中的,以及休眠中的:
[uwsgi]
; fifo '0'
master-fifo = /var/run/new.fifo
; fifo '1'
master-fifo = /var/run/running.fifo
; fifo '2'
master-fifo = /var/run/sleeping.fifo
; attach to zerg
zerg = /var/run/pool1
; other options ...
默认情况下,“新的”那个将会是活跃状态 (也就是:将能够处理命令)。
现在,我们想要生成一个新的实例,一旦这个新的实例准备好了接收请求,就会把旧的那个置于休眠模式。要做到这点,我们将使用uWSGI的高级钩子。钩子允许你在uWSGI的生命周期的各种阶段“做些事”。当新的实例准备好的时候,我们想要强制旧的实例开始工作在休眠FIFO,并且处于“暂停”模式。
[uwsgi]
; fifo '0'
master-fifo = /var/run/new.fifo
; fifo '1'
master-fifo = /var/run/running.fifo
; fifo '2'
master-fifo = /var/run/sleeping.fifo
; attach to zerg
zerg = /var/run/pool1
; hooks
; destroy the currently sleeping instance
if-exists = /var/run/sleeping.fifo
hook-accepting1-once = writefifo:/var/run/sleeping.fifo Q
endif =
; force the currently running instance to became sleeping (slot 2) and place it in pause mode
if-exists = /var/run/running.fifo
hook-accepting1-once = writefifo:/var/run/running.fifo 2p
endif =
; force this instance to became the running one (slot 1)
hook-accepting1-once = writefifo:/var/run/new.fifo 1
第一个worker准备好接收请求后, 会立即在每个实例中运行一次 hook-accepting1-once
阶段。 writefifo
命令允许在未连接其他对端的情况下无失败写入到FIFO (这与一个简单的 write
命令不同,后者在处理不正常的FIFO的时候会失败或者完全阻塞)。
注解
自uWSGI 1.9.21才同时有了这两个特性,而对于较老的发布版本,你可以使用 --hook-post-app
选项来取代 --hook-accepting1-once
,但是你会失去“一次”特性,因此它将只会在preforking模式下才能可靠工作。
你可以使用shell变量
exec:echo <string> > <fifo>
,来取代 writefifo
现在,开始反复使用相同的配置文件来启动实例。如果一切顺利,那么你应该总是拥有两个实例,一个休眠,而一个正在运行。
最后,如果你想唤醒一个休眠中的实例,那么仅需:
# destroy the running instance
echo Q > /var/run/running.fifo
# unpause the sleeping instance and set it as the running one
echo p1 > /var/run/sleeping.fifo
优点:
- 真正的零停机时间重载。
缺点:
- 要求高级的uWSGI和Unix技能。
SO_REUSEPORT
(Linux >= 3.9 和BSD们)¶
在最近的Linux内核和现代BSD们上,你可以尝试 --reuse-port
选项。这个选项允许多个无关实例绑定到相同的网络地址上。你可能会把它看成一个内核级别的zerg模式。基本上讲,可以遵循所有的zerg方法。
一旦你把 --reuse-port
添加到了你的实例中,那么所有的socket都将会设置 SO_REUSEPORT
标志。
优点:
- 与zerg模式相似,甚至可能会更容易管理。
缺点:
- 要求内核支持
- 可能导致不一致状态
- 失去把使用TCP地址作为一种避免意外运行多个实例的方式的能力。
黑色艺术 (对于富饶勇敢的人们): master forking¶
要触发它,将 f
写入到 Master FIFO.
这是一种最危险的重载方式,但是一旦掌握了,就可能会导致很酷的结果。
方法是:在master中调用 fork()
,关闭除了socket相关的文件描述符之外的所有文件描述符,然后 exec()
一个新的uWSGI实例。
最后,你将会获得两个镜面uWSGI实例,它们工作在相同一组socket之上。
有关它的可怕之处是,触发它是多么的容易 (仅需将一个字符写入到master FIFO中)……
有了一点掌握,你就可以在其上实现Zerg之舞。
优点:
- 不需要内核支持或者额外的进程
- 非常快
缺点:
- 每次重载都是完整的拷贝
- 到处都是不一致状态 (pid文件,日志记录等等:master FIFO命令可以帮助解决这些问题).
订阅系统¶
这可能是当你能依赖多服务器的时候最好的方法了。 在你的代理服务器(例如,nginx)和你的实例之间添加”fastrouter”。
实例会“订阅”fastrouter,在负载均衡的时候,它将会把请求从代理服务器 (nginx) 传递到实例,并且不断监控所有实例。
订阅是简单的UDP包,它指示fastrouter将哪个域映射到哪个(些)实例。
就像你可以订阅一样,你也可以取消订阅,而这里就是魔法发生的地方:
[uwsgi]
subscribe-to = 192.168.0.1:4040:unbit.it
unsubscribe-on-graceful-reload = true
; all of the required options ...
添加 unsubscribe-on-graceful-reload
将会强制实例发送“取消订阅”包到fastrouter,所以将不会有请求发送给它直到不会再回来。
优点:
- 低成本零停机时间
- 一种KISS方法 (终于来了).
缺点:
- 需要一个订阅服务器 (例如fastrouter) ,它会带来开销 (即使我们谈论到是微妙级别的)。
不一致状态¶
难过的是,大部分的方法都会涉及对整个实例的拷贝 (例如 Zerg之舞或者master forking),导致不一致状态。
就举一个实例写pid文件的例子:当启动它的一个拷贝的时候,将会重写那个pid文件。
如果你小心地规划你的配置,那么你就能避免不一致状态,但是多亏了 Master FIFO ,你可以管理其中一些 (也就是:最常见的那些):
l
命令将会重新打开日志文件P
命令将会更新所有实例pid文件。
使用Emperor对抗不一致状态¶
如果你通过 Emperor 管理你的实例,那么你可以使用它的特性来规避(或者减少)不一致状态。
为每个实例赋予一个不同的符号链接名将会允许你将文件(例如pid文件或者日志文件)映射到不同的路径上:
[uwsgi]
logto = /var/log/%n.log
safe-pidfile = /var/run/%n.pid
; and so on ...
safe-pidfile
选项与 pidfile
的效果相同,但是在加载过程中会稍微晚点执行写入。这避免了在应用加载失败的时候重写其值,以一个有效的PID号的损失作为代价。
处理ultra-lazy应用 (例如Django)¶
一些应用或者框架 (例如Django) 可能只会在第一个请求的时候才加载它们绝大多数的代码。这意味着,即使使用了诸如Zerg模式或者类似的东西,客户也会在重载过程中感受到速度的下降。
这个问题在应用服务器本身是难以(不可能?)解决的,因此你应该找到一种强制你的应用尽快加载自己的方式。一个不错的窍门是 (说明:对Django有用) 是在应用自身调用入口点函数 (例如WSGI可调用):
def application(environ, sr):
sr('200 OK', [('Content-Type', 'text/plain')])
yield "Hello"
application({}, lambda x, y: None) # call the entry-point function
你可能需要传递CGI变量给environ以进行一个真正的请求:这取决于WSGI应用。
玩转Perl, Eyetoy和RaspberryPi¶
作者: Roberto De Ioris
时间: 2013-12-07

简介¶
这篇文章是在2.0版本发布之前,旨在提高uWSGI在各种领域的性能和可用性的各种实验的产物。
要跟着这篇文章,你需要:
- 一个树莓派 (任何model),上面安装了Linux发行版 (我使用的是标准的Raspbian)
- 一个PS3 Eyetoy网络摄像头
- 一个可用websocket的浏览器 (基本上任何正经的浏览器都可以)
- 一点点Perl知识 (真的只要一点点,少于10行Perl代码 ;)
- 耐心 (在RPI上构建uWSGI + PSGI + coroae需要13分钟)
uWSGI子系统和插件¶
这个项目利用以下uWSGI子系统和插件:
- WebSocket支持
- SharedArea —— uWSGI组件间共享内存页 (用来存储帧)
- uWSGI Mule (用来收集帧)
- Symcall插件
- uWSGI Perl支持 (PSGI)
- uWSGI异步/非堵塞模式 (已更新至uWSGI 1.9) (可选,我们使用
Coro::Anyevent
,但是你可以依靠标准进程,虽然这将需要更多内存)
我们想要完成什么呢¶
我们想要我们的RPI收集来自Eyetoy的帧,然后使用wesockets将其流化至各种已连接的客户端,使用一个HTML5 canvas元素来展示它们。
整个系统必须尽可能使用少的内存,尽可能少的CPU周期,并且它应该支持大量的客户端 (... 好吧,即使是10个客户端对树莓派硬件而言也是个胜利 ;)
技术背景¶
Eyetoy以YUYV格式 (称为YUV 4:2:2) 捕获帧。这意味着,对于2个像素,我们需要4个字节。
默认情况下,分辨率被设置为640x480,因此每个帧将需要614,400字节。
一旦我们有了帧,我们就需要将其解码为RGBA,以允许HTML5 canvas展示它。
YUYV和RGBA之间的转换对RPI来说是重量的 (特别是若你需要为每个已连接客户端进行转换的时候),因此我们会在浏览器中使用Javascript来进行转换。(我们可以使用其他的方式,看看文章末尾以获取相关信息。)
uWSGI栈是由一个从Eyetoy收集帧,并将这些帧写到uWSGI的共享区域的mule组成的。
worker不断地从那个共享区域读取帧,并将它们作为websockets消息发送出去。
让我们开始吧:uwsgi-capture插件¶
uWSGI 1.9.21引入了一个构建uWSGI插件的简化(并且安全)的过程。(期待稍后会有更多的第三方插件!)
位于 https://github.com/unbit/uwsgi-capture 的项目展示了一个使用Video4Linux 2 API来收集帧的非常简单的插件。
每一帧都会被写入到由插件自身初始化的共享区域中。
第一步是获取uWSGI,然后用’coroae’配置文件来构建它:
sudo apt-get install git build-essential libperl-dev libcoro-perl
git clone https://github.com/unbit/uwsgi
cd uwsgi
make coroae
这个过程需要大约13分钟。如果一切顺利,那么你可以克隆uwsgi-capture插件,然后构建它。
git clone https://github.com/unbit/uwsgi-capture
./uwsgi --build-plugin uwsgi-capture
现在,在你的uwsgi目录中就有了capture_plugin.so文件。
把你的Eyetoy插入到RPI上的USB口中,然后看看它是否工作:
./uwsgi --plugin capture --v4l-capture /dev/video0
( --v4l-capture
选项是由capture插件公开的)
如果一切顺利,那么你应该在uWSGI启动日志中看到以下行:
/dev/video0 detected width = 640
/dev/video0 detected height = 480
/dev/video0 detected format = YUYV
sharedarea 0 created at 0xb6935000 (150 pages, area at 0xb6936000)
/dev/video0 started streaming frames to sharedarea 0
(这个共享区域内存指针显然将会不一样)
uWSGI进程将会在此之后立即退出,因为我们并没有告诉它要做什么。 :)
uwsgi-capture
插件公开了2个函数:
captureinit()
, 作为插件的init()钩子的映射,将会由uWSGI自动调用。如果指定了–v4l-capture选项,那么这个函数将会初始化指定设备,并且将其映射到一个uWSGI共享区域。captureloop()
是收集帧,并将它们写入到共享区域的函数。这个函数应该不断运行 (即使没有客户端读取帧)
我们想要一个mule来运行 captureloop()
函数。
./uwsgi --plugin capture --v4l-capture /dev/video0 --mule="captureloop()" --http-socket :9090
这次,我们绑定uWSGI到HTTP端口9090,并且带有一个映射到”captureloop()”函数的mule。这个mule语法是由symcall插件公开的,这个插件控制每一个由”()”结尾的mule参数 (引号是必须的,用来避免shell搞乱括号)。
如果一切顺利,那么你应该看到你的uWSGI服务器生成一个master,一个mule和一个worker。
第二步:PSGI应用¶
是时候来写我们发送Eyetoy帧的websocket服务器了 (你可以在这里找到这个例子的源代码:https://github.com/unbit/uwsgi-capture/tree/master/rpi-examples)。
这个PSGI应用将会非常简单:
use IO::File;
use File::Basename;
my $app = sub {
my $env = shift;
# websockets connection happens on /eyetoy
if ($env->{PATH_INFO} eq '/eyetoy') {
# complete the handshake
uwsgi::websocket_handshake($env->{HTTP_SEC_WEBSOCKET_KEY}, $env->{HTTP_ORIGIN});
while(1) {
# wait for updates in the sharedarea
uwsgi::sharedarea_wait(0, 50);
# send a binary websocket message directly from the sharedarea
uwsgi::websocket_send_binary_from_sharedarea(0, 0)
}
}
# other requests generate the html
else {
return [200, ['Content-Type' => 'text/html'], new IO::File(dirname(__FILE__).'/eyetoy.html')];
}
}
唯一有趣的部分是:
uwsgi::sharedarea_wait(0, 50);
这个函数挂起当前请求,直到指定的共享区域 (‘zero’那个) 得到更新。由于这个函数基本上是一个频繁循环的poll,因此第二个参数指定了poll的频率,以毫秒为单位。50毫秒就能有不错的结果了(随意尝试其他值)。
uwsgi::websocket_send_binary_from_sharedarea(0, 0)
这是一个特别的功能性函数,直接发送一个来自于共享区域(是哒,zero拷贝)的websocket二进制消息。第一个参数是共享区域id (‘zero’那个),而第二个是共享区域中开始读取的位置 (再次是0,因为我们想要一个完整的帧)。
第三步:HTML5¶
HTML部分 (好吧,说是Javascript部分更恰当些) 是非常简单的,除了从YUYV到RGB(A)转换。
<html>
<body>
<canvas id="mystream" width="640" height="480" style="border:solid 1px red"></canvas>
<script>
var canvas = document.getElementById('mystream');
var width = canvas.width;
var height = canvas.height;
var ctx = canvas.getContext("2d");
var rgba = ctx.getImageData(0, 0, width, height);
// fill alpha (optimization)
for(y = 0; y< height; y++) {
for(x = 0; x < width; x++) {
pos = (y * width * 4) + (x * 4) ;
rgba.data[pos+3] = 255;
}
}
// connect to the PSGI websocket server
var ws = new WebSocket('ws://' + window.location.host + '/eyetoy');
ws.binaryType = 'arraybuffer';
ws.onopen = function(e) {
console.log('ready');
};
ws.onmessage = function(e) {
var x, y;
var ycbcr = new Uint8ClampedArray(e.data);
// convert YUYV to RGBA
for(y = 0; y< height; y++) {
for(x = 0; x < width; x++) {
pos = (y * width * 4) + (x * 4) ;
var vy, cb, cr;
if (x % 2 == 0) {
ycbcr_pos = (y * width * 2) + (x * 2);
vy = ycbcr[ycbcr_pos];
cb = ycbcr[ycbcr_pos+1];
cr = ycbcr[ycbcr_pos+3];
}
else {
ycbcr_pos = (y * width * 2) + ((x-1) * 2);
vy = ycbcr[ycbcr_pos+2];
cb = ycbcr[ycbcr_pos+1];
cr = ycbcr[ycbcr_pos+3];
}
var r = (cr + ((cr * 103) >> 8)) - 179;
var g = ((cb * 88) >> 8) - 44 + ((cr * 183) >> 8) - 91;
var b = (cb + ((cb * 198) >> 8)) - 227;
rgba.data[pos] = vy + r;
rgba.data[pos+1] = vy + g;
rgba.data[pos+2] = vy + b;
}
}
// draw pixels
ctx.putImageData(rgba, 0, 0);
};
ws.onclose = function(e) { alert('goodbye');}
ws.onerror = function(e) { alert('oops');}
</script>
</body>
</html>
这里没啥特别的。绝大部分的代码是关于YUYV->RGBA转换。注意设置websocket通信为“二进制”模式 (binaryType = ‘arraybuffer’就够了),并且一定要使用Uint8ClampedArray (否则性能将会很糟糕)
准备观看¶
./uwsgi --plugin capture --v4l-capture /dev/video0 --http-socket :9090 --psgi uwsgi-capture/rpi-examples/eyetoy.pl --mule="captureloop()"
连接你的浏览器到你的树莓派到TCP端口9090,然后开始看看。
并发性¶
当你看你的websocket流时,你或许想要启动另一个浏览器窗口来看看你的视频的第二份拷贝。不幸的是,你生成的uWSGI只有一个worker,因此只有一个客户端才能获取到流。
你可以轻松添加多个worker:
./uwsgi --plugin capture --v4l-capture /dev/video0 --http-socket :9090 --psgi uwsgi-capture/rpi-examples/eyetoy.pl --mule="captureloop()" --processes 10
就像这个,最多支持10个人观看视频流。
但是对于像这样的绑定I/O应用,协程是更好的方式 (并且更便宜):
./uwsgi --plugin capture --v4l-capture /dev/video0 --http-socket :9090 --psgi uwsgi-capture/rpi-examples/eyetoy.pl --mule="captureloop()" --coroae 10
现在,奇妙的是,我们能够只用一个进程来管理10个客户端!树莓派上的内存将会对你心存感激。
零拷贝所有的东西¶
为什么我们要使用共享区域?
共享区域是uWSGI最高级的特性之一。如果你看看uwsgi-capture插件,那么你会看到它是如何轻松创建一个指向一个mmap()区域的共享区域的。基本上,每个worker,线程(但是在Perl中请千万不要使用线程)或者协程将会以一种并发安全的方式访问那个内存。
除此之外,多亏了websocket/共享区域合作API,你可以直接发送来自于一个共享区域的websocket包,而无需拷贝内存 (除了结果websocket包)。
这是比下面这样更快的方式:
my $chunk = uwsgi::sharedarea_read(0, 0)
uwsgi::websocket_send_binary($chunk)
我们需要每次迭代的时候为$chunk分配内存,拷贝共享区域内容到它里面,最后在一个websocket消息中封装它。
有了共享区域,你移除了不断分配(和释放)内存,以及将其从共享区域拷贝到Perl VM的需求。
其他方法¶
显然你还可以使用其他方法。
你可以破解uwsgi-capture,分配直接写入RGBA帧的第二个共享区域。
JPEG编码是相当快的,你可以尝试在RPI中编码帧,然后将其作为MJPEG帧发送 (而不是使用websockets):
my $writer = $responder->( [200, ['Content-Type' => 'multipart/x-mixed-replace; boundary=uwsgi_mjpeg_frame']]);
$writer->write("--uwsgi_mjpeg_frame\r\n");
while(1) {
uwsgi::sharedarea_wait(0);
my $chunk = uwsgi::sharedarea_read(0, 0);
$writer->write("Content-Type: image/jpeg\r\n");
$writer->write("Content-Length: ".length($chunk)."\r\n\r\n");
$writer->write($chunk);
$writer->write("\r\n--uwsgi_mjpeg_frame\r\n");
}
其他语言¶
在写这篇文章的时候,uWSGI PSGI插件是唯一一个为websockets+sharedarea公开了额外API的插件。其他语言插件将在不久后进行更新。
更多的hack¶
捣鼓RPI板子是相当有趣的,而uWSGI则是它的一个不错的伴侣 (特别是它的低层次API函数)。
注解
留给读者的一个练习:记住,你可以mmap()地址0x20200000来访问Raspberry PI GPIO控制器……准备好写一个uwsgi-gpio插件了吗?
Offloading Websockets and Server-Sent Events AKA “Combine them with Django safely”¶
作者:Roberto De Ioris
日期:20140315
免责声明¶
这篇文章显示了将websockets(或者sse)与Django以一种“安全的方式”结合起来的一种相当高级的方法。它不会向你展示websockets和sse有多酷,或者如何用它们写出更好的应用,而是试图让你避免使用它们的最差实践。
在我看来,Python面向web的世界正面临着一种通信/市场问题:大量的人在非阻塞技术(例如gevent)上运行大量阻塞应用(例如django)只是因为有人告诉他们这很酷并且会解决他们所有的扩展问题。
这是完全错误,危险和邪恶的,你不能将阻塞应用和非阻塞引擎混在一起,即使是一个单一、非常小的阻塞部分都能潜在摧毁你整个栈。正如我已经说了几十次一样,如果你的应用是99.9999999%非阻塞的,那么它仍然是阻塞的。
并且不是,你的Django应用上的猴子补丁并非魔法。除非你正使用高度自定义的数据库适配器,对工作在非阻塞模式进行了调整,否则你就是错的。
以看起来就是个超级大混蛋的代价,我强烈建议你完全忽略人们让你将你的Django应用移到gevent, eventlet, tornado或者其他什么的,而不警告你你会遇到数百个问题的建议。
话虽如此,我爱gevent,它可能是uWSGI项目中支持得最好的(带perl的Coro::AnyEvent)循环引擎了。因此,在这篇文章中,我将使用gevent来为Django部分管理websocket/sse流量和纯多进程。
如果这最后一句对你来说像是废话,那么你可能不知道uWSGI的卸载是什么……
uWSGI卸载¶
这个概念并非是个新东西,或者是uWSGI特有的。诸如nodejs或者twisted这样的项目已经用它多年了。
注解
一个提供静态文件服务的web应用的例子并不非常有趣,也不是用来展示的最好的东西,但是在稍后展示一个使用X-Sendfile的真实世界的例子的时候,会游泳。
想象这个简单的WSGI应用:
def application(env, start_response):
start_response('200 OK',[('Content-Type','text/plain')])
f = open('/etc/services')
# do not do it, if the file is 4GB it will allocate 4GB of memory !!!
yield f.read()
这将会简单返回/etc/services的内容。它是一个相当小的文件,因此在几毫秒时间内,你的进程将会准备好处理另一个请求。
那要是/etc/services是4千兆字节呢?你的进程(或者线程)将会阻塞几秒(甚至几分钟),并且不能够管理其他请求,直到完全传输这个文件。
如果你可以告诉其他线程为你发送这个文件,这样你就能够管理其他请求,岂不是很酷?
卸载就是这样的:它会为你提供一个或多个线程来为你完成一些简单并且慢点任务。哪种任务呢?所有那些可以以一种非阻塞方式管理的任务,因此单个线程就可以为你管理上千个传输。
你可以将其视为你的电脑中的DMA引擎,你的CPU将会编程DMA来将内存从控制器传输到RAM,然后将会被释放,以完成其他任务,同时DMA在后台工作。
要在uWSGI中启用卸载,你只需要添加 --offload-threads <n>
选项,其中<n>是每个进程要生成的线程数。 (一般而言,单个线程就够了,但是如果你想要使用/滥用你的多CPU核,那么随意增加)
一旦启用了卸载,那么每当uWSGI检测到一个操作能够安全被卸载的时候,它将自动使用它。
在python/WSGI中,任何对wsgi.file_wrapper的使用将会被自动卸载,以及当你使用uWSGI代理特性来传递请求到其他使用uwsgi或者HTTP协议到服务器时。
一个很酷的例子 (甚至在uWSGI文档的Snippets页面也有展示) 是实现一个卸载助力的X-Sendfile特性:
[uwsgi]
; load router_static plugin (compiled in by default in monolithic profiles)
plugins = router_static
; spawn 2 offload threads
offload-threads = 2
; files under /etc can be safely served (DANGEROUS !!!)
static-safe = /etc
; collect the X-Sendfile response header as X_SENDFILE var
collect-header = X-Sendfile X_SENDFILE
; if X_SENDFILE is not empty, pass its value to the "static" routing action (it will automatically use offloading if available)
response-route-if-not = empty:${X_SENDFILE} static:${X_SENDFILE}
; now the classic options
plugins = python
; bind to HTTP port 8080
http-socket = :8080
; load a simple wsgi-app
wsgi-file = myapp.py
现在,在我们的应用中,我们可以X-Sendfile来在无阻塞情况下发送静态文件:
def application(env, start_response):
start_response('200 OK',[('X-Sendfile','/etc/services')])
return []
在这篇文章中将会使用一个非常类似的概念:我们将会使用一个正常的Django来设置我们的会话,来认证用户,以及任意(快的)东东,然后我们会返回一个特别的头,它会指示uWSGI卸载连接到另一个uWSGI实例 (监听一个私有socket),这个实例将使用gevent,以一种非阻塞方式管理websocket/sse事务。
我们的SSE应用¶
SSE部分将非常简单,一个基于gevent的WSGI应用将会每秒发送当前时间:
from sse import Sse
import time
def application(e, start_response):
print e
# create the SSE session
session = Sse()
# prepare HTTP headers
headers = []
headers.append(('Content-Type','text/event-stream'))
headers.append(('Cache-Control','no-cache'))
start_response('200 OK', headers)
# enter the loop
while True:
# monkey patching will prevent sleep() blocking
time.sleep(1)
# add the message
session.add_message('message', str(time.time()))
# send to the client
yield str(session)
让我们在/tmp/foo UNIX socket上运行它 (将应用保存为sseapp.py)
uwsgi --wsgi-file sseapp.py --socket /tmp/foo --gevent 1000 --gevent-monkey-patch
(time.sleep()需要猴子补丁,如果你想要/喜欢的话,随意使用gevent原语来休眠)
(无趣的)HTML/Javascript¶
<html>
<head>
</head>
<body>
<h1>Server sent events</h1>
<div id="event"></div>
<script type="text/javascript">
var eventOutputContainer = document.getElementById("event");
var evtSrc = new EventSource("/subscribe");
evtSrc.onmessage = function(e) {
console.log(e.data);
eventOutputContainer.innerHTML = e.data;
};
</script>
</body>
</html>
它非常简单,它将连接到/subscribe,并且将开始等待事件。
Django视图¶
我们的django视图,将非常简单,它将简单生成一个特色的响应头 (我们讲称之为X-Offload-to-SSE),并且把登录用户的用户名作为它的值:
def subscribe(request):
response = HttpResponse()
response['X-Offload-to-SSE'] = request.user
return response
现在,我们已经为“高级”部分准备好了。
让我们卸载SSE事务¶
配置看起来会有点复杂,但是它与之前看到的X-Sendfile概念相同:
[uwsgi]
; the boring part
http-socket = :9090
offload-threads = 2
wsgi-file = sseproject/wsgi.py
; collect X-Offload-to-SSE header and store in var X_OFFLOAD
collect-header = X-Offload-to-SSE X_OFFLOAD
; if X_OFFLOAD is defined, do not send the headers generated by Django
response-route-if-not = empty:${X_OFFLOAD} disableheaders:
; if X_OFFLOAD is defined, offload the request to the app running on /tmp/foo
response-route-if-not = empty:${X_OFFLOAD} uwsgi:/tmp/foo,0,0
唯一“新的”部分是使用 disableheaders
路由动作。这是必须的,否则Django生成的头将会伴着由基于gevent的应用生成的头发送。
你可以避免它 (记住,只在2.0.3添加了 disableheaders
),在gevent应用中移除到start_response()到调用 (冒着被一些WSGI神诅咒的风险),然后修改Django视图来设置正确的头部:
def subscribe(request):
response = HttpResponse()
response['Content-Type'] = 'text/event-stream'
response['X-Offload-to-SSE'] = request.user
return response
最终,你或许想要更加“精简”,并简单检测’text/event-stream’ content_type存在:
[uwsgi]
; the boring part
http-socket = :9090
offload-threads = 2
wsgi-file = sseproject/wsgi.py
; collect Content-Type header and store in var CONTENT_TYPE
collect-header = Content-Type CONTENT_TYPE
; if CONTENT_TYPE is 'text/event-stream', forward the request
response-route-if = equal:${CONTENT_TYPE};text/event-stream uwsgi:/tmp/foo,0,0
现在,如何在gevent应用中访问Django登录用户的用户名呢?
你应该注意到,gevent应用在每个请求中打印了WSGI环境变量的内容。那个环境变量与Django应用+已收集头部相同。因此,访问environ[‘X_OFFLOAD’]将会返回已登录用户名。 (显然,在第二个例子中,使用了内容类型,不再收集带用户名的变量,因此你应该修复它)
你可以使用相同的方法传递所有你需要的信息,你可以收集所有你需要的变量,等等等等。
你甚至可以在运行时添加变量:
[uwsgi]
; the boring part
http-socket = :9090
offload-threads = 2
wsgi-file = sseproject/wsgi.py
; collect Content-Type header and store in var CONTENT_TYPE
collect-header = Content-Type CONTENT_TYPE
response-route-if = equal:${CONTENT_TYPE};text/event-stream addvar:FOO=BAR
response-route-if = equal:${CONTENT_TYPE};text/event-stream addvar:TEST1=TEST2
; if CONTENT_TYPE is 'text/event-stream', forward the request
response-route-if = equal:${CONTENT_TYPE};text/event-stream uwsgi:/tmp/foo,0,0
或者 (使用goto以获得更好的可读性):
[uwsgi]
; the boring part
http-socket = :9090
offload-threads = 2
wsgi-file = sseproject/wsgi.py
; collect Content-Type header and store in var CONTENT_TYPE
collect-header = Content-Type CONTENT_TYPE
response-route-if = equal:${CONTENT_TYPE};text/event-stream goto:offload
response-route-run = last:
response-route-label = offload
response-route-run = addvar:FOO=BAR
response-route-run = addvar:TEST1=TEST2
response-route-run = uwsgi:/tmp/foo,0,0
使用uwsgi api (>= uWSGI 2.0.3) 进行简化¶
虽然处理头部是相当HTTP友好型的,但uWSGI 2.0.3增加了在代码中直接定义每个请求变量的可能性。
这允许一个更“优雅”的方式 (即使高度不可移植):
import uwsgi
def subscribe(request):
uwsgi.add_var("LOGGED_IN_USER", request.user)
uwsgi.add_var("USER_IS_UGLY", "probably")
uwsgi.add_var("OFFLOAD_TO_SSE", "y")
uwsgi.add_var("OFFLOAD_SERVER", "/tmp/foo")
return HttpResponse()
现在,配置可以修改成更优雅:
; the boring part
http-socket = :9090
offload-threads = 2
wsgi-file = sseproject/wsgi.py
; if OFFLOAD_TO_SSE is 'y', do not send the headers generated by Django
response-route-if = equal:${OFFLOAD_TO_SSE};y disableheaders:
; if OFFLOAD_TO_SSE is 'y', offload the request to the app running on 'OFFLOAD_SERVER'
response-route-if = equal:${OFFLOAD_TO_SSE};y uwsgi:${OFFLOAD_SERVER},0,0
你注意到我们如何允许Django应用设置后端服务器来使用请求变量了吗?
现在,我们可以更进一步。我们不会使用路由框架 (除了禁用头部生成):
import uwsgi
def subscribe(request):
uwsgi.add_var("LOGGED_IN_USER", request.user)
uwsgi.add_var("USER_IS_UGLY", "probably")
uwsgi.route("uwsgi", "/tmp/foo,0,0")
return HttpResponse()
以及一个简单的:
; the boring part
http-socket = :9090
offload-threads = 2
wsgi-file = sseproject/wsgi.py
response-route = ^/subscribe disableheaders:
Websockets怎样?¶
我们已经看到了如何卸载SSE (那是单向的)。我们也可以卸载websockets (那是双向的)。
概念是相同的,你只需要保证 (和之前一样) Django没有发送任何头部,(否则,websocket握手将会失败),然后,你可以修改你的gevent应用:
import time
import uwsgi
def application(e, start_response):
print e
uwsgi.websocket_handshake()
# enter the loop
while True:
# monkey patching will prevent sleep() to block
time.sleep(1)
# send to the client
uwsgi.websocket_send(str(time.time()))
使用redis或者uWSGI缓存框架¶
请求变量是方便的 (并且有趣),但是它们有限 (见下)。如果你需要在Django和sse/websocket应用之间传递大量的数据,那么Redis是个不错的方式 (并且和gevent完美契合)。基本上,你存储来自Django的信息到redis中,然后只传递哈希键 (通过请求变量) 到sse/websocket应用。
可以用uWSGI缓存框架完成相同的工作,但是考虑到redis拥有大量的数据原语,而uWSGI只支持key->value项。
常见陷阱¶
- 你可以添加到每个请求上的变量总数是由uwsgi包缓存(默认是4k)限制的。你可以用–buffer-size选项将其增至64k。
- 这是这篇文章的重点:不要在你的gevent应用中使用Django ORM,除非你知道你在干什么!!!(读一读:你有一个Django数据库适配器,它支持gevent,并且与标准的相比,它并不糟糕……)
- 忘记找到一种方式来禁用Django中的头部生成吧。这是它的WSGI适配器的一个“限制/特性”,使用uWSGI设施 (如果可用的话),或者不要在你的gevent应用中生成头部。最终,你可以以这种方式修改wsgi.py:
"""
WSGI config for sseproject project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/1.6/howto/deployment/wsgi/
"""
import os
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "sseproject.settings")
from django.core.wsgi import get_wsgi_application
django_application = get_wsgi_application()
def fake_start_response(status, headers, exc_info=None):
pass
def application(environ, start_response):
if environ['PATH_INFO'] == '/subscribe':
return django_application(environ, fake_start_response)
return django_application(environ, start_response)
uWSGI子系统¶
uWSGI告警子系统 (自1.3起)¶
从1.3版本开始,uWSGI有了一个告警系统。这个子系统允许开发者/系统管理员通过各种通道“宣布”应用的特殊条件。例如,你也许想要通过一个全监听队列的Jabber/XMPP,或者harakiri条件收到通知。告警系统基于两个组件:事件监控器和事件操作。
事件监控器是等待特定条件(例如,文件描述符或者特定日志信息上的一个事件)的东东。
一旦条件为真,那么就会引发一个操作(例如,发送邮件)。
嵌入式事件监控器¶
事件监控器可通过插件添加,uWSGI核心包括以下事件监控器:
log-alarm
在指定的正则表达式匹配到一个日志行的时候,触发一个告警alarm-fd
在指定的文件描述符准备好的时候,触发一个告警(这是这相当底层,并且是大多数告警插件的基础)alarm-backlog
在socket backlog队列满的时候,触发一个告警alarm-segfault
(自1.9.9起) 在uWSGI出现段错误的时候触发一个告警alarm-cheap
为每个基于curl的告警使用主要的告警线程,而不是创建专用线程
定义告警¶
你可以定义无数个告警。每个告警都具有唯一的名字。
目前,在主要发布版本中,可用的告警操作如下:
'cmd' - 运行一个命令,将日志行传递给标准输入(stdin)
'signal' - 生成一个uWSGI信号
'mule' - 发送日志行给一个mule
'curl' - 传递一个日志行给一个curl url (支持http,https和smtp)
'xmpp' - 通过XMPP/jabber发送日志行
要定义一个告警,使用选项 --alarm
。
--alarm "<name> <plugin>:<opts>"
记住,只有当你在命令行定义告警的时候,才引用。
[uwsgi]
alarm = mailme cmd:mail -s 'uWSGI alarm' -a 'From: foobar@example.com' admin@example.com
alarm = cachefull signal:17
这里,我们定义了两个告警: mailme
和 cachefull
。第一个调用 mail
二进制来发送日志行给一个邮件地址;第二个生成一个uWSGI信号。现在,我们需要添加规则以触发告警:
[uwsgi]
alarm = mailme cmd:mail -s 'uWSGI alarm' -a 'From: foobar@example.com' admin@example.com
alarm = cachefull signal:17
log-alarm = cachefull,mailme uWSGI listen queue of socket
log-alarm = mailme HARAKIRI ON WORKER
log-alarm的语法如下
--log-alarm "<name> <regexp>"
在我们前面的例子中,我们使用应用到日志行的正则表达式定义了两个条件。第一个在监听队列满的时候会触发这两个告警,而第二个在一个worker被harakiri灭掉(commit harakiri)的时候,只会调用’mailme’。
该死,这……这是我见过的最原始的东东……¶
你也许是对的。但是如果你暂时丢掉你那本“如何成为一个有大量基友但是没啥钱的炫酷程序员”,那么你会意识到有了这么一个简单的系统,你可以做些什么。想要看个例子?
[uwsgi]
alarm = jabber xmpp:foobar@jabber.xxx;mysecretpassword;admin@jabber.xxx,admin2@jabber.xxx
log-alarm = jabber ^TERRIBLE ALARM
现在,在你的应用中,仅需添加
print "TERRIBLE ALARM! The world exploded!!!"
来发送一个Jabber信息给 admin@jabber.xxx
和 admin2@jabber.xxx
而无需添加任何显著开销到你的应用上 (因为告警是由master进程中一个或多个线程触发的,而跟worker无关)。
再看看另一个例子?
看看这个Rack中间件:
class UploadCheck
def initialize(app)
@app = app
end
def call(env)
if env['REQUEST_METHOD'] == 'POST' and env['PATH_INFO'] == '/upload'
puts "TERRIBLE ALARM! An upload has been made!"
end
@app.call(env)
end
end
不受糟糕的规则之害¶
这样一个多功能的系统易受到许多丑陋的错误干扰,主要是无限循环。因此,尽量精心构造你的正则表达式。内嵌的反循环子系统应防止告警插件错误的产生日志行。这个系统并不完美,因此,请再三检查你的正则表达式。
如果你正在写一个插件,那么确保在你的日志消息前附加’[uwsgi-alarm’字符串。这样的行将会被跳过,直接传递给日志子系统。有一个方便的可用API函数: uwsgi_log_alarm()
.
log-alarm是如何工作的呢?¶
启用log-alarm自动让uWSGI实例进入 log-master模式 ,委托日志写入到master中。master只在传递日志行给日志插件之前执行告警子系统。堵塞的告警插件应运行在一个线程中 (例如curl和xmpp这些),而简单的告警插件 (例如signal和cmd)可以直接运行在master中。
可用插件及其语法¶
curl¶
发送日志行给可cURL的URL。这个告警插件默认情况下不编译,因此如果需要构建它,仅需运行:
python uwsgiconfig.py --plugin plugins/alarm_curl
curl:<url>[;opt1=val1;opt2=val2]
url
是任意标准的cURL URL,而当前公开的选项如下
- “auth_pass”
- “auth_user”
- “conn_timeout”
- “mail_from”
- “mail_to”
- “method”
- “ssl”
- “subject”
- “timeout”
- “url”
- “ssl_insecure”
因此,要通过SMTP AUTH发送邮件:
[uwsgi]
plugins = alarm_curl
alarm = test curl:smtp://mail.example.com;mail_to=admin@example.com;mail_from=uwsgi@example.com;auth_user=uwsgi;auth_pass=secret;subject=alarm from uWSGI !!!
或者,我们可以使用Gmail来发送告警:
[uwsgi]
plugins = alarm_curl
alarm = gmail curl:smtps://smtp.gmail.com;mail_to=admin@example.com;auth_user=uwsgi@gmail.com;auth_pass=secret;subject=alarm from uWSGI !!!
或者将日志行 PUT 到由基本身份验证保护的HTTP服务器:
[uwsgi]
plugins = alarm_curl
alarm = test2 curl:http://192.168.173.6:9191/argh;auth_user=topogigio;auth_pass=foobar
或者将日志行 POST 到一个有自生成SSL证书的HTTPS服务器。
[uwsgi]
plugins = alarm_curl
alarm = test3 curl:https://192.168.173.6/argh;method=POST;ssl_insecure=true
xmpp¶
可能是内建的那一堆插件中最有趣的一个了。你需要 libgloox
包来构建XMPP告警插件 (在Debian/Ubuntu上,运行 apt-get install gloox-dev
).
python uwsgiconfig.py --plugin plugins/alarm_xmpp
xmpp:<jid>;<password>;<recipients>
你可以将“,”当成分隔符来设置多个收件人。
[uwsgi]
plugins = alarm_xmpp
alarm = jabber xmpp:app@example.it;secret1;foo1@foo.it,foo2@foo.it
一个仍然关于XMPP插件的更有趣的事情是,当你的应用死掉的时候,你会看到你的应用的Jabber账户。 :-)
一些XMPP服务器 (最明显的是OSX服务器) 要求你绑定到资源。你可以通过附加 /resource
到JID来这样做。
[uwsgi]
plugins = alarm_xmpp
alarm = jabber xmpp:max@server.local/uWSGI;secret1;foo1@foo.it,foo2@foo.it
speech¶
用于OSX的一个玩具插件,主要用于,卖弄uWSGI和Objective-C的集成。它只使用OSX语音合成器来“宣告”告警。
python uwsgiconfig.py --plugin plugins/alarm_speech
[uwsgi]
plugins = alarm_speech
http-socket = :8080
alarm = say speech:
log-alarm = say .*
打开你的扩音器,运行uWSGI,然后开始听……
airbrake¶
从1.9.9开始,uWSGI包含了 --alarm-segfault
选项,在uWSGI段错误的适合引发告警。
airbrake
插件可以用来发送段错误回溯给airbrake兼容的服务器。例如Airbrake自己,以及它的开源克隆errbit
(https://github.com/errbit/errbit),Airbrake支持是实验性的,它也许不会在所有情况下都可用。
plugins = airbrake
alarm = errbit airbrake:http://errbit.domain.com/notifier_api/v2/notices;apikey=APIKEY;subject=uWSGI segfault
alarm-segfault = errbit
注意,alarm-segfault不需要Airbrake插件。使用其他告警插件,也可以发送回溯。
uWSGI缓存框架¶
注解
本页面是关于“新一代”的缓存,它由uWSGI 1.9引入。 对于旧式缓存 (现在简单称其为“web缓存”),可以看看 WebCaching框架
uWSGI包含了一个非常快速、全内存访问、零IPC、SMP安全、不断优化、高度可调的、键值存储的简单的“缓存框架”。单个uWSGI实例可以使用不同的设置,出于不同的目的,创建无限个不同的“缓存”。
创建一个“缓存”¶
要创建一个缓存,你可以使用 --cache2
选项。它接收指定缓存选项的参数字典。要有一个有效的缓存,你需要指定它的名字,以及它可以包含的项的最大数目。
uwsgi --cache2 name=mycache,items=100 --socket :3031
这将会创建一个名为”mycache”的缓存,它最多拥有100个项。每个项最大可以是64k。
关于“项的最大数”的一个忧伤/奇怪/灵异/糟糕的注意¶
如果你开始使用的是100个项的缓存,那么你会突然注意到,你可以使用的项的实际最大数其实是99。
这是因为,缓存的第一个项总是被内部用于”NULL/None/undef”项。
当你开始计划你的缓存配置的时候,记住这点。
配置缓存(它是如何工作的)¶
uWSGI缓存的工作方式像一个文件系统。你有一个用于存储键(元数据)的区域,后面跟着一系列固定大小的块,其中保存每个键的内容。另一个内存区域,会分配哈希表,用于对键的快速搜索。当你请求一个键的时候,首先会在哈希表上对其进行散列。每一个哈希指向元数据区域中的一个艰难。可以链接键来管理哈希冲突。每一个键都有一个到包含其值的块的引用。
简单的阻塞(更快)VS. bitmap (更慢)¶
警告
Bitmap模式只从uWSGI 2.0.2开始才被认为是可用于生产的!(也就是说,在那之前,它有问题。)
在标准的 (“单个块”) 配置中,一个键可以只映射到单个块中。因此,如果你有一个大小为64k的缓存块,那么你的项可以至多为65,535字节长。相反,小于它的项仍将消耗64k内存。这个方法的优势是它的简单性和速度。系统不需要在每次你插入一个对象到缓存中的时候都扫描内存以查找可用块。
如果你需要一个更加灵活 (但相对较慢) 的方法,那么你可启用”bitmap”模式。将会创建另一个包含所有缓存已用和可用块的映射的内存区域。当你插入一个项的时候,会扫描bitmap,查找连续的可用块。块必须是连续的,这可能导致一些碎片,但是这并非和磁盘存储一样是个大问题,并且你可以总是调整块大小来减少碎片。
永久存储¶
你可以将缓存数据存储在后备文件中来实现持久化。由于这是由 mmap()
管理的,因此对于用户来说基本是透明的。你不应该依赖此来获得数据安全 (磁盘同步是异步管理的); 只为性能要求使用它。
网络访问¶
你所有的缓存都能通过网络来访问。一个名为”cache”的请求插件 (modifier1 111) 管理来自外部节点的请求。在一个标准的uWSGI单片构建中,缓存插件总是启用的。这个缓存插件以一种全非阻塞的方式工作,并且它是绿色线程/协程友好的,因此你可以安全地使用诸如gevent或者Coro::AnyEvent这样的技术。
UDP同步¶
这个技术的灵感来自于STUD项目,它使用了诸如此类的东东来进行SSL会话缩放 (巧的是,同样的方法也可被用于uWSGI SSL/HTTPS路由器)。基本上,每当你设置/更新/删除一个缓存中的项的时候,该操作就会通过简单的UDP包传送到远程节点。对于UDP同步,并无内置的保证,因此只为特定目的,例如 扩展SSL连接 (uWSGI 1.9) ,才使用它。
–cache2选项¶
这是 --cache2
的所有选项(以及它们的别名)列表
name¶
设置缓存名。必须是实例中唯一的。
max-items || maxitems || items¶
设置缓存项的最大数。
blocksize¶
设置单个块的大小(以字节为单位)。
blocks¶
设置缓存中的块数目。只有在bitmap模式下才有用,否则块的数目等于项的最大数。
hash¶
设置哈希表中使用的哈希算法。当前可选值为”djb33x”(默认)和”murmur2”。
hashsize || hash_size¶
这是哈希表的大小,以字节为单位。一般来说,65536 (默认值) 是个不错的值。只有在你知道你在做什么的时候,或者你的缓存中存在大量冲突的情况下才修改它。
keysize || key_size¶
设置一个键的大小,以字节为单位 (默认是2048)
store¶
为持久化存储设置文件名。如果它不存在,那么系统假设一个空的存储,并且会创建该文件。
store_sync || storesync¶
设置在持久化模式下,调用msync()来将内存缓存刷新到磁盘上之后的秒数。默认情况下,禁用它,将决定权交给内核。
store_delete || storedelete¶
默认情况下,如果一个缓存文件不存在,并且缓存文件并不匹配配置项/块大小,那么uWSGI将不会启动。设置这个选项将会让uWSGI在不匹配的时候删除存在的文件,然后创建一个新的。
node || nodes¶
以分号分隔的UDP服务器列表,它们将会接收UDP缓存更新。
sync¶
以分号分隔的uwsgi地址列表,缓存子系统将会连接到它们上面,来获取缓存的完整dump。它可以被用来初始化缓存同步。发送一个有效的dump的第一个节点将会停止这个过程。
udp || udp_servers || udp_server || udpserver¶
以分号分隔的UDP地址列表,会将缓存绑定在上面,以等待UDP更新。
bitmap¶
设为1来启用bitmap模式。
lastmod¶
设置lastmod为1将会在每次缓存项修改的时候更新每个缓存的last_modified_at时间戳。如果你想要跟踪这个值,或者如果其他特性依赖于它,那么启用它。稍后,可以通过统计数据socket来访问这个值。
ignore_full¶
默认情况下,如果缓存满了,那么uWSGI会在每一次缓存设置操作的时候打印告警消息。要禁用这个告警,则设置这个选项。自2.0.4起可用。
purge_lru¶
这个选项允许你在缓存存储满的时,试图添加新的项的时候,让缓存框架移除最近最少使用 (LRU) 项。将会忽略下面描述的 expires
参数。当一个项被cache_get(), cache_set() 和cache_update() 访问、添加和更新的时候,会被认为使用了这个项;而由cache_exists()进行的存在性检查则不是。
使用缓存API,在应用中访问缓存¶
你可以通过使用缓存API,访问你的实例或者远程实例中的各种缓存。目前,公开了以下函数 (每个语言对其的命名可能与标准有点不同):
- cache_get(key[,cache])
- cache_set(key,value[,expires,cache])
- cache_update(key,value[,expires,cache])
- cache_exists(key[,cache])
- cache_del(key[,cache])
- cache_clear([cache])
如果调用该缓存API的语言/平台区分了字符串和字节 (例如Python 3和Java),那么你必须假设键是字符串,而值是字节 (或者在java之下,是字节数组)。否则,键和值都是无特定编码的字符串,因为在内部,缓存值和缓存键都是简单的二进制blob。
expires
参数 (默认为0,表示禁用) 是对象失效的秒数 (并当未设置purge_lru
的时候,由缓存清道夫移除,见下)
cache
参数是所谓的“魔法标识符”,它的语法是
cache[@node]
.
要操作本地缓存”mycache”,将其设为”mycache”,而要操作位于192.168.173.22,端口为4040上的uWSGI服务器中的”yourcache”,则将其设为 yourcache@192.168.173.22:4040
。
一个空的缓存值表示默认缓存,它一般是第一个初始化的缓存。默认值为空。
所有的网络操作都是透明的、全非阻塞的、并且线程/绿色线程安全。
缓存清道夫线程¶
当至少一个缓存不配置 purge_lru
,并且启用master的时候,就会启动一个名为”the cache sweeper”的线程。它的主要目的是将过期键从缓存中删除。异常,如果你想要自动过期的话,那么你需要启用master。
Web缓存¶
在其第一次成型的时候,uWSGI缓存框架仅仅是用于缓存web页面。老系统已经被重新构建。它现在名为
WebCaching框架 。启用旧式的 --cache
选项将会创建一个名为”default”的缓存。
监控缓存¶
统计信息服务器公开了缓存信息。存在一个基于ncurses的工具 (https://pypi.python.org/pypi/uwsgicachetop) ,它使用该信息来进行实时监控。
WebCaching框架¶
注解
这是旧的缓存子系统到记录在这里 uWSGI缓存框架 的新的uWSGI缓存API的移植。 使用这里的选项将会创建一个名为“default”的新型缓存。
要启用web缓存,使用 cache
选项为你的项分配槽。以下的命令行将会创建一个包含至少1000个项的缓存。
./uwsgi --socket 127.0.0.1:3031 --module mysimpleapp --master --processes 4 --cache 1000
要在应用中使用这个缓存,
uwsgi.cache_set("foo_key", "foo_value") # set a key
value = uwsgi.cache_get("foo_key") # get a key.
持久化存储¶
你可以把缓存数据存储在一个后备存储文件来实现持久化。简单添加 cache-store <filename>
选项。每个内核都会以一个不同的速率提交数据到磁盘。你可以使用 cache-store-sync <n>
设置是否/何时强制,其中, n
是每次磁盘同步之前等待的master周期数。
缓存清道夫¶
自uWSGI 1.2起,缓存项过期是由 master 进程中的一个线程进行管理的,以减少死锁的风险。这个线程可以通过 cache-no-expire
选项禁用 (让缓存项过期成为空操作)。
可以用 cache-expire-freq <seconds>
设置缓存清道夫线程的频率。你可以通过使用 cache-report-freed-items
来让清道夫记录可用项数目。
直接从你的web服务器访问缓存¶
location / {
uwsgi_pass 127.0.0.1:3031;
uwsgi_modifier1 111;
uwsgi_modifier2 3;
uwsgi_param key $request_uri;
}
就这样啦!Nginx现在会从一个远程uwsgi协议兼容的服务器获取HTTP响应。虽然老实说,它并不非常有用,因为如果缓存未命中,那么你将会看到你一个空白页。
一个会回退到真正的uwsgi请求的更好的系统是
location / {
uwsgi_pass 192.168.173.3:3032;
uwsgi_modifier1 111;
uwsgi_modifier2 3;
uwsgi_param key $request_uri;
uwsgi_pass_request_headers off;
error_page 502 504 = @real;
}
location @real {
uwsgi_pass 192.168.173.3:3032;
uwsgi_modifier1 0;
uwsgi_modifier2 0;
include uwsgi_params;
}
Django缓存后端¶
如果你运行着Django,那么有一个已经可以使用的名为 django-uwsgi-cache
的应用。它由Ionel Cristian Mărieș维护,位于https://github.com/ionelmc/django-uwsgi-cache,并且在pypi上。
uWSGI类cron接口¶
uWSGI的 master 拥有一个内部类cron的工具,它可以在预设的时间生成事件。你可以这样用它
- 通过uWSGI API,在这种情况下,cron事件将会生成uWSGI信号
- 直接通过选项,在这种情况下,事件将会运行shell命令
基于uWSGI信号¶
uwsgi.add_cron()
函数是uWSGI信号cron工具的接口。语法如下
uwsgi.add_cron(signal, minute, hour, day, month, weekday)
后面5个参数与标准的crontab工作原理相似,但是用-1代替”*”,用-2,-3等代替”/2”, “/3”等。
import uwsgi
def five_o_clock_on_the_first_day_of_the_month(signum):
print "It's 5 o'clock of the first day of the month."
uwsgi.register_signal(99, "", five_o_clock_on_the_first_day_of_the_month)
uwsgi.add_cron(99, 0, 5, 1, -1, -1)
定时器 VS. cron¶
没有设置到指定日期的周期性事件应该使用定时器/红黑定时器。但你对指定日期/小时感兴趣的时候,使用cron。
例如:
uwsgi.add_cron(99, -1, -1, -1, -1, -1) # ugly, bad and inefficient way to run signal 99 every minute :(
uwsgi.add_timer(99, 60) # much better.
注意¶
day
和weekday
像原始crontab那样进行或运算。- 默认情况下,你可以为每个master最多定义64个基于信号的cron任务。可以在
uwsgi.h
中增加这个值。
基于选项的cron¶
你可以使用 cron
选项,在配置中直接定义cron任务。可以指定无数个基于选项的cron记录。语法与基于信号的cron相同。
例如:
[uwsgi]
cron = 59 2 -1 -1 -1 /usr/bin/backup_my_home --recursive
cron = 9 11 -1 -1 2 /opt/dem/bin/send_reminders
<uwsgi>
<cron>59 2 -1 -1 -1 /usr/bin/backup_my_home --recursive</cron>
<cron>9 11 -1 -1 2 /opt/dem/bin/send_reminders</cron>
</uwsgi>
[uwsgi]
; every two hours
cron = -1 -2 -1 -1 -1 /usr/bin/backup_my_home --recursive
Legion cron¶
当你的实例是 uWSGI Legion子系统 的一部分时,你可以配置它只在它是指定Legion的Lord时才运行cron。
[uwsgi]
legion = mycluster 225.1.1.1:1717 100 bf-cbc:hello
legion-node = mycluster 225.1.1.1:1717
; every two hours
legion-cron = mycluster -1 -2 -1 -1 -1 /usr/bin/backup_my_home --recursive
Unique crons¶
注解
这个特性自1.9.11起可用。
一些命令可能需要较长的时间来完成,或者只是挂在那里执行它们的任务。有时,这没问题,但还是有一些情况,运行相同命令的多个实例可能是危险的。
对于这种情况,增加了 unique-cron
和 unique-legion-cron
选项。语法与 cron
和 legion-cron
相同,但是不同在于,uWSGI将会跟踪执行状态,并且在它完成之前,并不会再次执行cronjob。
例子:
[uwsgi]
cron = -1 -1 -1 -1 -1 sleep 70
这会在每分钟执行 sleep 70
,但是sleep命令将会比我们的执行时间间隔还要长,最后,将会得到一个数目增长的sleep进程。要修复这个问题,我们可以简单的用 unique-cron
替换 cron
,uWSGI会确保只有一个sleep进程在运行。一个新的进程会在前一个结束的时候立即启动。
Harakiri¶
注解
自1.9.11起可用。
--cron-harakiri
将会强制命令执行的时间。如果有命令花费更长的时间,那么将会杀死这个命令。
[uwsgi]
cron = sleep 30
cron-harakiri = 10
这会在10秒后杀死cron命令。注意, cron-harakiri
是全局限制,它影响的是所有的cron命令。要设置一个用单一命令上的限制,使用 cron2
选项 (见下)。
cron选项的新语法¶
注解
自1.9.11起可用
为了对cron更好的控制,一个新的选项被添加到uWSGI:
[uwsgi]
cron2 = option1=value,option2=value command to execute
例子:
[uwsgi]
cron2 = minute=-2,unique=1 sleep 130
将会每2分钟生成一个唯一的cron命令 sleep 130
。
选项列表是可选的,每个cron可用的选项如下:
minute
- crontab的分钟部分,默认是-1 (解析为*)hour
- crontab的小时部分,默认是-1 (解析为*)day
- crontab的天部分,默认是-1 (解析为*)month
- crontab的月部分,默认是-1 (解析为*)week
- crontab的周部分,默认是-1 (解析为*)unique
- 将cron命令标记为唯一 (见上),默认是0 (不唯一)harakiri
- 为该cron命令设置harakiri超时时间 (以秒为单位),默认是0 (不使用harakiri)legion
- 为这个cron命令设置使用的legion名,cron legion只有在legion lord节点上才会执行。
注意,你不能在选项列表中使用空格。(minute=1, hour=2
将不会工作,但是 minute=1,hour=2
则是可以的。)
如果缺失了任何选项,那么就会使用默认值。
[uwsgi]
# execute ``my command`` every minute (-1 -1 -1 -1 -1 crontab).
cron2 = my command
# execute unique command ``/usr/local/bin/backup.sh`` at 5:30 every day.
cron2 = minute=30,hour=5,unique=1 /usr/local/bin/backup.sh
[uwsgi]
legion = mycluster 225.1.1.1:1717 100 bf-cbc:hello
legion-node = mycluster 225.1.1.1:1717
cron2 = minute=-10,legion=mycluster my command
这会对 my command
禁用harakiri,但是其他的cron命令仍然会在10秒后被杀死:
[uwsgi]
cron-harakiri = 10
cron2 = harakiri=0 my command
cron2 = my second command
uWSGI FastRouter¶
对于高级设置,uWSGI包含了”fastrouter”插件,它是一个代理/负载均衡器/路由器,使用uwsgi协议。默认内建它。你可以将其放在你的web服务器和真正的uWSGI实例之间,以获取对于HTTP请求到你的应用服务器的路由的更多控制。
开始¶
首先,你必须运行fastrouter,将它绑定到一个指定的地址上。也支持多个地址。
uwsgi --fastrouter 127.0.0.1:3017 --fastrouter /tmp/uwsgi.sock --fastrouter @foobar
注解
这是世界上最没用的Fastrouter设置。
恭喜你!你刚刚运行了世界上最没用的Fastrouter设置。简单将fastrouter绑定到一堆地址上并不会指示它如何路由请求。要赋予它智能,你必须告诉它如何路由请求。
方法1:–fastrouter-use-base¶
这个选项将会告诉fastrouter连接到一个UNIX socket,这个socket的名字与指定目录中的请求主机的名字相同。
uwsgi --fastrouter 127.0.0.1:3017 --fastrouter-use-base /tmp/sockets/
如果你接收到一个对 example.com
的请求,那么fastrouter将会转发请求到 /tmp/sockets/example.com
。
方法2:–fastrouter-use-pattern¶
与前面的设置相同,但是你将能够使用模式,其中, %s
映射到请求的键/主机名。
uwsgi --fastrouter 127.0.0.1:3017 --fastrouter-use-pattern /tmp/sockets/%s/uwsgi.sock
对 example.com
的请求将会被映射到
/tmp/sockets/example.com/uwsgi.sock
.
方法3:–fastrouter-use-cache¶
你可以在 uWSGI cache 中存储键值映射。选择一种方式去填充缓存,例如,一个像这样的Python脚本……
import uwsgi
# Requests for example.com on port 8000 will go to 127.0.0.1:4040
uwsgi.cache_set("example.com:8000", "127.0.0.1:4040")
# Requests for unbit.it will go to 127.0.0.1:4040 with the modifier1 set to 5 (perl/PSGI)
uwsgi.cache_set("unbit.it", "127.0.0.1:4040,5")
然后运行你启用了Fastrouter的服务器,告诉它首先运行脚本。
uwsgi --fastrouter 127.0.0.1:3017 --fastrouter-use-cache --cache 100 --file foobar.py
方法4:–fastrouter-subscription-server¶
这可能是用于大量自动扩展托管的最好的方式之一。它使用 subscription server 来允许实例宣告自己,并订阅到fastrouter。
uwsgi --fastrouter 127.0.0.1:3017 --fastrouter-subscription-server 192.168.0.100:7000
这将会在地址192.168.0.100,端口7000上生成一个订阅服务器
现在,你可以生成订阅到fastrouter的实例了:
uwsgi --socket :3031 -M --subscribe-to 192.168.0.100:7000:example.com
uwsgi --socket :3032 -M --subscribe-to 192.168.0.100:7000:unbit.it,5 --subscribe-to 192.168.0.100:7000:uwsgi.it
正如你可能注意到的,你可以订阅多个fastrouter,使用多个键。具有相同的键的订阅到相同fastrouter的多个实例将会自动负载均衡,并被监控。很方便,不是吗?就像缓存键/值存储,可以用一个逗号来设置 modifier1
。(上面是 ,5
) 订阅系统的另一个特性是避免选择端口。你可以将实例绑定到随机端口,而订阅系统将会发送真实的值到订阅服务器上。
uwsgi --socket 192.168.0.100:0 -M --subscribe-to 192.168.0.100:7000:example.com
映射文件¶
如果你需要指定大量的键,那么你可以使用一个映射文件来取代。
# mappings.txt
unbit.it
unbit.it:8000,5
uwsgi.it
projects.unbit.it
uwsgi --socket :3031 -M --subscribe-to 192.168.0.100:7000:@mappings.txt
方法5:–fastrouter-use-code-string¶
如果Darth Vader穿着一个带有你的脸的T恤 (还要其他一些边界情况),那么你可以使用代码驱动的映射来自定义fastrouter。选择一个支持uWSGI的语言 (就像Python或者Lua),然后定义你的映射函数。
def get(key):
return '127.0.0.1:3031'
uwsgi --fastrouter 127.0.0.1:3017 --fastrouter-use-code-string 0:mapper.py:get
这将会指示fastrouter使用插件(modifier1) 0加载脚本 mapper.py
,并且调用’get’全局函数,传给它key值。在前面的例子中,你会总是路由请求到127.0.0.1:3031。让我们创建一个更高级的系统,只是为了好玩!
domains = {}
domains['example.com'] = {'nodes': ('127.0.0.1:3031', '192.168.0.100:3032'), 'node': 0}
domains['unbit.it'] = {'nodes': ('127.0.0.1:3035,5', '192.168.0.100:3035,5'), 'node': 0}
DEFAULT_NODE = '192.168.0.1:1717'
def get(key):
if key not in domains:
return DEFAULT_NODE
# get the node to forward requests to
nodes = domains[key]['nodes']
current_node = domains[key]['node']
value = nodes[current_node]
# round robin :P
next_node = current_node + 1
if next_node >= len(nodes):
next_node = 0
domains[key]['node'] = next_node
return value
uwsgi --fastrouter 127.0.0.1:3017 --fastrouter-use-code-string 0:megamapper.py:get
只需短短几行,我们就实现了一个带有回退节点的循环负载均衡。Pow! 你可以添加某些形式的节点监控,在脚本中启动线程,或者其他疯狂的东东。 (确保将其添加到文档中!)
注意
记住,不要在你的函数中写阻塞代码。fastrouter是完全非阻塞的,不要毁了它!
Cheap模式和共享socket¶
一个常见的设置是让一个web服务器/代理连接到一个fastrouter,并且让一系列的uWSGI实例订阅到它上面。通常请情况下,你会将web服务器节点当成一个uWSGI实例节点使用。这个节点将会订阅到本地fastrouter。好吧……不要在上面浪费时间周期!共享socket是一种在各种uWSGI部件之间共享socket的方式。让我们使用它在fastrouter和uWSGI实例之间共享socket。
[uwsgi]
;create a shared socket (the webserver will connect to it)
shared-socket = 127.0.0.1:3031
; bind the fastrouter to the shared socket
fastrouter = =0
; bind an instance to the same socket
socket = =0
; having a master is always a good thing...
master = true
; our subscription server
fastrouter-subscription-server = 192.168.0.100:4040
; our app
wsgi-file = /var/www/myheavyapp.wsgi
; a bunch of processes
processes = 4
; and put the fastrouter in cheap mode
fastrouter-cheap = true
使用这个设置,你的请求将会直接到达你的应用(无代理开销),或者到fastrouter (传递请求给远程节点)。当fastrouter处于cheap模式的时候,它将不会响应请求,直到节点可用。这意味着,当没有订阅的节点的时候,只有你本地的应用会响应。当所有的节点都挂掉的时候,fastrouter将会回到cheap模式。看到套路了吗?另一个到棒棒哒的自动缩放的设置。
Post-buffering模式 (uWSGI >= 2.0.9)¶
fastrouter (默认情况下) 是一个流代理。这意味着,一旦解析了uwsgi包 (即,请求头部),它就会被转发到后端(们)。
现在,如果你的web代理也是一个流代理 (例如apache,或者uWSGI http路由器),那么你的应用在带有请求体的请求下会被阻塞很长一段时间。说得更明白一点:
- 客户端启动请求,发送http头部
- web代理接收它,然后发送给fastrouter
- fastrouter接收它,然后发送给后端
- 客户端开始发送请求体块 (例如文件上传)
- web代理接收它们,然后转发给fastrouter
- fastrouter接收它们,然后转发给后端,以此类推
现在,想象有10个并发的客户端在做这件事,你将会得到10个处于忙碌状态不确定时间的应用服务器worker(或者线程)。(注意,这个问题会被这样一个事实放大:一般来说,线程/进程的数目是非常受限的,甚至是处于异步模式也是如此,因此,你有一个有限的并发请求,但是它通常如此之高,以至于这个问题没那么相关)
像nginx这样的web代理是会“缓冲”的,因此它们会等待,直到整个请求(及其请求体)读完,然后再将其发送到后端。
你可以用 --fastrouter-post-buffering <n>
选项指示fastrouter像nginx一样,其中,<n>是指,请求体到多大之后,将会被存储到磁盘(作为临时文件),而不是内存:
[uwsgi]
fastrouter = 127.0.0.1:3031
fastrouter-to = /var/run/app.socket
fastrouter-post-buffering = 8192
将会把fastrouter置于缓冲模式,每当请求体大于8192字节,就会把它存储到一个临时文件中,而当小于(或等于)时,则会存在内存里
记住,post-buffering,并非一本万利的解决方法 (否则,会默认使用它),启用它会破坏websockets,块输入,上传过程等等等等。只在需要的时候启用它。
注意¶
- fastrouter使用以下变量 (按优先顺序) 来选择使用的键:
UWSGI_FASTROUTER_KEY
- 最通用的,因为它不依赖于以任何方式的请求HTTP_HOST
SERVER_NAME
- 你可以使用–fastrouter-events增加fastrouter可以管理的异步事件数目 (默认情况下,它是依赖于系统的)
你可以用–fastrouter-timeout修改默认超时时间。默认情况下,当通过unix socket使用的时候,fastrouter会设置fd socket passing。如果你不想要它,那么添加–no-fd-passing
uWSGI内部路由¶
更新至1.9
自uWSGI 1.9起,提供了一个可编程的内部路由子系统。 (1.1版本之后的旧版本有一个不大起眼的版本)。你可以使用内部路由子系统来动态改变处理请求的方式。例如,你可以用它来触发对于指定URL的301重定向,或者在指定条件下提供来自于缓存的内容。这个内部路由子系统的灵感来源于Apache的 mod_rewrite
和Linux的 iptables
命令。
在喷它是凌乱、不优雅或者不图灵完备之前,请记住,它必须快并且只是快。如果你需要优雅以及更多的复杂度,那么你可以在自己的代码中实现。
路由链¶
在请求周期内,会经过不同的“链”。每个链包含一个路由表 (见下)。
链可以是“递归的”,一个“递归”链在一个请求周期内可以被多次调用。
这是链的顺序:
request
在请求被传递给插件之前应用
error
一个生成了一个HTTP状态码就会被立即应用(递归链)
response
在生成了最后一个响应头后被应用 (仅在发送响应体之前)
final
在已经发送响应到客户端之后被应用
request
链是 (为了方便起见) 是“默认的”链,因此它的选项没有前缀,而其他的链需要前缀。
例如:
route-user-agent
-> 用于request链
而
response-route-uri
-> 用于response链
内部路由表¶
内部路由表是一个接一个(也允许向前跳跃)执行的“规则”序列。每个规则由一个’‘subject(标题)’‘,一个’‘condition(条件)’‘和一个’‘action(动作)’‘组成。’‘条件’‘通常是一个应用到标题的PCRE正则表达式:如果匹配上了,那么就会触发相应的动作。标题是请求的变量。目前支持以下标题:
host
(检查HTTP_HOST)uri
(检查REQUEST_URI)qs
(检查QUERY_STRING)remote-addr
(检查REMOTE_ADDR)remote-user
(检查REMOTE_USER)referer
(检查HTTP_REFERER)user-agent
(检查HTTP_USER_AGENT)status
(检查HTTP响应状态码,在request链中不可用)default
(默认标题,映射到PATH_INFO)
除此之外,有一个可用的低层次条件可插拔系统。你可以使用 --route-if
选项来访问此系统。目前,支持以下检查:
exists
(检查标题是否存在于文件系统)isfile
(检查标题是否是一个文件)isdir
(检查标题是否是一个目录)isexec
(检查标题是否是一个可执行文件)equal
/isequal
/eq
/==
(检查标题是否等于指定模式)ishigherequal
/>=
ishigher
/>
islower
/<
islowerequal
/<=
startswith
(检查标题是否以指定模式开头)endswith
(检查标题是否以指定模式结尾)regexp
/re (检查标题是否匹配指定的正则表达式)empty
(检查标题是否为空)contains
当一次检查需要一个模式(例如使用’equal’或者’regexp’)时,你可以使用分号将它从标题分隔开:
; never matches
route-if = equal:FOO;BAR log:never here
; matches
route-if = regexp:FOO;^F log:starts with F
动作是当一个规则匹配的时候要运行的汗水。这些动作由插件导出,并有一个返回值。
动作返回值¶
每个动作都有一个返回值,这个返回值告诉路由引擎接下来要做什么。支持以下返回码:
NEXT
(继续下一个规则)CONTINUE
(停止扫描内部路由表并运行请求)BREAK
(停止扫描内部路由表并关闭请求)GOTO x
(跳到规则x
)
当一个规则不匹配的时候,则假设 NEXT
。
第一个例子¶
[uwsgi]
route-user-agent = .*curl.* redirect:http://uwsgi.it
route-remote-addr = ^127\.0\.0\.1$ break:403 Forbidden
route = ^/test log:someone called /test
route = \.php$ rewrite:/index.php
route = .* addheader:Server: my uWSGI server
route-host = ^localhost$ logvar:local=1
route-uri = ^/foo/(.*)\.jpg$ cache:key=$1.jpg
route-if = equal:${PATH_INFO};/bad break:500 Internal Server Error
前面的规则构建以下表:
- 如果
HTTP_USER_AGENT
变量包含’curl’,那么重定向请求到 http://uwsgi.it (状态码为302,动作返回BREAK) - 如果
REMOTE_ADDR
是‘127.0.0.1’,则返回403 Forbidden (动作返回 BREAK) - 如果
PATH_INFO
以/test开头,则在日志中打印字符串’someone called /test’ (动作返回NEXT) - 如果
PATH_INFO
以’.php’结尾,那么将其重写至/index.php (动作返回 NEXT) - 对于所有的
PATH_INFO
,添加HTTP头’Server: my uWSGI server’到响应中 (动作返回NEXT) - 如果
HTTP_HOST
是localhost,那么添加日志变量’local’,并将其设置为‘1’ - 如果
REQUEST_URI
以/foo开头,并且以.jpg结尾,那么使用附带的键(通过正则表达式分组构建)从uWSGI缓存中获取它 (动作返回 BREAK) - 如果
PATH_INFO
等于/bad,那么抛出500错误
访问请求变量¶
除了PCRE占位符/组 (使用$1到$9),你可以使用${VAR}语法来访问请求变量 (PATH_INFO, SCRIPT_NAME, REQUEST_METHOD...)。
[uwsgi]
route-user-agent = .*curl.* redirect:http://uwsgi.it${REQUEST_URI}
访问cookie¶
你可以使用${cookie[name]}语法来访问一个cookie值:
[uwsgi]
route = ^/foo log:${cookie[foobar]}
这将会在日志中记录当前请求的’foobar’ cookie内容
访问查询字符串¶
你可以使用${qs[name]}语法访问HTTP请求字符串的值:
[uwsgi]
route = ^/foo log:${qs[foobar]}
这将会在日志中记录当前请求的查询字符串的’foobar’项的内容
可插拔路由变量¶
cookie和qs变量都是所谓的“路由变量”。它们是可插拔的,因此外部插件可以添加新的变量来添加新特性到你的应用上。 (看看 GeoIP插件 插件,以获取关于这个的一个例子。) 还有一些可用的嵌入式路由变量。
mime
– 返回指定变量的mime类型:${mime[REQUEST_URI]}[uwsgi] route = ^/images/(.+) addvar:MYFILE=$1.jpg route = ^/images/ addheader:Content-Type: ${mime[MYFILE]}
time
– 以多种形式返回时间/日期。唯一支持(目前)的是time[unix],返回时代httptime
– 返回http日期,添加数值参数 (如果指定) 来获得当前时间 (使用空参数来获取当前的服务器时间)
[uwsgi]
; add Date header
route-run = addheader:Date ${httptime[]}
math
– 需要matheval支持。例如:math[CONTENT_LENGTH+1]base64
– 将指定的变量用base64编码hex
– 将指定的变量用hex编码upper
– 将指定变量转换成大写lower
– 将指定变量转换成小写uwsgi
– 返回内部的uWSGI信息,目前支持uwsgi[wid], uwsgi[pid], uwsgi[uuid]和uwsgi[status]
–route-if不够吗?为什么还要–route-uri和其他小伙伴?¶
这是个好问题。你只需要一直记住,uWSGI是关于通用性和 性能 的。可以循环利用总是好的。
--route-if
选项,虽然通用,但是不能够被优化,因为在处理每一个请求的时候都必须重新计算它所有的部分。这显然非常快,但是
--route-uri
选项 (和其他小伙伴)可以被预先优化 (在启动期间) 来直接映射到请求内存区域,因此,如果你可以使用它们,那么你绝对应该用它们。 :)
GOTO¶
是哒,整个信息技术产业(和历史)最具争议的结构就是这里。你可以向前 (只能向前!)跳到内部路由表的特定点。你可以设置标签来标记路由表的特定点,或者如果你勇敢 (或者蠢),直接跳到一个规则变化。规则编号会在服务器启动的时候打印处理,但请用标签。
[uwsgi]
route-host = ^localhost$ goto:localhost
route-host = ^sid\.local$ goto:sid.local
route = .* last:
route-label = sid.local
route-user-agent = .*curl.* redirect:http://uwsgi.it
route-remote-addr = ^192\.168\..* break:403 Forbidden
route = ^/test log:someone called /test
route = \.php$ rewrite:/index.php
route = .* addheader:Server: my sid.local server
route = .* logvar:local=0
route-uri = ^/foo/(.*)\.jpg$ cache:key=$1.jpg
route = .* last:
route-label = localhost
route-user-agent = .*curl.* redirect:http://uwsgi.it
route-remote-addr = ^127\.0\.0\.1$ break:403 Forbidden
route = ^/test log:someone called /test
route = \.php$ rewrite:/index.php
route = .* addheader:Server: my uWSGI server
route = .* logvar:local=1
route-uri = ^/foo/(.*)\.jpg$ cache:key=$1.jpg
route = .* last:
这个例子像前面那个,但域之间存在些不同。看看”last:”的时候,来终端路由表扫描。你可以将前2个规则写成一个:
[uwsgi]
route-host = (.*) goto:$1
收集响应头¶
正如我们已经看到的,每个uWSGI请求都有一组相关的变量。它们一般是由web服务器传递的CGI变量,但是你也可以用其他变量来扩展它们 (看看’addvar’动作)。
uWSGI 1.9.16添加了一个新特性,允许你存储一个响应头的内容到一个请求变量中。这让编写更高级的规则变得更简单。
例如,你可能想要gzip所有的text/html响应:
[uwsgi]
; store Content-Type response header in MY_CONTENT_TYPE var
collect-header = Content-Type MY_CONTENT_TYPE
; if response is text/html, and client supports it, gzip it
response-route-if = equal:${MY_CONTENT_TYPE};text/html goto:gzipme
response-route-run = last:
response-route-label = gzipme
; gzip only if the client support it
response-route-if = contains:${HTTP_ACCEPT_ENCODING};gzip gzip:
可用动作¶
break
¶
返回值: BREAK
停止扫描内部路由表,并关闭该请求。可以选择返回指定的HTTP状态码:
[uwsgi]
route = ^/notfound break:404 Not Found
route = ^/bad break:
route = ^/error break:500
注意: break
并不支持请求变量,因为它旨在通知浏览器有关错误,而不是通知终端用户。也就是说,我们可以判定以下代码将会发送它读取的内容给浏览器 (即 ${REMOTE_ADDR}
并不会被转换成远程IP地址)。
[uwsgi]
route-remote-addr = ^127\.0\.0\.1$ break:403 Forbidden for ip ${REMOTE_ADDR}
如果你真的想要做些古古怪怪的东西,那么可以看看 clearheaders
。
return
/break-with-status
¶
返回值: BREAK
return
使用uWSGI内置的状态码并同时返回状态码和消息体。它类似于 break
,但正如上面提到的, break
并没有错误消息体。 return:403
等价于:
[uwsgi]
route-run = clearheaders:403 Forbidden
route-run = addheader:Content-Type: text/plain
route-run = addheader:Content-Length: 9
route-run = send:Forbidden
route-run = break:
send-crnl
¶
返回值: NEXT
非常高级 (且危险) 的函数,允许你添加原始数据到响应中,以rn作为后缀。
[uwsgi]
route = ^/foo/(.) send-crnl:HTTP/1.0 100 Continue
redirect-permanent
/redirect-301
¶
返回值: BREAK
插件: router_redirect
返回一个HTTP 3301 Permanent Redirect给指定的URL。
rewrite
¶
返回值: NEXT
插件: router_rewrite
一个重写引擎,灵感来自于Apache mod_rewrite。在请求被派发给请求处理函数之前,根据指定的规则重建PATH_INFO和 QUERY_STRING。
[uwsgi]
route-uri = ^/foo/(.*) rewrite:/index.php?page=$1.php
rewrite-last
¶
rewrite的别名,但是返回值是 CONTINUE
, 直接传递请求到请求处理器next。
uwsgi
¶
返回值: BREAK
插件: router_uwsgi
重写一个请求的modifier1, modifier2和可选的 UWSGI_APPID
值,或者路由请求到一个外部的uwsgi服务器。
[uwsgi]
route = ^/psgi uwsgi:127.0.0.1:3031,5,0
这个配置路由所有以 /psgi
开始的请求到在127.0.0.1:3031上运行的uwsgi服务器,设置modifier1为5,设置modifier2为0.如果你只想修改modifier1/modifier2,而不想将请求路由到一个外部服务器,那么使用以下语法。
[uwsgi]
route = ^/psgi uwsgi:,5,0
要设置一个指定的 UWSGI_APPID
值,在其后附加。
[uwsgi]
route = ^/psgi uwsgi:127.0.0.1:3031,5,0,fooapp
子请求是异步型的 (支持诸如gevent或者ugreen这样的引擎),如果offload线程可用,那么将会使用它们。
http
¶
返回值: BREAK
插件: router_http
将请求路由到外部HTTP服务器。
[uwsgi]
route = ^/zope http:127.0.0.1:8181
你可以使用以下语法来替换一个替代的Host头:
[uwsgi]
route = ^/zope http:127.0.0.1:8181,myzope.uwsgi.it
static
¶
返回值: BREAK
插件: router_static
提供来自指定物理路径的静态文件。
[uwsgi]
route = ^/logo static:/var/www/logo.png
basicauth
¶
返回值: NEXT
or BREAK 401
on failed authentication
插件: router_basicauth
支持四种语法。
basicauth:realm,user:password
– 一个简单的用户名:密码映射basicauth:realm,user:
– 只鉴权用户名basicauth:realm,htpasswd
– 使用一个类htpasswd文件。支持所有的POSIX crypt()算法。这与Apache传统的htpasswd文件 _不_ 同,因此,使用htpasswd工具的-d
标志来创建兼容文件。basicauth:realm,
– 用来立即引起一个HTTP 401响应。 由于路由是从上至下解析的,因此,你也许想要引发这个来避免绕过规则。
例如:
[uwsgi]
route = ^/foo basicauth-next:My Realm,foo:bar
route = ^/foo basicauth:My Realm,foo2:bar2
route = ^/bar basicauth:Another Realm,kratos:
例如: 对Trac使用basicauth
[uwsgi]
; load plugins (if required)
plugins = python,router_basicauth
; bind to port 9090 using http protocol
http-socket = :9090
; set trac instance path
env = TRAC_ENV=myinstance
; load trac
module = trac.web.main:dispatch_request
; trigger authentication on /login
route = ^/login basicauth-next:Trac Realm,pippo:pluto
route = ^/login basicauth:Trac Realm,foo:bar
;high performance file serving
static-map = /chrome/common=/usr/local/lib/python2.7/dist-packages/trac/htdocs
basicauth-next
¶
与 basicauth
相同,但在鉴权失败的时候返回 NEXT
。
ldapauth
¶
返回值:鉴权失败的时候返回 NEXT
或者 BREAK 401
插件: ldap
这个鉴权路由器是LDAP插件的一部分,因此为了让这个能用,必须加载它。它就像basicauth路由器,但是使用一个LDAP服务器来进行鉴权,语法: ldapauth:realm,options
可用选项:
url
- LDAP服务器URI (必填)binddn
- 用于绑定的DN。如果LDAP服务器不允许匿名搜索,那么该选项必填。bindpw
-binddn
用户的密码basedn
- 搜索用户时使用的基本DN (必填)filter
- 搜索用户时使用的过滤器 (默认是 “(objectClass=*)”)attr
- LDAP属性,保存用户登录 (默认是”uid”)loglevel
- 0 - 不记录任何绑定,1 - 记录鉴权错误,2 - 同时记录成功和失败的绑定
例如:
route = ^/protected ldapauth:LDAP auth realm,url=ldap://ldap.domain.com;basedn=ou=users,dc=domain;binddn=uid=proxy,ou=users,dc=domain;bindpw=password;loglevel=1;filter=(objectClass=posixAccount)
ldapauth-next
¶
与ldapauth相同,但是鉴权失败的时候返回 NEXT
。
cachestore
/cache-store
¶
cachevar
¶
cacheset
¶
memcached
¶
rpc
¶
“rpc”路由指令让你可以直接调用来自路由子系统的uWSGI RPC函数,并且将它们的输出转发到客户端。
[uwsgi]
http-socket = :9090
route = ^/foo addheader:Content-Type: text/html
route = ^/foo rpc:hello ${REQUEST_URI} ${HTTP_USER_AGENT}
route = ^/bar/(.+)$ rpc:test $1 ${REMOTE_ADDR} uWSGI %V
route = ^/pippo/(.+)$ rpc:test@127.0.0.1:4141 $1 ${REMOTE_ADDR} uWSGI %V
import = funcs.py
call
¶
插件: rpc
rpcraw
¶
插件: rpc
access
¶
spnego
¶
开发中……
radius
¶
开发中……
donotlog
¶
chdir
¶
seturi
¶
更新 REQUEST_URI
setapp
¶
setuser
¶
sethome
¶
setfile
¶
setscriptname
¶
setprocname
¶
alarm
¶
flush
¶
fixcl
¶
cgi
¶
插件: cgi
cgihelper
¶
插件: cgi
access
¶
插件: router_access
cache-continue
¶
插件: router_cache
cachevar
¶
插件: router_cache
cacheinc
¶
插件: router_cache
cachedec
¶
插件: router_cache
cachemul
¶
插件: router_cache
cachediv
¶
插件: router_cache
proxyhttp
¶
插件: router_http
memcached
¶
插件: router_memcached
memcached-continue
¶
插件: router_memcached
memcachedstore
¶
插件: router_memcached
memcached-store
¶
插件: router_memcached
redis
¶
插件: router_redis
redis-continue
¶
插件: router_redis
redisstore
¶
插件: router_redis
redis-store
¶
插件: router_redis
proxyuwsgi
¶
插件: router_uwsgi
harakiri
¶
为当前请求设置harakiri。
file
¶
在 不 使用加速(sendfile, offloading等等)的情况下直接转移指定的文件名。
[uwsgi]
http-socket = :9090
route-run = file:filename=/var/www/${PATH_INFO}
clearheaders
¶
清除响应头,设置一个新的HTTP状态码,可用于重置响应
[uwsgi]
http-socket = :9090
response-route = ^/foo goto:foobar
response-route-run = last:
response-route-label = foobar
response-route-run = clearheaders:404 Not Found
response-route-run = addheader:Content-Type: text/html
resetheaders
¶
clearheaders的别名
xattr
¶
插件: xattr
xattr插件允许你在内部路由子系统中引用文件扩展属性:
[uwsgi]
...
route-run = addvar:MYATTR=user.uwsgi.foo.bar
route-run = log:The attribute is ${xattr[/tmp/foo:MYATTR]}
或者 (带2个变量的变体)
[uwsgi]
...
route-run = addvar:MYFILE=/tmp/foo
route-run = addvar:MYATTR=user.uwsgi.foo.bar
route-run = log:The attribute is ${xattr2[MYFILE:MYATTR]}
仅在Linux平台上可用。
uWSGI Legion子系统¶
自uWSGI 1.9-dev起,已经添加了一个用于集群的新的子系统:Legion子系统。一个Legion是一组不断争取主导的uWSGI节点。每个节点都有一个武力值 (如果可能的话,彼此间该值不同)。具有最高武力值的节点就是这个Legion的Lord (或者如果你喜欢不大游戏的说法,嗯,更工程师点的术语是:master)。这种不断的斗争会生成7种事件:
setup
- legion子系统在一个节点上启动的时候join
- 在第一次到达仲裁时,只有在新加入的节点上lord
- 当该节点成为lordunlord
- 当这个节点失去了lord称号death
- 当legion子系统关闭的时候node-joined
- 当任何新节点加入到我们的legion的时候node-left
- 当任何节点离开我们的legion的时候
每当这样的一个事件发生的时候,你都可以触发操作。
注意:要构建带Legion支持的uWSGI,则必须安装 openssl
头文件。
IP接管¶
这对于集群环境来说,是一个非常常见的配置。IP地址是一种必须只由一个节点拥有的资源。对于这个例子而言,那个节点就是我们的Lord。如果我们正确的配置了一个Legion (记住,一个uWSGI实例可以是所有你需要的legion的成员),那么我们可以很容易实现IP接管。
[uwsgi]
legion = clusterip 225.1.1.1:4242 98 bf-cbc:hello
legion-node = clusterip 225.1.1.1:4242
legion-lord = clusterip cmd:ip addr add 192.168.173.111/24 dev eth0
legion-lord = clusterip cmd:arping -c 3 -S 192.168.173.111 192.168.173.1
legion-setup = clusterip cmd:ip addr del 192.168.173.111/24 dev eth0
legion-unlord = clusterip cmd:ip addr del 192.168.173.111/24 dev eth0
legion-death = clusterip cmd:ip addr del 192.168.173.111/24 dev eth0
在这个例子中,我们加入了一个名为 clusterip
的legion。要从其他节点接收消息,那么绑定到多播地址225.1.1.1:4242。这个节点的武力值会是98,而每一条消息将会使用CBC中的Blowfish算法,使用共享密钥 hello
进行加密。 legion-node
选项制定了我们宣布消息的目的地。由于我们使用多播,因此只需要制定一个单一的“节点”。最后的选项是在该集群的不同阶段触发的动作。对于一个IP接管解决方案,我们简单依赖于Linux的 iproute
命令来设置/取消设置ip地址,发送一个额外的ARP消息来宣告这种改变。显然,这个特定的例子需要root选项,或者 CAP_NET_ADMIN
Linux能力。因此,确保不要在与管理IP接管相同的uWSGI实例上运行不可信应用。
仲裁集¶
要选择一个Lord,Legion中的每个成员都要进行投票。当一个legion中所有活动成员都同意选择一个Lord的时候,那么这个Lord就会当选,而老的Lord会被降级。每当加入一个新的节点,或者有节点离开的时候,就会重新计算仲裁集,然后被记录到整个集群中。
选择Lord¶
一般来讲,具有较高武力值的节点会被选为Lord,但是可能会出现多个节点具有相同武力值的情况。当启动一个节点的时候,会给它分配一个UUID。如果两个节点具有相同的武力值,那么拥有较高字典序的UUID值的那个节点将会胜出。
分裂的集群¶
虽然Legion中的每个成员必须发送它内部集群成员的校验和,但是系统仍然易受“分裂的集群”问题的攻击。如果一个节点失去了与集群的连接,那么它可能会相信它是唯一可用的节点,然后开始Lord模式。
对于许多场景来说,这并不是最佳的。如果在一个legion中有超过2个节点,那么你可能想要考虑调整仲裁集水平。仲裁集水平是选举一个lord所需的票数(与节点数相对)。
legion-quorum
是用于这项任务的选项。你可以通过让Legion子系统检查至少2票来解决分裂集群问题:
[uwsgi]
legion = clusterip 225.1.1.1:4242 98 bf-cbc:hello
legion-node = clusterip 225.1.1.1:4242
legion-quorum = clusterip 2
legion-lord = clusterip cmd:ip addr add 192.168.173.111/24 dev eth0
legion-lord = clusterip cmd:arping -c 3 -S 192.168.173.111 192.168.173.1
legion-setup = clusterip cmd:ip addr del 192.168.173.111/24 dev eth0
legion-unlord = clusterip cmd:ip addr del 192.168.173.111/24 dev eth0
legion-death = clusterip cmd:ip addr del 192.168.173.111/24 dev eth0
由1.9.7开始,你可以使用武力值为0的节点 (概念类似于MongoDB的 Arbiter节点),在检查仲裁集的时候会考虑这样的节点,但是这样的节点将永远不会成为Lord。这在你只需要一对节点,但是要对抗分裂集群的时候很有用。
动作¶
一个legion的四个阶段都能触发动作。动作系统是模块化的,因此你可以添加新类型的动作。目前支持的动作是:
cmd:<command>
¶
运行一个shell命令。
signal:<num>
¶
引发一个uWSGI信号。
log:<msg>
¶
记录一个消息。例如,你可以将日志动作与告警系统结合在一起,从而获得免费的集群监控。
Multicast, broadcast and unicast
¶
即使多播可能是实现集群最简单的方式,但是它不是适用于所有网络的。如果多播不可选,那么你可以依赖于正常的IP地址。仅需绑定到一个地址上,然后发送所有你需要的legion-node选项:
[uwsgi]
legion = mycluster 192.168.173.17:4242 98 bf-cbc:hello
legion-node = mycluster 192.168.173.22:4242
legion-node = mycluster 192.168.173.30:4242
legion-node = mycluster 192.168.173.5:4242
这是用于一个拥有4个节点的集群(这个节点 + 3个其他节点)
多Legion¶
你可以把多个legion加入到相同的实例中。只是要记住,为每个legion使用不同的地址 (多播情况下则是端口)。
[uwsgi]
legion = mycluster 192.168.173.17:4242 98 bf-cbc:hello
legion-node = mycluster 192.168.173.22:4242
legion-node = mycluster 192.168.173.30:4242
legion-node = mycluster 192.168.173.5:4242
legion = mycluster2 225.1.1.1:4243 99 aes-128-cbc:secret
legion-node = mycluster2 225.1.1.1:4243
legion = anothercluster 225.1.1.1:4244 91 aes-256-cbc:secret2
legion-node = anothercluster 225.1.1.1:4244
安全性¶
Legion子系统发送的每个包都是使用特定的加密方法,一个预先共享的密钥以及一个可选的IV(初始化向量)进行加密的。取决于加密方法,IV可能是一个必要参数。要获取支持的加密方法列表,运行 openssl enc -h
。
重要
Legion的每个节点必须使用相同的加密参数。
要指定IV,仅需添加另一个参数到 legion 选项。
[uwsgi]
legion = mycluster 192.168.173.17:4242 98 bf-cbc:hello thisistheiv
legion-node = mycluster 192.168.173.22:4242
legion-node = mycluster 192.168.173.30:4242
legion-node = mycluster 192.168.173.5:4242
要减少基于重放攻击的影响,拒绝带低于30秒的时间戳的包。这是一个可调参数。如果你对所有节点的时间无法控制,那么你可以增加时钟偏差公差。
调整和时间偏差¶
当前,你可以调整三个参数。这些调整项影响系统中的所有Legion。每个包发送的频率 (以秒为单位) (legion-freq <secs>),节点不发送包后经过多少时间会被认为已经死亡(该时间以秒为单位) (legion-tolerance <secs>),以及节点之间的时间偏差数 (legion-skew-tolerance <secs>)。Legion子系统要求严格的时间同步,因此强烈建议使用NTP或者类似的工具。默认情况下,每3秒发送每个包,一个节点会在(不发送包后经过)15秒后认为死亡,而允许30秒的时间偏差。减少偏差容忍度应该加大对重放工具的安全保护。
Lord scroll (即将推出)¶
Legion子系统可以用于各种各样的目的,从master选举到节点自动发现或者简单的监控。一个例子是将”一组数据” (一个scroll)赋给每个节点。这个的一种使用时传递重新配置的参数给你的应用,或者记录特定的消息。目前,scroll系统正在加以改进之中,因此,如果你有任何想法,那么请加入到我们的邮件列表或者IRC频道中吧。
Legion API¶
你可以通过简单调用以下函数来判断实例是否是Legion的Lord:
int uwsgi_legion_i_am_the_lord(char *legion_name);
如果当前实例时特定Legion的Lord的话,则返回1。
- Python插件将其公开为
uwsgi.i_am_the_lord(name)
- PSGI插件将其公开为
uwsgi::i_am_the_lord(name)
- Rack插件将其公开为
UWSGI::i_am_the_lord(name)
显然,未来将会添加更多的API函数,你可以自由公开你的想法。
统计信息¶
可以在 uWSGI Stats服务器 中找到Legion的信息。确保理解“节点”和“成员”之间的不同。节点是你用 legion-node 选项配置的对端,而成员是加入到集群的有效的节点。
老的集群子系统¶
在0.9开发周期中,添加了一个集群子系统 (基于多播)。它非常原始不可靠,并且很有可能没人把它当回事。新的方法是使用可以使用不同后端的一般的API来改变它。Legion子系统可以是这些后端中的一员,以及像corosync或者redhat集群套件这样的项目(也可以是这些后端)。
锁¶
uWSGI支持可配置数量的锁,你可以用来同步worker进程。0号锁(Lock 0) (zero) 始终是可用的,但是你可以通过 locks
选项添加更多的锁。如果你的应用有大量的关键块,那么一次又一次地持有释放相同的锁会大量耗费性能。
def use_lock_zero_for_important_things():
uwsgi.lock() # Implicit parameter 0
# Critical section
uwsgi.unlock() # Implicit parameter 0
def use_another_lock():
uwsgi.lock(1)
time.sleep(1) # Take that, performance! Ha!
uwsgi.unlock(1)
uWSGI Mule¶
Mule是活在uWSGI栈中的worker进程,但通过socket连接是不能访问的,它可以作为一种通用子系统使用,以卸载任务。你可以将它们看成一个比较原始的 spooler。
它们可以访问整个 uWSGI API,可以管理信号,并且可以通过一个简单的基于字符串的消息系统来进行通信。
要启动一个mule (你可以启动无限个它们),需要多少次,就使用多少次 mule
选项。
Mule有两种模式,
- 纯信号模式(默认模式)。在这种模式下,mule像正常的worker那样加载你的应用。它们只能响应 uWSGI signals。
- 编程模式。在这种模式下,mule与你的应用分开加载一个程序。见 ProgrammedMules.
默认情况下,每个mule以纯信号模式启动。
uwsgi --socket :3031 --mule --mule --mule --mule
<uwsgi>
<socket>:3031</socket>
<mule/>
<mule/>
<mule/>
<mule/>
</uwsgi>
基本使用¶
import uwsgi
from uwsgidecorators import timer, signal, filemon
# run a timer in the first available mule
@timer(30, target='mule')
def hello(signum):
print "Hi! I am responding to signal %d, running on mule %d" % (signum, uwsgi.mule_id())
# map signal 17 to mule 2
@signal(17, target='mule2')
def i_am_mule2(signum):
print "Greetings! I am running in mule number two."
# monitor /tmp and arouse all of the mules on modifications
@filemon('/tmp', target='mules')
def tmp_modified(signum):
print "/tmp has been modified. I am mule %d!" % uwsgi.mule_id()
赋予mule智慧¶
如前所述,可以对mule进行编程。要赋予一个mule自定义逻辑,则将脚本名传递给 mule
选项。
uwsgi --socket :3031 --mule=somaro.py --mule --mule --mule
这将会运行4个mule,3个处于纯信号模式,一个运行 somaro.py
。
# somaro.py
from threading import Thread
import time
def loop1():
while True:
print "loop1: Waiting for messages... yawn."
message = uwsgi.mule_get_msg()
print message
def loop2():
print "Hi! I am loop2."
while True:
time.sleep(2)
print "This is a thread!"
t = Thread(target=loop2)
t.daemon = True
t.start()
if __name__ == '__main__':
loop1()
因此,正如你可以从这个例子看到的那样,你可以在一个编程mule中使用 mule_get_msg()
来接收消息。相同编程mule中的多个线程会等待消息。
如果你想阻塞一个mule,以等待一个uWSGI信号,而不是消息,那么你可以使用 uwsgi.signal_wait()
。
使用 uwsgi.mule_msg()
来发送一个消息给编程mule。可以从uWSGI栈中的任何一个地方发送mule消息,包括但不限制于worker, spooler, 另一个mule。
# Send the string "ciuchino" to mule1.
# If you do not specify a mule ID, the message will be processed by the first available programmed mule.
uwsgi.mule_msg("ciuchino", 1)
由于你可以生成无限个mule,因此你或许需要某些形式的同步 —— 例如,如果你正在开发一个任务管理子系统,并且不希望两个mule能够同时启动相同的任务。你很幸运 —— 见 锁。
uWSGI 卸载(offloading)子系统¶
卸载是一种优化小任务的方式,将它们委托给一个或多个线程。
这些线程在非阻塞/事件触发的方式中运行这样的任务,允许大量的并发。
uWSGI栈的各种组件是卸载友好型的,而长期目标是允许应用代码随意使用它们。
要启动卸载子系统,仅需添加–offload-threads <n>,其中<n>是要生成的线程数 (每个worker)。 它们是原生线程,无锁(无共享资源),无惊群效应(到该系统的请求会进行轮询),并且它们是任意使用你的CPU核心的最佳方式。
统计信息子系统的”offloaded_requests”度量中计算了卸载请求的数量。
卸载静态文件¶
第一个卸载感知的组件是静态文件服务系统。
当卸载线程可用的时候,文件的整个传输会被委托给其中一个线程,立即释放你的worker (这样它就准备好接收新的请求了)
例如:
[uwsgi]
socket = :3031
check-static = /var/www
offload-threads = 4
卸载内部路由¶
router_uwsgi和router_http插件是卸载友好型的。
你可以路由请求到外部uwsgi/HTTP服务器,而无需担忧在响应生成期间阻塞了worker。
例如:
[uwsgi]
socket = :3031
offload-threads = 8
route = ^/foo http:127.0.0.1:8080
route = ^/bar http:127.0.0.1:8181
route = ^/node http:127.0.0.1:9090
自1.9.11起, cache
路由器也是卸载友好型的了。
[uwsgi]
socket = :3031
offload-threads = 8
route-run = cache:key=${REQUEST_URI}
一旦从缓存检索到该对象,就会在其中一个卸载线程中对其进行传输。
未来¶
卸载子系统具有很大的潜力,你可以把它想成一个软件DMA:编程它,然后让它单打独斗。
目前,它是相当整体的,但想法是允许更复杂的插件 (一个redis相关的正在进行中)。
下一步是运行用户通过uwsgi api来对它“进行编程”。
uWSGI队列框架¶
除了 caching framework ,uWSGI还有一个共享队列。
在低层次,它是一个简单的基于块的共享数组,有两个可选的计数器,一个用于对于堆栈式,LIFO,另一个用于FIFO。
数组是环形的,因此,当两个指针的任意一个到达了尾部(或者首部),它会被重置。记住这点!
要启用队列,则使用 queue
选项。默认情况下,队列块是8KiB。使用 queue-blocksize
来修改其大小。
# 100 slots, 8 KiB of data each
uwsgi --socket :3031 --queue 100
# 42 slots, 128 KiB of data each
uwsgi --socket :3031 --queue 42 --queue-blocksize 131072
将队列当成共享数组使用¶
# Put a binary string in slot 17.
uwsgi.queue_set(17, "Hello, uWSGI queue!")
# Get it back.
print uwsgi.queue_get(17)
将队列当成共享堆栈使用¶
# Push a value onto the end of the stack.
uwsgi.queue_push("Hello, uWSGI stack!")
# Pop it back
print uwsgi.queue_pop()
# Get the number of the next available slot in the stack
print uwsgi.queue_slot()
# Pop the last N items from the stack
items = uwsgi.queue_last(3)
将队列当成一个FIFO队列使用¶
注解
当前,你只能pull,不能push。要入队一个元素,请使用 uwsgi.queue_set()
。
# Grab an item from the queue
uwsgi.queue_pull()
# Get the current pull/slot position (this is independent from the stack-based one)
print uwsgi.queue_pull_slot()
注释¶
- 你可以通过使用
uwsgi.queue_size
获取队列大小。 - 使用
queue-store
选项将队列在磁盘上持久化。使用queue-store-sync
(在master循环中 —— 通常是秒) 来强制磁盘同步队列。 tests/queue.py
应用是一个完整的可用例子。
uWSGI RPC栈¶
uWSGI包含了一个快速、简单、平稳跨平台的RPC栈。
虽然,你或许会爱上这个子系统,但是,答应我,只在你需要的时候才用它,好吗?对于绝大部分常见,有大量更适合的高层次RPC技术可以用。
也就是说,uWSGI RPC子系统闪光点在于它的性能和内存使用。例如,如果你需要将一个请求的负载拆分到多个服务器上,那么uWSGI RPC是个不错的选择,因为它允许你不怎么费力就可以卸载任务。
它最大的限制在于它的“无类型”方法。
RPC函数可以接收多达254个参数。每个参数必须是一个字符串,最大大小为16位 (65535字节),而返回值必须是一个字符串 (这次是64位,所以这不是一个实际的限制)。
警告
64位响应长度只在uWSGI 1.9.20中实现,较老的版本则是16位响应长度限制。
注解
RPC函数以二进制字符串的形式接收参数,所以每个RPC可导出函数必须假设每个参数都是一个字符串。每个RPC函数返回一个0或者拥有更多字符的二进制字符串。
所以,如果你需要“优雅的”或者强类型,那么就看看其他的吧 (或者,在uWSGI RPC之上自己处理,或许……)。
从1.9起,RPC子系统就是完全异步友好型的,因此,你可以把它跟gevent和Coro::AnyEvent等一起使用。
通过实例学习¶
让我们从一个简单的RPC调用开始,这个调用从 10.0.0.1:3031
到 10.0.0.2:3031
。
所以,让我们导出 .2
上的一个”hello”函数
import uwsgi
def hello_world():
return "Hello World"
uwsgi.register_rpc("hello", hello_world)
这使用了 uwsgi.register_rpc()
来声明一个名为”hello”的函数,以备导出。我们会用 --socket :3031
启动这个服务器。
在调用者那端,在 10.0.0.1
之上,让我们声明这个世界(第二个)最简单的WSGI应用。
import uwsgi
def application(env, start_response):
start_response('200 Ok', [('Content-Type', 'text/html')])
return uwsgi.rpc('10.0.0.2:3031', 'hello')
就这样!
你需要Perl?¶
或者也许你想要从单个perl脚本中调用一个RPC函数?
那么,比方说,Lua呢?¶
很高兴你问了。如果你想要导出Lua中的函数,简单这样:
function hello_with_args(arg1, arg2)
return 'args are '..arg1..' '..arg2
end
uwsgi.register_rpc('hellolua', hello_with_args)
而在你的Python WSGI应用中:
import uwsgi
def application(env, start_response):
start_response('200 Ok', [('Content-Type', 'text/html')]
return uwsgi.rpc('10.0.0.2:3031', 'hellolua', 'foo', 'bar')
本地进行RPC¶
本地进行RPC听起来可能有点蠢,但如果你需要从Python调用一个Lua函数,并且使用绝对最少的开销,那么uWSGI RPC就是你的人啦。
如果你想要调用定义在同一台服务器(由同一个master管理,等)上的RPC,只需设置 uwsgi.rpc
的第一个参数为None或者nil,或者使用方便函数 uwsgi.call()
。
从内部路由子系统进行RPC¶
RPC插件导出了一堆内部路由动作:
- rpc 调用指定的rpc函数,并将响应发送给客户端
- rpcnext/rpcblob 调用指定的rpc函数,发送响应给客户端,并继续执行下一条规则
- rpcret 调用指定的rpc函数,并将其返回值当成动作返回码 (next, continue, goto ...)
[uwsgi]
route = ^/foo rpc:hello ${REQUEST_URI} ${REMOTE_ADDR}
; call on remote nodes
route = ^/multi rpcnext:part1@192.168.173.100:3031
route = ^/multi rpcnext:part2@192.168.173.100:3031
route = ^/multi rpcnext:part3@192.168.173.100:3031
从nginx进行RPC¶
由于Nginx支持对发送到上游uWSGI服务器的uwsgi包的低层次操作,所以你可以直接通过它进行RPC。疯了!
location /call {
uwsgi_modifier1 173;
uwsgi_modifier2 1;
uwsgi_param hellolua foo
uwsgi_param bar ""
uwsgi_pass 10.0.0.2:3031;
uwsgi_pass_request_headers off;
uwsgi_pass_request_body off;
}
大小为0的字符串将会被uWSGI数组解析器忽略,所以当参数数目+function_name不是偶数的时候,你可以安全使用它们。
Modifier2被设为1,来通知接收到了原始字符串 (在这个例子中,是HTTP响应)。否则,RPC子系统会将输出封装到一个uwsgi协议包中,而nginx还没智能到读取它们。
HTTP PATH_INFO -> RPC bridge¶
XML-RPC -> RPC bridge¶
uWSGI信号框架¶
警告
uwsgi信号的原始使用只提供给高级用户。对于一个更优雅的抽象,你应该看看 uWSGI API - Python装饰器 。
注解
uWSGI信号量与UNIX/Posix信号量_毫无共同之处_ (如果你找的是那些,那么 管理uWSGI服务器 才是你要看的)。
随着时间的推移,你的uWSGI堆栈越来越大,你添加spooler,更多的进程,更多的插件,等等。你添加的功能越多,你就越需要所有这些组件之间能彼此通信。
现今丰富/高级的web应用的另一个重要任务是响应不同事件。一个事件可能是一个文件修改,一个新的集群节点冒出来,另一个(黯然)死去,一个定时器时间已经到了……任何你能想象到的事件。
通信和事件管理都由相同的子系统管理 —— uWSGI信号框架。
uWSGI信号是由socket管理的,因此它们 够可靠 。当你发送一个uWSGI信号时,你可以保证它会被转发。
信号表¶
信号是简单的 1字节 消息,它可以由master进程路由给worker和spooler.
当一个workder接收到一个信号,它会搜索信号表,查找对应的处理程序并执行。
信号表由所有worker共享 (并通过共享锁对抗竞争条件)。
每个uWSGI进程 (虽然主要是master)可以写信号表,以设置信号处理程序和接收进程。
警告
要经常注意谁会运行信号处理程序。它必须能够访问信号处理程序本身。这意味着,如果你在 worker1
中定义一个新的函数,并将其注册为信号处理程序,那么只有 worker1
可以运行它。注册信号最好的方法是在master中定义它们,这样(多亏了 fork()
)所有的worker都能看到它们。
定义信号处理程序¶
要管理信号表,uWSGI API提供了一个简单的函数, uwsgi.register_signal()
.
下面是两个简单的定义信号表项的例子,分别用Python和Lua编写。
import uwsgi
def hello_signal(num):
print "i am the signal %d" % num
def hello_signal2(num):
print "Hi, i am the signal %d" % num
# define 2 signal table items (30 and 22)
uwsgi.register_signal(30, "worker", hello_signal)
uwsgi.register_signal(22, "workers", hello_signal2)
function hello_signal(sig)
print("i am Lua, received signal " .. sig ..)
end
# define a single signal table item (signal 1)
uwsgi.register_signal(1, "worker", hello_signal)
信号目标¶
uwsgi.register_signal的第三个参数是’signal targer’。
它指示系统“谁”必须运行处理程序。默认情况下,目标是targer是表示“第一个可用worker”的’worker’。下面是可用的目标:
- workerN (只在workder N上运行信号处理程序)
- worker/worker0 (默认,在第一个可用的worker上运行信号处理程序)
- workers (在所有worker上运行信号处理程序)
- active-workers (在所有活跃的 [non-cheaped] worker上运行信号处理程序)
- spooler (在第一个可用的spooler上运行信号处理程序)
- mules (在所有的mule上运行信号处理程序)
- muleN (在mule N上运行信号处理程序)
- mule/mule0 (在第一个可用的mule上运行信号处理程序)
- farmN/farm_XXX (在mule farm N或者指定的XXX上运行信号处理程序)
引发信号¶
可以使用 uwsgi.signal()
引发信号。当你发送一个信号时,它会被拷贝到master的队列中。然后,master会检查信号表并调度消息。
外部事件¶
uWSGI信号最有用的特性是,它们可以用于宣布外部事件。
编写可用外部事件的时机是
- 文件系统修改
- timer/rb_timer
- cron
其他事件是通过插件暴露出来的,例如,每当一个postgres通知通道准备好时,https://github.com/unbit/uwsgi-pgnotifyj就会引发一个信号。
文件系统修改¶
要将一个特定的文件/目录修改事件映射到一个信号上,你可以使用 uwsgi.add_file_monitor()
。
一个例子:
import uwsgi
def hello_file(num):
print "/tmp has been modified !!!"
uwsgi.register_signal(17, "worker", hello_file)
uwsgi.add_file_monitor(17, "/tmp")
从现在开始,每次 /tmp
被修改时,将会引发信号17,然后第一个可用worker将会运行 hello_file
。
定时器¶
定时器是web编程中另一个有用特性 —— 例如清理会话、购物车等诸如此类。
定时器是利用内核工具实现的(BSD系统上的kqueue,以及现代Linux内核上的timerfd())。uWSGI还包含对rb_timer的支持,这是一个在用户空间中,使用红黑树实现的定时器。
要注册一个定时器,可以使用 uwsgi.add_timer()
。要注册一个rb_timer,可以使用 uwsgi.add_rb_timer()
。
import uwsgi
def hello_timer(num):
print "2 seconds elapsed, signal %d raised" % num
def oneshot_timer(num):
print "40 seconds elapsed, signal %d raised. You will never see me again." % num
uwsgi.register_signal(26, "worker", hello_timer)
uwsgi.register_signal(30, "", oneshot_timer)
uwsgi.add_timer(26, 2) # never-ending timer every 2 seconds
uwsgi.add_rb_timer(30, 40, 1) # one shot rb timer after 40 seconds
每2秒钟就会引发一次信号26,并且由第一个可用worker处理。40秒过后会引发一次信号30,然后只执行一次。
signal_wait和signal_received¶
未注册信号(那些没有相关处理程序的)将会路由到第一个可用worker,以使用 uwsgi.signal_wait()
函数。
uwsgi.signal_wait()
signum = uwsgi.signal_received()
你可以将外部事件(文件监控、定时器……)和这项技术结合起来,以实现基于事件的应用。一个很好的例子是聊天服务器,其中,每个核等待用户发送的文本。
你也可以通过传递一个信号数字给 signal_wait
来等待一个特定的(甚至注册了的)信号。
待办/已知问题¶
- 不能移除信号表项(这会尽快解决)
- 迭代只适用于rb_timer
- uwsgi.signal_wait()在异步模式下无效(将会解决)
- 添加迭代到文件监控(以允许定时器的一次事件)
uWSGI Spooler¶
更新至uWSGI 2.0.1
支持语言:Perl, Python, Ruby
Spooler是内置于uWSGI的队列管理器,它的工作方式像打印/邮件系统。
你可以排队大量的邮件发送、图像处理、视频编码等等,并且让spooler在后台为你努力工作,同时用户的请求会被正常的worker处理。
spooler通过定义”spooler文件”将会写入的目录来工作,每次spooler在它的目录下找到一个文件,它就会解析它,然后运行一个特定的函数。
你可以让多个spooler映射到不同的目录,甚至可以让多个spooler映射到相同的目录。
--spooler <directory>``选项允许你生成一个spooler进程,而
–spooler-processes <n>``允许你设置为每个spooler生成多少个进程。
spooler也能够管理uWSGI信号量,因此,你可以把它当成你的处理器的目标使用。
这个配置将为你的实例生成一个spooler (myspool目录必须存在)
[uwsgi]
spooler = myspool
...
而这个将创建两个spooler:
[uwsgi]
spooler = myspool
spooler = myspool2
...
拥有多个spooler使你能够把任务区分优先次序(甚至对其并行处理)
spool文件¶
spool文件是序列化的字符串哈希/字典。spooler将对其进行解析,然后将得到的哈希/字典传递给spooler函数(见下文)。
序列化格式与’uwsgi’协议使用的格式相同,因此,最多只能64k (即使有窍门传递更大的值,见下面的’body’魔法键)。用于spooler包的modifier1是17, 因此,一个{‘hello’ => ‘world’}哈希将会被编码成:
header | key1 | value1 |
---|---|---|
17|14|0|0 | |5|0|h|e|l|l|o | |5|0|w|o|r|l|d |
一个锁定系统允许你在出现问题的时候安全地手工移除spool文件,或者在两个spooler目录之间移动。
允许跨NFS的spool目录,但是如果你在适当的位置上没有合适的NFS锁,那么请避免映射相同的spooler NFS目录到不同机器上的spooler。
设置spooler函数/调用¶
由于有几十种不同的方式来排队spooler请求,因此我们将首先涵盖接收请求。
想要有个全面运作的spooler,那么你需要定义一个”spooler函数/调用”来处理请求。
无论配置的spooler数目是多少,都会执行相同的函数。由开发者指示它识别任务。如果你不处理请求,那么spool目录就会只是填满。
这个函数必须返回一个整数值:
- -2 (SPOOL_OK) —— 任务已完成,将会移除spool文件
- -1 (SPOOL_RETRY) —— 暂时错误,在下一个spooler迭代将会重试该任务。
- 0 (SPOOL_IGNORE) —— 忽略此任务,如果实例中加载了多个语言,那么它们所有都会竞争管理该任务。这个返回值允许你跳过特定的语言的任务。
任何其他值都会被解析成-1 (重试)。
每个语言插件都有它自己定义spooler函数的方法:
Perl:
uwsgi::spooler(
sub {
my ($env) = @_;
print $env->{foobar};
return uwsgi::SPOOL_OK;
}
);
# hint - uwsgi:: is available when running using perl-exec= or psgi=
# no don't need to use "use" or "require" it, it's already there.
Python:
import uwsgi
def my_spooler(env):
print env['foobar']
return uwsgi.SPOOL_OK
uwsgi.spooler = my_spooler
Ruby:
module UWSGI
module_function
def spooler(env)
puts env.inspect
return UWSGI::SPOOL_OK
end
end
spooler函数必须在master进程中定义,因此,如果是在lazy-apps模式下,那么确保将其放到一个文件中,该文件要在服务器设置之初被解析。(在Python中,你可以使用–shared-import,在Ruby中,使用–shared-require,在Perl中,使用–perl-exec)。
Python支持使用 --spooler-python-import
选项,直接在spooler中导入代码。
排队请求到一个spooler¶
‘spool’ api函数允许你排队一个哈希/目录到实例指定的spooler:
# add this to your instance .ini file
spooler=/path/to/spooler
# that's it! now use one of the code blocks below to send requests
# note: you'll still need to register some sort of receiving function (specified above)
# python
import uwsgi
uwsgi.spool({'foo': 'bar', 'name': 'Kratos', 'surname': 'the same of Zeus'})
# or
uwsgi.spool(foo='bar', name='Kratos', surname='the same of Zeus')
# for python3 use bytes instead of strings !!!
# perl
uwsgi::spool({foo => 'bar', name => 'Kratos', surname => 'the same of Zeus'})
# the uwsgi:: functions are available when executed within psgi or perl-exec
# ruby
UWSGI.spool(foo => 'bar', name => 'Kratos', surname => 'the same of Zeus')
一些键有特殊含义:
- ‘spooler’ => 指定必须管理这个任务的spooler的绝对路径
- ‘at’ => 必须执行该任务的unix时间 (读:该任务将不会运行,直到过去’at’时间)
- ‘priority’ => 这将是spooler目录中的子目录,任务将会被放置在其中,你可以使用哪个技巧来赋予任务足够好的优先权 (更好的方法是使用多个spooler)
- ‘body’ => 为大于64k的对象使用这个键,这个blob将会被附加到序列化的uwsgi包上,然后作为’body’参数传回给spooler函数
注解
Spool arguments must be strings (or bytes for python3). The API functions will try to cast non-string values to strings/bytes, but do not rely on that functionality!
外部spooler¶
你可能想要为你的服务器实现一个跨多个uWSGI实例的集中式spooler。
单个实例将会管理由多个uWSGI实例入队的所有任务。
要完成这个配置,每个uWSGI实例必须知道哪个spooler目录是有效的 (将其当成一种形式的安全来考虑)。
要添加一个外部spooler目录,使用 --spooler-external <directory>
选项,然后使用spool函数来添加。
spooler锁子系统将会避免你认为可能会出现的任何混乱。
[uwsgi]
spooler-external = /var/spool/uwsgi/external
...
# python
import uwsgi
uwsgi.spool({'foo': 'bar', 'spooler': '/var/spool/uwsgi/external'})
# or
uwsgi.spool(foo='bar', spooler='/var/spool/uwsgi/external')
# for python3 use bytes instead of strings !!!
网络spooler¶
你甚至可以通过网络入队任务 (确保在你的实例中加载了’spooler’插件,但是一般来说,是默认内置的)。
正如我们已经看到的那样,spooler包使用modifier1 17,你可以直接发送那些包到一个启用了spooler的实例的uWSGI socket上。
在这个例子中,我们会使用Perl的 Net::uwsgi
模块 (公开了一个方便的uwsgi_spool函数) (但随意使用任何你想要的模块来写spool文件)。
#!/usr/bin/perl
use Net::uwsgi;
uwsgi_spool('localhost:3031', {'test'=>'test001','argh'=>'boh','foo'=>'bar'});
uwsgi_spool('/path/to/my.sock', {'test'=>'test001','argh'=>'boh','foo'=>'bar'});
[uwsgi]
socket = /path/to/my.sock
socket = localhost:3031
spooler = /path/for/files
spooler-processes=1
perl-exec = /path/for/script-which-registers-spooler-sub.pl
...
(感谢brianhorakh提供这个例子)
优先级¶
我们已经看到,你可以使用’priority’键来赋予spooler解析次序。
虽然使用多个spooler也许是一个更好的方法,但是在一个资源不多的系统上,‘优先权’是个好技巧。
只有你启动了 --spooler-ordered
选项,它们才能用。这个选项允许spooler以字母序扫描目录项。
如果在扫描期间,发现了一个具有‘数字’名的目录,那么扫描就会被挂起,然后将会探索这个子目录的内容以查找任务。
/spool
/spool/ztask
/spool/xtask
/spool/1/task1
/spool/1/task0
/spool/2/foo
使用这个布局,文件解析的次序将是:
/spool/1/task0
/spool/1/task1
/spool/2/foo
/spool/xtask
/spool/ztask
记住,优先级只对命名为“数字”的子目录有用,并且你需要 --spooler-ordered
选项。
uWSGI spooler为任务赋予了特殊的名字,因此,入队的次序总是会被遵循的。
选项¶
spooler=directory
在指定的目录上运行一个spooler
spooler-external=directory
映射spooler请求到一个由外部实例管理的spooler目录
spooler-ordered
试着排序spooler任务的执行 (使用scandir来取代readdir)
spooler-chdir=directory
在每个spooler任务之前,调用chdir()到指定的目录
spooler-processes=##
为spooler设置进程数
spooler-quiet
不要打印spooler任务的冗余信息
spooler-max-tasks=##
设置循环利用一个spooler之前运行的最大任务数 (以帮助减轻内存泄漏)
spooler-signal-as-task
与 spooler-max-tasks
组合使用。启用这个,spooler将会把信号事件当成任务。运行信号处理器也将会增加spooler任务数。
spooler-harakiri=##
为spooler任务设置harakiri超时时间,见[harakiri]以获取更多信息。
spooler-frequency=##
设置spooler频率
spooler-python-import=???
直接在spooler中导入一个python模块
技巧和窍门¶
你可以通过在你的可回调对象中返回 uwsgi.SPOOL_RETRY
来重新入队一个spooler请求:
def call_me_again_and_again(env):
return uwsgi.SPOOL_RETRY
你可以使用 --spooler-frequency <secs>
选项来设置spooler poll频率 (默认是30秒)。
你可以使用 uWSGI缓存框架 或者 SharedArea —— uWSGI组件间共享内存页 来在spooler和worker之间交换内存结构。
Python (uwsgidecorators.py)和Ruby (uwsgidsl.rb)公开了高层次的功能来管理spooler,试着使用它们来取代这里描述的低层次方法。
当把一个spooler当成uWSGI信号处理器的目标使用的时候,你可以使用绝对目录名来指定路由信号到哪个。
uWSGI订阅服务器¶
uWSGI栈的一些部分要求有一个键值映射系统。
例如, uWSGI FastRouter 需要知道对于一个特定的请求,要联系哪个服务器。
在拥有大量的节点的大网络中,手工管理这些配置可能就是地狱般可怕。uWSGI实现了一个订阅系统,其中,节点本身向订阅服务器宣告它的存在,这将反过来填入它们的内部字典。
uwsgi --fastrouter :1717 --fastrouter-subscription-server 192.168.0.100:2626
这将会在端口1717上运行一个uWSGI fastrouter,并且创建一个空字典,其中,主机名是键,而uwsgi地址则是值。
要填充这个字典,你可以联系192.168.0.100:2626,这是订阅服务器的地址。
对于每个键,可以存在多个地址,启用负载均衡 (各种算法也能用)。
一个节点可以使用 subscribe-to
或者 subscribe2
选项来向订阅服务器宣告它的存在。
uwsgi --socket 192.168.0.10:3031 --wsgi myapp -M --subscribe-to 192.168.0.100:2626:uwsgi.it
FastRouter将会映射每个对uwsgi.it的请求到192.168.0.10:3031。
现在,为uwsgi.it添加第二个节点,简单运行它并订阅:
uwsgi --socket 192.168.0.11:3031 --wsgi myapp --master --subscribe-to 192.168.0.100:2626:uwsgi.it
会自动将死掉的节点从池中移除。
subscribe2
的语法是类似的,但是它允许更多的控制,因为它允许指定额外的选项,例如所有的请求应该被转发到哪个地址。它的值语法是一个带”key=value”对的字符串,每个由一个逗号分隔。
uwsgi -s 192.168.0.10:3031 --wsgi myapp --master --subscribe2 server=192.168.0.100:2626,key=uwsgi.it,addr=192.168.0.10:3031
可用 subscribe2
键的列表,见下。
该订阅系统目前对集群加入(cluster joining) (当多播/广播不能用的时候)、Fastrouter、HTTP/HTTPS/SPDY路由器、rawrouter和sslrouter可用。
那就是说,你可以很快创建一个事件化/fast_as_hell的HTTP负载均衡器。
uwsgi --http :80 --http-subscription-server 192.168.0.100:2626 --master
现在,简单订阅你的节点到HTTP订阅服务器。
你可以通过 http-stats-server
选项检查订阅服务器统计信息以及/或者订阅节点。
uwsgi --http :80 --http-subscription-server 192.168.0.100:2626 --http-stats-server 192.168.0.100:5004 --master
你还可以使用 http-resubscribe
选项转发订阅请求到其他服务器。
uwsgi --http :80 --http-subscription-server 192.168.0.100:2626 --http-resubscribe 192.168.0.101:2627 --master
防护订阅系统¶
订阅服务器意味着“可信任”网络。你网络中的所有节点可能潜在会制造大混乱。
如果你正为不可信任用户构建基础设施,或者你只是对可以订阅到订阅服务器的用户需要更多的控制权,那么你可以使用openssl rsa 公钥/密钥对,来“签名”你的订阅请求。
# First, create the private key for the subscriber. DO NOT SET A PASSPHRASE FOR THIS KEY.
openssl genrsa -out private.pem
# Generate the public key for the subscription server:
openssl rsa -pubout -out test.uwsgi.it_8000.pem -in private.pem
密钥必须以我们订阅到服务器的域名/键命名,加上.pem扩展名。
注解
如果你正订阅到一个池,使得应用监听到一个指定的端口,那么你需要为你的密钥文件使用 domain_port.pem
模式。一般而言,支持所有DNS允许的字符,其他都会被映射到一个下划线。
一个RSA保护的服务器如下:
[uwsgi]
master = 1
http = :8000
http-subscription-server = 127.0.0.1:2626
subscriptions-sign-check = SHA1:/etc/uwsgi/keys
最后一行告诉uWSGI,公钥文件将会存储在/etc/uwsgi/keys中。
对于每个订阅请求,服务器将会检查公钥文件的可用性,如果可用,则会用它来验证包的签名。拒绝不能正确验证的包。
在客户端,你需要传递你的私钥,以及其他 subscribe-to
选项。这是一个例子:
[uwsgi]
socket = 127.0.0.1:8080
subscribe-to = 127.0.0.1:2626:test.uwsgi.it:8000,5,SHA1:/home/foobar/private.pem
psgi = test.psgi
让我们分析 subscribe-to
使用:
127.0.0.1:2626
是我们想要订阅的订阅服务器。test.uwsgi.it:8000
是订阅键。5
是用于我们的psgi应用的modifier1值SHA1:/home/private/test.uwsgi.it_8000.pem
是用来鉴权服务器的 <digest>:<rsa> 对 (<rsa>字段是私钥地址)。
注解
请确保你在服务器和客户端都使用相同的摘要方法 (上面的例子中是SHA1)。
为了避免重放攻击,每个订阅包都有一个增量数字 (一般是Unix时间),避免允许重复包。即使攻击者试图嗅探一个订阅包,它也会是不可用的,因为之前已经处理它了。显然,如果有人试图窃取你的私钥,他将能够构建伪造数据包。
使用SSH密钥¶
SSH格式的密钥一般受到开发者喜爱 (嗯,比经典的PEM文件更受欢迎)。
–subscribe-to和–subscribe2 (见下) 都支持SSH密钥,而对于服务器部分,你要用pkcs8编码公钥:
ssh-keygen -f chiavessh001.pub -e -m pkcs8
–subscribe2¶
这是–subscribe-to的键值版本。它支持更多技巧,以及(一般)更可读的语法:
uwsgi --socket 127.*:0 --subscribe2 server=127.0.0.1:7171,key=ubuntu64.local:9090,sign=SHA1:chiavessh001
支持的字段是:
server
订阅服务器的地址key
订阅的键 (一般是域名)addr
订阅的地址 (项的值)socket
socket数字 (基于0),这就像’addr’,通过接收uWSGI内部socket值weight
负载均衡值modifier1
和modifier2
sign
<algo>:<file> 安全系统的签名check
它接收一个文件作为参数。如果存在,则发送包,否则,跳过它sni_key
为SNI代理管理设置密钥文件sni_crt
为SNI代理管理设置crt文件sni_ca
为SNI代理管理设置ca文件algo
(uWSGI 2.1) 设置使用的负载均衡算法 (它们是可插拔的,包含wrr, lrc, wlrc和iphash)proto
(uWSGI 2.1) 使用的协议,默认是’uwsgi’backup
(uWSGI 2.1) 设置备份层次 (基于算法改变意义)
通知¶
当你订阅到一个服务器的时候,你可以让它“确认”你的请求的接受情况。
只需添加 --subscription-notify-socket <addr>
,指向一个数据报 (Unix或者UDP) 地址,你的实例将会绑定到上面,并且订阅服务器将会发送确认到这个地址。
挂载点 (uWSGI 2.1)¶
一般来说,你订阅你的应用到指定域上。
幸好有了uWSGI 2.1中引入的挂载点支持,你现在可以订阅每个节点到一个指定的目录了 (需要指定你想要支持多少层次):
首先,你需要告诉订阅服务器支持 (和管理) 挂载点请求:
uwsgi --master --http :8080 --http-subscription-server 127.0.0.1:4040 --subscription-mountpoints 1
然后,你可以开始订阅到挂载点。
uwsgi --socket 127.0.0.1:0 --subscribe2 server=127.0.0.1:4040,key=mydomain.it/foo
uwsgi --socket 127.0.0.1:0 --subscribe2 server=127.0.0.1:4040,key=mydomain.it/bar
uwsgi --socket 127.0.0.1:0 --subscribe2 server=127.0.0.1:4040,key=mydomain.it/foo
uwsgi --socket 127.0.0.1:0 --subscribe2 server=127.0.0.1:4040,key=mydomain.it
第一个和第三个实例将会响应所有对/foo的请求,而第二个将会响应/bar,最后一个会管理所有其他的请求。
对于安全的订阅系统,你只需要使用域名键(不需要为每个挂载点生成证书)。
如果你想以/one/two的形式,而不是/one的形式来支持挂载点,那么只需传递‘2’到–subscription-mountpoints,等等。出于性能考虑,你需要选择你的路径可以支持多少个元素,并且不能弄混它们 (说明:如果–subscription-mountpoints是2,那么你可以支持/one/two或者/foo/bar,但不能支持/foobar)
使用uWSGI提供静态文件 (更新至1.9)¶
不幸的是,你不能无需通过一些协议 (HTTP, SPDY或者别的什么的) 提供静态文件服务。
幸运的是,对于提供静态文件,uWSGI有一系列广泛的选项了微优化。
一般来说,你所选的web服务器 (Nginx, Mongrel2等) 将高效快速地提供静态文件,并且会直接把动态请求转发到uWSGI后端节点。
uWSGI项目的主要目标是ISPs和PaaS (即,托管市场),其中,一般你会想要避免在中央服务器上生成磁盘I/O,并且让每个用户专有的区域自己处理(负责)它。更重要的是,你想要让客户在无需打扰你的系统管理员的情况下自定义他们提供静态资产服务的方式。
模式1:在传递请求给你的应用之前检查静态资源¶
在web应用中管理静态文件是一种相当常见的方式。诸如Ruby on Rails这样的关键和许多PHP应用已使用这种方法好多年了。
假设你的静态资产位于 /customers/foobar/app001/public
之下。你想要在传递请求给你的动态应用之前检查在那个目录下请求都有一个对应的文件。 --check-static
选项就是为你而设的:
--check-static /customers/foobar/app001/public
如果uWSGI接收到一个对 /foo.png
的请求,那么它将首先检查 /customers/foobar/app001/public/foo.png
是否存在,如果不存在,那么它会调用你的应用。
你可以多次指定 --check-static
,来指定多个可能的根路径。
--check-static /customers/foobar/app001/public --check-static /customers/foobar/app001/static
uWSGI将首先检查 /customers/foobar/app001/public/foo.png
;如果它找不到它,那么它会在最后委托请求到你的应用之前尝试 /customers/foobar/app001/static/foo.png
。
模式2:信任前端DOCUMENT_ROOT¶
如果你的前端 (一个web服务器,一个uWSGI核心路由器……) 设置了 DOCUMENT_ROOT
值,那么你可以使用 --check-static-docroot
选项,指示uWSGI将其作为一个检查静态文件的有效目录。
模式3:使用静态文件挂载点¶
一个更一般的方法是“映射”指定请求前缀到你的文件系统上的物理目录。
--static-map mountpoint=path
选项可以实现此目的。
--static-map /images=/var/www/img
如果你接收到一个对 /images/logo.png
的请求,并且 /var/www/img/logo.png
存在,那么将会提供它。否则,你的应用会管理这个请求。
你可以多次指定 --static-map
选项,即使是对于同一个挂载点。
--static-map /images=/var/www/img --static-map /images=/var/www/img2 --static-map /images=/var/www/img3
在找到文件之前会在每个目录中进行搜索,如果找不到,这个请求就会交由你的应用管理。
在一些特定的情况下,你或许想要以一种不同的方式构建内部路径,保留请求的原始路径部分。 --static-map2
选项会完成此项工作。
--static-map2 /images=/var/www/img
对 /images/logo.png
的请求将会被当成 /var/www/img/images/logo.png
。
你可以同时 map
(或者 map2
) 目录和文件。
--static-map /images/logo.gif=/tmp/oldlogo.gif
# (psst: put favicons here)
模式4:使用高级内部路由¶
当映射不够的时候,高级内部路由 (自1.9起可用) 将是你最后的手段。
多亏了强大的正则表达式,你将能够构建非常复杂的映射。
[uwsgi]
route = /static/(.*)\.png static:/var/www/images/pngs/$1/highres.png
route = *\.jpg static:/var/www/always_the_same_photo.jpg
设置首页¶
默认情况下,对“目录” (例如/或者/foo)的请求会被跳过 (如果未使用高级内部路由)。
如果你想要映射指定文件到一个“目录”请求 (如久负盛名的index.html) ,那么仅需使用 --static-index
选项。
--static-index index.html --static-index index.htm --static-index home.html
正如其他选项一样,第一个匹配将会终止整个链。
MIME类型¶
你的对静态文件的HTTP/SPDY/随便什么响应应该总是返回特定文件正确的mime类型,从而让用户代理正确地处理它们。
默认情况下,uWSGI从 /etc/mime.types
文件构建它的MIME类型列表。你可以使用 --mime-file
选项加载额外的文件。
--mime-file /etc/alternatives.types --mime-file /etc/apache2/mime.types
所有的文件将会被组合成单个自动优化的链表。
跳过指定扩展¶
一些平台/语言,最值得注意的是基于CGI的,例如PHP,是以一种非常简单的方式进行部署的。
你只需把它们放在文档的根目录下,然后每当你调用它们的时候,就会执行。
当与静态文件服务组合的时候,这个方法需要你注意一下,避免你的CGI/PHP/其他什么的会像静态文件一样被提供。
--static-skip-ext
将会达到理想效果。
CGI和PHP部署上的一个非常常见的模式是这个:
--static-skip-ext .php --static-skip-ext .cgi --static-skip-ext .php4
设置Expires头部¶
当提供静态文件的时候,尽情使用客户端浏览器缓存是明智之选。默认情况下,uWSGI会添加一个 Last-Modified
头部到所有的静态响应中,并且将遵循 If-Modified-Since
请求头部。
这对于高流量站点或许不够。你可以使用以下选项之一来添加自动 Expires
头部:
--static-expires-type
将会对指定的MIME类型设置Expires头部为指定秒数。--static-expires-type-mtime
类似,但是基于文件修改时间,而非当前时间。--static-expires
(和-mtime
) 将会为所有匹配指定正则表达式的文件名(在结束映射到文件系统之后)设置Expires头部。--static-expires-uri
(和-mtime
) 针对REQUEST_URI
匹配正则表达式--static-expires-path-info
(和-mtime
) 针对PATH_INFO
匹配正则表达式
# Expire an hour from now
--static-expires-type text/html=3600
# Expire an hour from the file's modification time
--static-expires-type-mtime text/html=3600
# Same as static-expires-type, but based on a regexp:
--static-expires /var/www/static/foo*\.jpg 3600
传输模式¶
如果你开发了一个异步/非阻塞应用,那么直接从uWSGI提供静态文件就不是一个大问题。
所有的传输都是以异步方式进行管理的,因此在此期间,你的应用都不会阻塞。
在多进程/多线程模式下,你的进程(或者线程)将会在文件的整个传输期间阻塞。
对于较小的文件,这不是个问题,但是对于较大的,将它们的传输卸载到其他什么的会是一个不错的想法。
你有多种方式做到这点:
X-Sendfile¶
如果你的web服务器支持X-Sendfile头部,并且访问了你想要发送的文件 (例如,它位于你的应用所在的机器上,或者可以通过NFS访问它),那么你可以用 --file-serve-mode x-sendfile
选项避免从你的应用中传输这个文件。
使用这个,uWSGI将只会生成响应头,并且将会委托web服务器传输物理文件。
X-Accel-Redirect¶
目前(2013年1月)只有Nginx支持。与X-Sendfile的工作方式相同,唯一的区别在于选项参数。
--file-serve-mode x-accel-redirect
卸载¶
如果你的前端服务器不能访问静态文件的话,这是最好的方法。它使用 uWSGI 卸载(offloading)子系统 来委托文件传输给非阻塞线程池。
这些线程每一个都能并发管理数以千计的文件传输。
要启用文件传输卸载,只需使用选项 --offload-threads
,指定要生成线程的数目 (试着将其设为CPU核心数,来利用SMP)。
GZIP (uWSGI 1.9)¶
uWSGI 1.9可以检测一个静态文件的 *.gz
变体。
许多用户/系统管理员低估了即使Gzip编码的CPU影响。
每次都压缩文件 (除非你对web服务器以某种方式对它们进行缓存) 将使用CPU,而你将不能够使用高级的 (零拷贝) 技术,例如 sendfile()
。对一个非常满载的站点(或网络),这可能会是个问题(特别是当gzip编码对于一个更好更灵敏的用户体验而言是必须的时候)。
虽然uWSGI能够即时压缩内容 (例如,这用于HTTP/HTTPS/SPDY路由器),但是提供gzip压缩的静态文件最好的方法是“手动” (但请使用脚本,而不是让实习生来做这件事) 生成它们,然后让uWSGI选择每次最好提供压缩的还是未压缩的。
用这种方式,提供gzip内容将与提供标准静态文件 (sendfile, 卸载……) 没啥区别
要触发这个行为,你有多种选择:
static-gzip <regexp>
对所有匹配到指定正则表达式(该正则表达式被应用到文件的完整文件系统路径)的请求文件检测.gz变体static-gzip-dir <dir>
/static-gzip-prefix <prefix>
对位于指定目录下的所有的文件检测.gz变体static-gzip-ext <ext>
/static-gzip-suffix <suffix>
对所有具有指定扩展/后缀的文件检测.gz变体static-gzip-all
对所有请求的静态文件检测.gz变体
所以基本上,如果你有 /var/www/uwsgi.c
和 /var/www/uwsgi.c.gz
,那么接收gzip作为它们的Content-Encoding的客户端将会透明地提供gzip压缩版本。
安全¶
每一个静态映射都将完整转换成“正在的”路径 (因此也会转换符号链接)。
如果结果路径并不位于选项中指定的路径下,那么将会触发一个安全性错误,并且拒绝该请求。
如果你信任你的UNIX技能,并且知道你在做什么,那么你可以添加“安全”路径列表。如果一个转换路径并不位于一个配置目录下,但位于一个安全路径下,那么仍然会提供。
例如:
--static-map /foo=/var/www/
/var/www/test.png
是到 /tmp/foo.png
的符号链接
在 /foo/test.png
转换后,uWSGI会引发一个安全性错误,因为 /tmp/foo.png
并不位于 /var/www/
下。
使用
--static-map /foo=/var/www/ --static-safe /tmp
将绕过那个限制。
你可以多次指定 --static-safe
选项。
缓存路径映射/解析¶
静态文件服务的瓶颈之一是恒定大量的 stat()
系统调用。
你可以使用uWSGI缓存系统来存储从URI到文件系统路径的映射。
--static-cache-paths 30
将会缓存每个静态文件转换在uWSGI缓存中,时间为30秒。
从uWSGI 1.9起,添加了一个更新的缓存子系统,运行你创建多个缓存。如果你想要存储转换到一个指定的缓存中,那么你可以使用 --static-cache-paths-name <cachename>
。
福利:在缓存中存储静态文件¶
你可以使用选项 --load-file-in-cache <filename>
(可以多次指定它) 在启动期间直接把一个静态文件存储到uWSGI缓存中。该文件的内容将会被存储在键<filename>之下。
所以请注意 —— load-file-in-cache ./foo.png
将会把这个项作为 ./foo.png
存储,而非它的完整路径。
注意事项¶
- 静态文件服务子系统自动遵循If-Modified-Since HTTP请求头
SNI - 服务器名称识别 (SSL节点的虚拟主机)¶
uWSGI 1.9 (代号为”ssl as p0rn”) 添加了对SNI (服务器名称识别,Server Name Identification) 的支持,贯穿整个SSL子系统。HTTPS路由器,SPDY路由器和SSL路由器都可以透明使用它。
SNI是SSL标准的一个扩展,它允许客户端为它想要的资源指定一个“名字”。名字通常是请求主机名,因此你可以像使用HTTP Host:
头部那样实现类虚拟主机行为,而无需而外的IP地址等。
在uWSGI中,一个SNI对象是由名字和值组成的。名字是服务器名/主机名,而值是“SSL上下文” (你可以把它想成特定领域的证书、密钥和密码的总和)。
添加SNI对象¶
要添加一个SNI对象,只需使用 --sni
选项:
--sni <name> crt,key[,ciphers,client_ca]
例如:
--sni "unbit.com unbit.crt,unbit.key"
或者对于基于客户端的SSL鉴权和OpenSSL HIGH加密级别
--sni "secure.unbit.com unbit.crt,unbit.key,HIGH,unbit.ca"
添加复杂SNI对象¶
有时,对于你的SNI对象,你需要更复杂的密钥 (例如当使用通配符证书时)
如果你构建了带PCRE/正则表达式支持的uWSGI (你应该会),那么你可以使用 --sni-regexp
选项。
--sni-regexp "*.unbit.com unbit.crt,unbit.key,HIGH,unbit.ca"
海量SNI托管¶
uWSGI的主要目的之一是海量托管,因此,不支持这样的SSL将会让人相当郁闷。
如果你有映射到相同的IP地址的几十个 (或者对于这个问题,几百个) 证书,那么你可以简单将它们放在一个目录中 (遵循一个简单的约定,我们将复杂一点) ,然后每当需要为一个域名查找环境的时候,让uWSGI扫描它。
要添加一个目录,仅需使用
--sni-dir <path>
例如
--sni-dir /etc/customers/certificates
现在,如果你有 unbit.com
和 example.com
证书= (.crt) ,以及密钥 (.key),那么仅需把它们放在那里,遵循以下命名规则:
/etc/customers/certificates/unbit.com.crt
/etc/customers/certificates/unbit.com.key
/etc/customers/certificates/unbit.com.ca
/etc/customers/certificates/example.com.crt
/etc/customers/certificates/example.com.key
正如你所看到的, example.com
没有.ca文件,因此将会对其禁用客户端鉴权。
如果你想要强制设置一个默认的密码到SNI上下文中,那么使用
--sni-dir-ciphers HIGH
(或者任何其他你需要的值)
注:不支持卸载SNI对象。一旦将其加载到内存,就会一直持有它们,直到重载。
订阅系统和SNI¶
uWSGI 2.0在订阅系统中添加了对SNI的支持。
https/spdy路由器和sslrouter可以动态加载来自一个订阅包指定的路径的证书和密钥:
uwsgi --subscribe2 key=mydomain.it,socket=0,sni_key=/foo/bar.key,sni_crt=/foo/bar.crt
这个路由器将会基于指定的文件(确保路由器可以访问它们)创建一个新的SSL上下文,并将当最后一个节点断开连接的时候会销毁它。
这对于海量托管有用,其中,客户的证书位于家目录下,并且你想要他们不用找你就可以修改/更新那些文件。
注解
我们明白,直接在订阅包中封装密钥和证书会更有用得多,但是从安全的角度来看,密钥的网络传输真的有点蠢。我们正在研究将它与安全订阅系统(其中,每个包都被加密)结合起来是否是一个解决方法。
GeoIP插件¶
geoip
插件添加新的路由变量到你的内部路由子系统中。
GeoIP变量前缀为”geoip”标签。要构建geoip插件,你需要官方的GeoIP C库和它的头文件。支持的数据库是country和city,它们会在启动的时候完全加载到内存中。
可以访问country数据库的以下变量:
${geoip[country_code]}
${geoip[country_code3]}
${geoip[country_name]}
而以为存储数据而增长的内存使用为代价,city数据库提供更多变量
${geoip[continent]}
${geoip[country_code]}
${geoip[country_code3]}
${geoip[country_name]}
${geoip[region]}
${geoip[region_name]}
${geoip[city]}
${geoip[postal_code]}
${geoip[latitude]}
(${geoip[lat]}
)${geoip[longitude]}
(${geoip[lon]}
)${geoip[dma]}
${geoip[area]}
启用geoip查询¶
要启用GeoIP查询系统,你需要加载至少一个数据库。在加载完geoip插件后,你会获得2个新的选项:
--geoip-country
指定一个country数据库--geoip-city
指定一个city数据库
如果你没有至少指定一个数据库,那么,该系统将会总是返回空字符串。
一个例子¶
[uwsgi]
plugin = geoip
http-socket = :9090
; load the geoip city database
geoip-city = GeoLiteCity.dat
module = werkzeug.testapp:test_app
; first some debug info (addvar will ad WSGI variables you will see in the werkzeug testapp)
route-run = log:${geoip[country_name]}/${geoip[country_code3]}
route-run = addvar:COUNTRY=${geoip[country_name]}
route-run = log:${geoip[city]}/${geoip[region]}/${geoip[continent]}
route-run = addvar:COORDS=${geoip[lon]}/${geoip[lat]}
route-run = log:${geoip[region_name]}
route-run = log:${geoip[dma]}/${geoip[area]}
; then something more useful
; block access to all of the italians (hey i am italian do not start blasting me...)
route-if = equal:${geoip[country_name]};Italy break:403 Italians cannot see this site :P
; try to serve a specific page translation
route = ^/foo/bar/test.html static:/var/www/${geoip[country_code]}/test.html
内存使用¶
country数据库很小,因此你在使用它的时候一般没有问题。反而,city数据库能很大 (从20MB到大于40MB)。如果你有大量使用GeoIP的city数据库的实例,并且是使用最近的 Linux系统,那么考虑使用 在uWSGI中使用Linux KSM 来减少内存使用。 GeoIP数据库使用的所有内存可以由所有实例共享。
uWSGI转换¶
从uWSGI 1.9.7起,一个“转换”API已被添加到 uWSGI内部路由 。
一个转换就像是一个应用到由你的应用生成的响应上的过滤器。
转换可以是链式的 (一个转换的输出将会是接下来的转换的输入) ,并且可以完全覆盖响应头。
转换最常见的例子是gzip编码。你的应用的输出被传递到一个用gzip压缩它并且设置Content-Encoding头的函数中。这个特性依赖2个外部包:libpcre3-dev, Ubuntu上的libz-dev。
[uwsgi]
plugin = python,transformation_gzip
http-socket = :9090
; load the werkzeug test app
module = werkzeug.testapp:test_app
; if the client supports gzip encoding goto to the gzipper
route-if = contains:${HTTP_ACCEPT_ENCODING};gzip goto:mygzipper
route-run = last:
route-label = mygzipper
; pass the response to the gzip transformation
route = ^/$ gzip:
这个 cachestore
路由指令也是一个转换,因此,你可以缓存响应的各种状态。
[uwsgi]
plugin = python,transformation_gzip
http-socket = :9090
; load the werkezeug test app
module = werkzeug.testapp:test_app
; create a cache of 100 items
cache = 100
; if the client support gzip encoding goto to the gzipper
route-if = contains:${HTTP_ACCEPT_ENCODING};gzip goto:mygzipper
route = ^/$ cache:key=werkzeug_homepage
route = ^/$ cachestore:key=werkzeug_homepage
route-run = last:
route-label = mygzipper
route = ^/$ cache:key=werkzeug_homepage.gz
; first cache the 'clean' response (for client not supporting gzip)
route = ^/$ cachestore:key=werkzeug_homepage
; then pass the response to the gzip transformation
route = ^/$ gzip:
; and cache it again in another item (gzipped)
route = ^/$ cachestore:key=werkzeug_homepage.gz
另一个常见的转换是将样式表应用到XML文件上。 (见 XSLT插件)
toxslt
转换由xslt
插件公开:
uwsgi --plugin xslt --http-socket :9090 -w mycd --route-run "toxslt:stylesheet=t/xslt/cd.xml.xslt,params=foobar=test&agent=\${HTTP_USER_AGENT}"
这里的 mycd
模块是一个简单的XML生成器。它的输出稍后被传递给XSLT转换。
流与缓冲¶
每个转换宣告自己是一个“流”,还是一个“缓冲”。
流是可以应用到响应块(部分)的转换。一个流转换的例子是gzip (要开始压缩它,你无需完整的body)。缓冲转换是那些在应用某些操作之前需要完整的body的转换。XSLT就是一个缓冲转换的例子。另一个缓冲转换的例子是那些用于将响应存储在某些缓存中的操作。
如果你整个管道是只由“流”转换组成的,那么你的客户端将会按块接收输出。另一方面,单个缓冲转换会让整个管道缓冲,因此,你的客户端只会在最后获得输出。
常用的流功能是gzip + chunked:
[uwsgi]
plugins = transformation_gzip,transformation_chunked
route-run = gzip:
route-run = chunked:
...
整个转换管道是由流插件组成的,英寸你将会实时获得每个HTTP块。
flush魔法¶
“flush”转换是特别的。它让你发送当前转换缓冲内容到客户端 (无需清理缓冲)。
你可以用它在应用缓冲的时候实现流模式。常用的例子是流+缓存:
[uwsgi]
plugins = transformation_toupper,transform_tofile
; convert each char to uppercase
route-run = toupper:
; after each chunk converted to upper case, flush to the client
route-run = flush:
; buffer the whole response in memory for finally storing it in a file
route-run = tofile:filename=/tmp/mycache
...
你可以多次以及在链的不同部分调用flush。试验一下吧...
可用转换 (最新更新 20130504)¶
gzip
, 由transformation_gzip
插件公开 (编码响应缓冲到gzip)toupper
, 由transformation_toupper
插件公开 (样例插件转换每个字符为大写)tofile
, 由transformation_tofile
插件公开 (用来将响应缓冲缓存到一个静态文件中)toxslt
, 由xslt
插件公开 (应用xslt样式表到一个XML响应缓冲)cachestore
, 由router_cache
插件公开 (缓存响应缓冲到uWSGI缓存中)chunked
, 以HTTP chunked编码输出flush
, 刷新当前缓冲到客户端memcachedstore
, 将响应缓冲存储到一个memcached对象上redisstore
, 将响应缓冲存储到一个redis对象上template
, 应用路由转换到每个块上
正在进行中¶
rpc
, 允许应用rpc函数到一个响应缓冲 (限制64k大小)lua
, 应用一个lua函数到一个响应缓冲 (无大小限制)
WebSocket支持¶
在uWSGI 1.9中,添加了一个高性能的websocket (RFC 6455) 实现。
虽然对于WebSockets,存在许多不同的解决方案,但是其中大多数依赖于更高级的语言实现,对于诸如游戏或者流这样的主题很少有足够好的。
默认编译uWSGI websockets实现。
Websocket支持是由20Tab S.r.l. http://20tab.com/ 赞助的。
它们发布了一个完整的游戏 (基于uWSGI websockets api的bomberman复制): https://github.com/20tab/Bombertab
一个echo服务器¶
这是一个uWSGI websockets应用的样子:
def application(env, start_response):
# complete the handshake
uwsgi.websocket_handshake(env['HTTP_SEC_WEBSOCKET_KEY'], env.get('HTTP_ORIGIN', ''))
while True:
msg = uwsgi.websocket_recv()
uwsgi.websocket_send(msg)
你无需操心保持连接,或者拒绝死掉的对端。 uwsgi.websocket_recv()
将会在后台为你做一切脏活。
握手¶
握手是一个websocket连接的第一个阶段。
要发送一个完整的握手响应,你可以使用 uwsgi.websocket_handshake([key,origin, proto])
函数。没有正确的握手,连接将会永远不会完成。
在1.9系列,key参数是必须的。在2.0+,你可以在不传递参数的情况下调用websocket_handshake (将会更加请求日期自动构建响应)。
发送¶
发送数据给浏览器是相当容易的。 uwsgi.websocket_send(msg)
– 没有更多的了。
接收¶
这是整个实现真正核心的部分。
这个函数实际上隐瞒了它的实际目的。它确实返回一个websocket消息,但它实际上也保持连接打开 (使用ping/pong子系统),并且监控流状态。
msg = uwsgi.websocket_recv()
这个函数可以从一个命名通道(见下)接收消息,并且自动将其转发到你的websocket连接。
它将总是只返回从浏览器发送的websocket消息 – 任何其他通信则在后台进行。
也有一个非阻塞变体 – msg = uwsgi.websocket_recv_nb()
。见: https://github.com/unbit/uwsgi/blob/master/tests/websockets_chat_async.py
PING/PONG¶
要保持一个websocket连接打开,你应该不断发送ping (或者pong,见下文) 到浏览器,并期望它响应。如果来自浏览器/客户端的响应不及时到达,那么就会关闭连接 (uwsgi.websocket_recv()
将会引发一个异常)。除了ping之外, uwsgi.websocket_recv()
函数发送所谓的 ‘无偿pong’。它们被用来通知客户端服务端可用。
所有这些任务都发生在后台,你无需管理它们!
可用代理¶
不幸的是,并非所有的HTTP web服务器/代理都与websockets工作得很好。
uWSGI HTTP/HTTPS/SPDY路由器完美支持它们。只需记得添加
--http-websockets
选项。uwsgi --http :8080 --http-websockets --wsgi-file myapp.py
或者
uwsgi --http :8080 --http-raw-body --wsgi-file myapp.py
这有点更“原始”,但支持诸如块输入这样的东东。
- Haproxy正常工作。
- nginx >= 1.4 正常工作,并且无需额外的配置。
语言支持¶
- Python https://github.com/unbit/uwsgi/blob/master/tests/websockets_echo.py
- Perl https://github.com/unbit/uwsgi/blob/master/tests/websockets_echo.pl
- PyPy https://github.com/unbit/uwsgi/blob/master/tests/websockets_chat_async.py
- Ruby https://github.com/unbit/uwsgi/blob/master/tests/websockets_echo.ru
- Lua https://github.com/unbit/uwsgi/blob/master/tests/websockets_echo.lua
支持的并发模型¶
- 多进程
- 多线程
- uWSGI原生异步api
- Coro::AnyEvent
- gevent
- Ruby fibers + uWSGI async
- Ruby threads
- greenlet + uWSGI async
- uGreen + uWSGI async
- PyPy continulets
wss:// (基于https的websockets)¶
uWSGI HTTPS路由器可用websockets。只需记得在你的客户端代码中使用wss://作为连接scheme。
基于SPDY的Websockets¶
n/a
路由¶
http代理内部路由器直接支持websocket (假设你的前线代理已经支持它们了)
[uwsgi]
route = ^/websocket uwsgi:127.0.0.1:3032,0,0
或者
[uwsgi]
route = ^/websocket http:127.0.0.1:8080
Api¶
uwsgi.websocket_handshake([key, origin, proto])
uwsgi.websocket_recv()
uwsgi.websocket_send(msg)
uwsgi.websocket_send_binary(msg) (在1.9.21中添加,以支持二进制消息)
uwsgi.websocket_recv_nb()
uwsgi.websocket_send_from_sharedarea(id, pos) (在1.9.21中添加,允许直接从 SharedArea —— uWSGI组件间共享内存页 发送)
uwsgi.websocket_send_binary_from_sharedarea(id, pos) (在1.9.21中添加,允许直接从 SharedArea —— uWSGI组件间共享内存页 发送)
度量(Metrics)子系统¶
自1.9.19起可用。
uWSGI度量子系统让你可以管理来自应用的“数字”。
虽然在1.9开发周期内,缓存子系统获得了一些计算能力,但是度量子系统在设计上进行了优化,以存储数字并在其上应用函数。因此,与缓存子系统相比,它是一种更快的方式,并且需要消耗一小部分内存。
在启用后,度量子系统配置了大量的度量 (例如每核请求,内存使用,等等) ,但是,除此之外,你可以配置自己的度量,例如活跃用户数,或者,比方说,特定URL的访问数,以及应用或整个服务器的内存消耗。
要启用度量子系统,只需添加 --enable-metrics
到选项中,或者配置一个统计数据推送器 (见下)。
度量子系统是完全线程安全的。
默认情况下,uWSGI会创建许多度量 (大部分是已经已计划的),因此,在添加你自己的度量之前,确保uWSGI并没有公开你所需要的度量。
度量类型¶
在处理度量之前,你需要了解每个度量表示的各种类型:
COUNTER (type 0)¶
这是一个一般增长的数字 (如请求数)。
GAUGE (type 1)¶
这是一个可以动态增长或减少的数字 (如worker的使用内存,或者CPU负载)。
ABSOLUTE (type 2)¶
这是一个绝对数字,如整个服务器的内存,或者磁盘的大小。
ALIAS (type 3)¶
这是一个指向另一个度量的虚拟度量。你可以用它来为已存在的度量赋予不同的名字。
度量收集器¶
一旦你定义了一个度量类型,那么你需要告诉uWSGI如何“收集”特定的度量。
有各种可用的收集器 (可以通过插件添加更多收集器)。
ptr
– 该值是从内存指针收集来的file
– 该值是从文件收集来的sum
– 该值是其他度量的总和avg
– 计算孩子的算术平均值 (在1.9.20中添加)accumulator
– 总是增加孩子的值的总和到最后的值。看看下面这个例子。Round 1: child1 = 22, child2 = 17 -> metric_value = 39 Round 2: child1 = 26, child2 = 30 -> metric_value += 56
multiplier
- 用指定的参数 (arg1n) 乘以孩子的值的总和。child1 = 22, child2 = 17, arg1n = 3 -> metric_value = (22+17)*3
func
- 每次调用指定的函数计算该值。manual
- NULL收集器。必须使用度量API,从应用手动更新该值。
自定义度量¶
你可以在你的应用中定义额外的度量。
--metric
选项允许你添加更多度量。
它有两种语法:“简化的”和“键值”。
uwsgi --http-socket :9090 --metric foobar
将会创建一个度量’foobar’,它的类型是“counter”,手工收集,并且无oid。
要创建高级度量,那么你需要键值方式:
uwsgi --http-socket :9090 --metric name=foobar,type=gauge,oid=100.100.100
可以使用以下的键:
name
– 设置度量名oid
– 设置度量oidtype
– 设置度量类型,可以是counter
,gauge
,absolute
,alias
initial_value
– 启动时设置度量为一个指定的值。freq
– 设置收集频率,以秒为单位 (默认为1)reset_after_push
– 在度量被推到后端后,重置度量为0 (或者配置的initial_value
) (因此是每freq
秒)children
– 映射孩子到该度量 (见下)alias
– 该度量将会是指定的度量的一个简单别名 (–metric name=foobar,alias=worker.0.requests,type=alias)arg1
到arg3
– 基于字符串的参数 (见下)arg1n
到arg3n
– 基于数字的参数 (见下)collector
设置收集器,可以是ptr
,file
,sum
,func
或者任何插件公开的收集器。如果没有指定收集器,那么说明该度量是手工收集的 (你的应用需要更新它)。
当前未实现ptr,而其他收集器则需要一点额外的配置:
collector=file
需要 arg1
作为文件名,以及一个可选的 arg1n
作为所谓的分割值。
uwsgi --metric name=loadavg,type=gauge,collector=file,arg1=/proc/loadavg,arg1n=1,freq=3
这将会添加一个 loadavg 度量,类型为gauge,使用 /proc/loadavg
的内容每3秒更新一次。内容会被分割 (使用\n, \t, spaces, \r和0作为分隔符),并且第一项 (返回数组是基于0的) 会被当成返回值使用。
分割符是非常强大的,它使得从更复杂的文件(例如 /proc/meminfo
)中收集信息成为可能。
uwsgi --metric name=memory,type=gauge,collector=file,arg1=/proc/meminfo,arg1n=4,freq=3
一旦分割, /proc/meminfo
在第4个位置上保存的是MemFree值。
collector=sum
要求度量列表必须加起来。每个度量都有“孩子”的概念。总和收集器将会对它所有的孩子的值进行求和:
uwsgi --metric name=reqs,collector=sum,children=worker.1.requests;worker.2.requests
这将会每秒计算worker.1.requests和worker.2.requests值的总和。
collector=func
是一个方便的收集器,避免你为了添加一个新的收集器而去编写整个插件。
让我们定义一个C函数 (称这个文件为mycollector.c或者任何你想要的名字):
int64_t my_collector(void *metric) {
return 173;
}
然后把它作为一个共享库进行构建……
gcc -shared -o mycollector.so mycollector.c
现在,运行uWSGI来加载库……
uwsgi --dlopen ./mycollector.so --metric name=mine,collector=func,arg1=my_collector,freq=10
这将会每10秒调用C函数my_collector,并且将会设置度量’mine’的值为该函数的返回值。
这个函数必须返回一个 int64_t
值。它接收的参数是一个 uwsgi_metric
指针。一般来说,你不需要解析这个度量,因此,只要把它转换成void,就能避免很多糟心的事。
度量目录¶
UNIX系统管理员超爱文本文件。它们一般就是系统管理员们大部分时间必须处理的东东。如果你想取悦一个UNIX系统管理员,那么只需给他/她一些文本文件。 (或者一些咖啡,又或者也许是些威士忌,这取决于他们的口味。但一般来说,文本文件应该就可以了。)
度量子系统可以将它所有的度量,以文本文件的形式公开到一个目录中:
uwsgi --metrics-dir mymetrics ...
这个目录必须预先存在。
这将在’mymetrics’目录中,为每个度量创建一个文本文件。每个文件的内容是度量的值 (实时更新)。
每个文件被映射到进程的地址空间,因此,如果你的虚拟内存稍有增加,也不用担心。
恢复度量(持久化度量)¶
当你重启一个uWSGI实例的时候,会重置它所有的度量。
这一般是最好的,但如果你想要,你可以使用之前存储在定义的度量目录中的值来恢复先前的状态。
只需增加 --metrics-dir-restore
选项,来强制度量子系统在开始收集值之前,从度量目录读回度量值。
API¶
你的语言插件应该至少公开以下API函数。目前,它们已经在Perl, CPython, PyPy和Ruby中实现了。
metric_get(name)
metric_set(name, value)
metric_set_max(name, value)
– 只有当给定的 value 比当前存储的值大的时候,才设置度量 namemetric_set_min(name, value)
– 只有当给定的 value 比当前存储的值小的时候,才设置度量 name当你需要将一个度量设置为最大值或最小值的时候,
metric_set_max
和metric_set_min
可以被用来避免必须调用metric_get
。另一个简单的用例时使用avg
收集器来收集一些被设置成 max 和 min 的度量之间的平均值。metric_inc(name[, delta])
metric_dec(name[, delta])
metric_mul(name[, delta])
metric_div(name[, delta])
metrics (度量键的元组/数组,应该是不可变并且不可调用的,当前未实现)
统计信息推送器¶
可以将已收集的度量发送到外部系统,用于分析或者图表生成。
统计信息推送器是旨在发送度量给那些系统的插件。
目前,有两种类型的统计信息推送器:JSON和raw
JSON统计信息推送器发送整个JSON统计信息块 (与你从统计信息服务器获取的相同),而’raw’则户发送度量列表。
目前可用的统计信息推送器:
rrdtool¶
- 类型: raw
- 插件: rrdtool (默认内置)
- 需要(运行时): librrd.so
- 语法:
--stats-push rrdtool:my_rrds ...
这将会为每个度量存储一个rrd文件到指定的目录中。每个rrd文件都有一个单一的数据来源,名为’metric’。
用法:
uwsgi --rrdtool my_rrds ...
# or
uwsgi --stats-push rrdtool:my_rrds ...
默认情况下,每300秒更新RRD文件。你可以使用 --rrdtool-freq
来调整这个值
在运行时检测librrd.so库。如果你需要,那么你可以使用 --rrdtool-lib
来指定它的绝对路径。
statsd¶
- 类型: raw
- 插件: stats_pusher_statsd
- 语法:
--stats-push statsd:address[,prefix]
推送度量给一个statsd服务器。
用法:
uwsgi --stats-push statsd:127.0.0.1:8125,myinstance ...
carbon¶
- 类型: raw
- 插件: carbon (built-in by default)
- 见: 与Graphite/Carbon集成
zabbix¶
- 类型: raw
- 插件: zabbix
- 语法:
--stats-push zabbix:address[,prefix]
推送度量给一个zabbix服务器。
该插件公开了一个 --zabbix-template
选项,它将会生成一个zabbix模板 (在标准输出或者在指定的文件中),该模板包含所有公开的度量作为捕获项。
注解
在一些Zabbix版本,你将需要授权获准推送的IP地址。
用法:
uwsgi --stats-push zabbix:127.0.0.1:10051,myinstance ...
mongodb¶
- 类型: json
- 插件: stats_pusher_mongodb
- 需要(运行时): libmongoclient.so
- 语法(键值):
--stats-push mongodb:addr=<addr>,collection=<db>,freq=<freq>
推送统计信息 (JSON格式) 到指定的MongoDB数据库。
socket¶
- 类型: raw
- 插件: stats_pusher_socket (builtin by default)
- 语法:
--stats-push socket:address[,prefix]
使用以下格式将度量推送到一个UDP服务器: <metric> <type> <value>
(<type> 是前面报告的数字形式)。
例子:
uwsgi --stats-push socket:127.0.0.1:8125,myinstance ...
告警/阈值¶
你可以为每个度量配置一个或多个“阈值”。
一旦到达了这个限制,那么就会触发指定的告警 (见 uWSGI告警子系统 (自1.3起))。
一旦传递了该告警,你可以选择重置计数器为一个指定的值 (一般是0),或者继续以特定的比率触发告警。
[uwsgi]
...
metric-alarm = key=worker.0.avg_response_time,value=2000,alarm=overload,rate=30
metric-alarm = key=loadavg,value=3,alarm=overload,rate=120
metric-threshold = key=mycounter,value=1000,reset=0
...
不需要指定告警。使用阈值来自动重置一个度量是非常有效的。
注意: --metric-threshold
和 --metric-alarm
是同个选项的别名。
SNMP集成¶
内嵌SNMP服务器 服务器从1.3.6.1.4.1.35156.17.3 OID开始,公开了度量。
例如,要获得 worker.0.requests
的值:
snmpget -v2c -c <snmp_community> <snmp_addr>:<snmp_port> 1.3.6.1.4.1.35156.17.3.0.1
记住:只有具有相关的OID的度量可以通过SNMP使用。
内部路由集成¶
‘’router_metrics’’ 插件 (默认内置) 添加了一系列的动作到内部路由子系统。
metricinc:<metric>[,value]
increase the <metric>metricdec:<metric>[,value]
decrease the <metric>metricmul:<metric>[,value]
multiply the <metric>metricdiv:<metric>[,value]
divide the <metric>metricset:<metric>,<value>
set <metric> to <value>
除了动作之外,还添加了一个名为”metric”的路由变量。
例子:
[uwsgi]
metric = mymetric
route = ^/foo metricinc:mymetric
route-run = log:the value of the metric 'mymetric' is ${metric[mymetric]}
log-format = %(time) - %(metric.mymetric)
请求日志记录¶
你可以使用 %(metric.xxx) 占位符,在你的请求日志格式中访问度量值:
[uwsgi]
log-format = [hello] %(time) %(metric.worker.0.requests)
官方已注册度量¶
这是一项正在进行的工作。
要知道公开了哪个默认度量的最好的方式是启用统计信息服务器,然后查询它 (或者添加 --metrics-dir
选项)。
- worker/3 (公开关于worker的信息,例如 worker.1.requests [或者3.1.1] 报告了由worker 1提供服务的请求数)
- plugin/4 (用于由插件自动添加的度量的命名空间,例如plugins.foo.bar)
- core/5 (用于一般实例信息的命名空间)
- router/6 (用于核心路由器的命名空间,例如router.http.active_sessions)
- socket/7 (用于socket的命名空间,例如socket.0.listen_queue)
- mule/8 (用于mule的命名空间,例如mule.1.signals)
- spooler/9 (用于spooler的命名空间,例如spooler.1.signals)
- system/10 (用于系统度量的命名空间,例如loadavg或者空闲内存)
为插件分配OID¶
如果你想要编写一个会公开度量的插件,那么请添加OID命名空间,你将会用到下面的列表的OID命名空间,并且先进行pull请求。
这将确保所有的插件都使用唯一的OID命名空间。
给所有的插件度量名加上插件名前缀,来确保当相同的键用在多个插件中的时候没有冲突 (example plugin.myplugin.foo.bar, worker.1.plugin.myplugin.foo.bar)
- (3|4).100.1 - cheaper_busyness
外部工具¶
块输入API¶
已在uWSGI 1.9.13中添加了一个用于管理HTTP块输入请求的API。
这个API是非常低层次的,允许与标准的应用进行集成。
只公开了两个函数:
chunked_read([timeout])
chunked_read_nb()
(从uWSGI 1.9.20起) 在CPython, PyPy和Perl上都支持这个API
读取块¶
要读取一个块(阻塞),只需运行
my $msg = uwsgi::chunked_read
如果未指定超时时间,则会使用默认值。如果你没有及时获得一个块,那么这个函数将会嚷嚷 (或者在Python下,引发一个异常)。
在非阻塞/异步引擎下,你或许想要使用
my $msg = uwsgi::chunked_read_nb
如果没有可用的块(并且错误时抛出了异常),那么该函数将会立即返回 undef
(或者在Python上是 None
)。
一个完整的PSGI流回显例子:
# simple PSGI echo app reading chunked input
sub streamer {
$responder = shift;
# generate the headers and start streaming the response
my $writer = $responder->( [200, ['Content-Type' => 'text/plain']]);
while(1) {
my $msg = uwsgi::chunked_read;
last unless $msg;
$writer->write($msg);
}
$writer->close;
}
my $app = sub {
return \&streamer;
};
调整块缓冲¶
在开始读取块之前,uWSGI会分配一个固定的缓冲来存储块。
所有的消息总是会存储在相同的缓冲中。如果接收到了比缓冲大的消息,那么会引发一个异常。
默认情况下,缓冲限制为1MB。你可以用 --chunked-input-limit
选项来调整它 (单位是字节)。
与代理集成¶
如果你计划把uWSGI放在代理/路由器之后,那么确保它支持块输入请求 (或者一般原始HTTP请求)。
当使用uWSGI HTTP路由器时,只需添加–http-raw-body来支持块输入。
HAProxy属于开箱即用。
Nginx >= 1.4支持块输入。
选项¶
--chunked-input-limit
: 一个块消息的限制 (以字节为单位,默认是1MB)--chunked-input-timeout
: 阻塞chunked_read的默认超时时间 (以秒为单位,默认与–socket-timeout值相同,4秒)
小抄¶
- 在消耗了请求体哪怕是一字节之后调用块API函数是错误的 (这包含
--post-buffering
)。 - 块API函数可以被”Transfer-Encoding: chunked” 头的存在独立调用。
使用uWSGI进行扩大化¶
uWSGI cheaper子系统 —— 适应性进程生成¶
uWSGI通过可插拔算法,提供了动态扩展运行的worker数量的能力。使用 uwsgi --cheaper-algos-list
来获取可用算法的列表。
用法¶
要启用cheaper模式,则添加 cheaper = N
选项到uWSGI配置文件中,其中,N是uWSGI可以运行的worker的最小数目。
cheaper
必须小于配置的worker最大数目
(workers
或者 processes
选项)。
# set cheaper algorithm to use, if not set default will be used
cheaper-algo = spare
# minimum number of workers to keep at all times
cheaper = 2
# number of workers to spawn at startup
cheaper-initial = 5
# maximum number of workers that can be spawned
workers = 10
# how many workers should be spawned at a time
cheaper-step = 1
这个配置将会告诉WSGI负载之下最多运行10个worker。如果应用处于idle状态,那么uWSGI将会停止worker,但它总是会让至少2个worker在运行。使用 cheaper-initial
,你可以控制在启动的时候应该生成几个worker。如果你的平均负载要求比最小数量的worker还要多,那么你可以让它们立即生成,然后在负载足够低的情况下,”省省” (杀死它们)。当cheaper算法决定它需要更多的worker时,它会生成它们的 cheaper-step
。这在你有一个高的最大worker数的时候有用 —— 否则在突然尖峰负载的情况下,它会花费大量的时间来一个一个生成足够的worker。
设置内存限制¶
自1.9.16起,可以设置rss内存限制,从而在没有达到进程数量限制,但是所有worker使用的rss内存的总数量达到指定限制的时候停止cheapter生成新worker。
# soft limit will prevent cheaper from spawning new workers
# if workers total rss memory is equal or higher
# we use 128MB soft limit below (values are in bytes)
cheaper-rss-limit-soft = 134217728
# hard limit will force cheaper to cheap single worker
# if workers total rss memory is equal or higher
# we use 160MB hard limit below (values are in bytes)
cheaper-rss-limit-hard = 167772160
注意:
- 硬限制是可选的,可以单独使用软限制。
- 硬限制值必须比软限制值高,这两个值都不应该彼此太接近。
- 硬限制值应该是软限制值 + 至少是用于给定应用的平均worker内存使用。
- 软限制值是cheaper的限制器,它不会生成更多的worker,但已运行中的worker的内存使用可能会增长,要处理它,也可以设置reload-on-rss。建议为应用内存使用cgroups设置牢不可破的限制。
spare
cheaper算法¶
这是默认的算法。如果所有的worker在 cheaper_overload
秒内都处于忙碌状态,那么uWSGI将会生成新的worker。当负载消失的时候,它将开始一次停止一个进程。
spare2
cheaper算法¶
这个算法与spare类似,但适用于更快增长worker数,并且较慢降低worker数的大规模。
当idle状态的worker数比 cheaper
指定的数量小的时候,它生成 (cheaper
-
idle状态的worker数) worker。一次生成worker的数量的最大值可以通过 cheaper-step
来限制。例如。 cheaper
是4,有2个idle状态的worker,并且 cheaper-step
为1,它会生成1个worker。
当idle状态的worker数比 cheaper
大时,它会增加内部计数器。当idle状态的worker数小于或等于 cheaper
时,会重置计数器。
当计数器等于 cheaper-idle
时,省掉一个worker,然后重置计数器。
样例配置:
workers = 64 # maximum number of workers
cheaper-algo = spare2
cheaper = 8 # tries to keep 8 idle workers
cheaper-initial = 8 # starts with minimal workers
cheaper-step = 4 # spawn at most 4 workers at once
cheaper-idle = 60 # cheap one worker per minute while idle
backlog
cheaper算法¶
注解
backlog
只适用于Linux以及TCP socket (不是UNIX域的socket)。
如果socket的监听队列有超过 cheaper_overload
个请求在等待处理,那么uWSGI将会生成新的worker。如果积压降低,它将会开始一次杀掉一个进程。
busyness
cheaper算法¶
注解
这个算法是可选的,只有在编译并加载了 cheaper_busyness
插件的情况下,才可以用它。
该插件实现了这样一个算法,基于给定时间周期内的平均利用率来添加或移除worker。它的目标是保持在任意有比需要的最小值的worker数还多的worker可用,这样,应用将总是能够处理新的请求。如果你只想运行最小数量的worker,那么使用spare或者backlog算法。
使用该插件主要是因为spare和backlog插件工作的方式引发非常激进的缩放行为。如果你设置一个低的 cheaper
值
(例如,1),那么uWSGI将会一直只运行1个worker,然后只在运行中的那个worker过载的时候才生成新的worker。如果应用要求更多的worker,那么uWSGI将会一直生成停止worker。只有在非常低的负载期间,最小数量的worker才够。
Busyness算法试着与其相反:按需生成worker,然后只有在很有肯能不需要它们的时候才停止一些。这应该会使得worker数量更加稳定,并且更少进行重新生成。由于大部分的时间里,我们比实际需要的拥有更多的worker,因此平均应用响应时间应该比使用其他插件更低。
选项:
cheaper-overload¶
指定窗口,以秒为单位,用于追踪worker的平均busyness。例如:
cheaper-overload = 30
这个选项将会每30秒检查busyness。如果在上一个30秒期间,所有worker都是运行3秒,并且在剩下的27秒内出于idle状态,那么计算所得的busyness将会是10% (3/30)。这个值将会决定uWSGI可以多快响应负载尖峰。至少每 cheaper-overload
秒会生成新的worker (除非你在Linux上运行uWSGI —— 详情请见
cheaper-busyness-backlog-alert
)。
如果你想要更快地对负载尖峰进行响应,那么为这个值取一个小的值,这样,就会更频繁地计算busyness。记住,这可能会导致比需要的更频繁地启动/停止worker,因为每一个小的尖峰都会生成新的worker。使用一个高的 cheaper-overload
值,则worker数量将会更少发生改变,因为较长的周期将会吞掉所有负载短尖峰和极端值。默认是3,对于busyness插件,最好使用较高的值 (10-30)。
cheaper-step¶
当算法决策需要worker的时候,要生成worker的数目。默认是1。
cheaper-initial¶
启动应用的时候,启动的worker数。在应用启动之后,如果需要的话,算法可以停止或者启动worker。
cheaper-busyness-max¶
这是允许的最大busyness。每次上一个 cheaper-overload
秒计算的busyness比这个值高的时候,uWSGI将会生成
cheaper-step
新worker。默认是50.
cheaper-busyness-min¶
这是最小的busyness。如果当前的busyness位于该值之下,那么应用就会被认为处在一个“idle周期”中,而uWSGI将会开始对它们进行计数。一旦到达idle周期的所需数目,uWSGI将会杀掉一个worker。默认是25.
cheaper-busyness-multiplier¶
这个选项告诉uWSGI在停止一个worker之前,我们需要多少个idle周期。 在到达这个限制之后,uWSGI将会停止一个worker,并且设置这个计数器。
例如:
cheaper-overload = 10
cheaper-busyness-multiplier = 20
cheaper-busyness-min = 25
如果连续20次检查,每10秒执行一次(总共200秒),平均worker busyness低于25%,那么将会停止一个worker。如果平均busyness跳到 cheaper-busyness-max
以上,idle周期计数器将会被重置,并且我们生成新的worker。如果在idle周期计数期间,平均busyness跳到 cheaper-busyness-min
以上,但仍然低于 cheaper-busyness-max
,那么idle周期计数器将会被调整,而我们需要等待另外一个idle周期。如果在idle周期计数期间,平均busyness跳到 cheaper-busyness-min
以上,但仍然低于 cheaper-busyness-max
连续3次,那么会重置idle周期计数器。
cheaper-busyness-penalty¶
当worker因为足够的idle周期停止,然后快速(比worker所需的同等时间少)又生成回来的时候,uWSGI将会自动调整停止worker所需的idle周期数,然后我们会增加 cheaper-busyness-multiplier
值这个值。默认是1.
例如:
cheaper-overload = 10
cheaper-busyness-multiplier = 20
cheaper-busyness-min = 25
cheaper-busyness-penalty = 2
如果连续20次检查,每10秒执行一次(总共200秒),平均worker busyness低于25%,那么将会停止一个worker。如果新的worker在少于200秒内生成 (时间从我们生成它之前的最后一个worker算起),那么 cheaper-busyness-multiplier
值将会被增加到22 (20+2)。现在,我们将需要等待220秒 (22*10)来cheap另一个worker。这个选项用于防止worker一直启动和停止,因为一旦我们停止了一个worker,busyness也许会提供以致于够着 cheaper-busyness-max
。没有这个,或者如果被不充分地调整,我们会陷入一个停止/启动反馈循环。
cheaper-busyness-verbose¶
这个选项启用 cheaper_busyness
插件的调试日志。
cheaper-busyness-backlog-alert¶
这个选项只有在Linux上能用。它用于允许快速响应负载峰值,即使使用了高的 cheaper-overload
值。在每个uWSGI master周期(默认是1秒)中,会检查当前的监听队列。如果它比这个值高,那么会生成一个紧急worker。当使用这个选项的时候,使用高的 cheaper-overload
值以拥有更平滑的worker数缩放是安全的。默认是33.
cheaper-busyness-backlog-multiplier¶
这个选项只有在Linux上能用。它就像 cheaper-busyness-multiplier
,除了它只用于监听队列高于 cheaper-busyness-backlog-alert
时的紧急worker生成。
紧急worker是在大负载峰值下生成的,用以防止当前运行的worker过载。有时,负载峰值是随机并且短暂的,这会生成大量的紧急worker。在这种情况下,获得那些worker之前,我们会需要等待几个周期。这提供了一个交替的multiplier来更快的获得这些进程。默认是3.
cheaper-busyness-backlog-step¶
这个选项只有在Linux上能用。 它设置当监听队列高于 cheaper-busyness-backlog-alert
时生成的紧急worker数。默认是1.
cheaper-busyness-backlog-nonzero¶
这个选项只有在Linux上能用。如果超过N秒,请求监听队列都>0,那么它将会生成新的紧急worker。它用来保护服务器不受这样的边缘情况之害:只有单个worker运行,并且这个worker在处理一个长时间运行的请求。如果uWSGI收到了新的请求,那么它们将会待在请求队列中,直到那个长时间运行请求完成。使用这个选项,我们可以检测这样一个条件,并生成新的worker来防止入队请求超时。默认是60.
关于Busyness的一些注意事项¶
通过实验确定设置。对于每个人而言,没有哪个万金油值应该被使用。测试并挑选对你来说的最佳值。监控uWSGI统计数据 (例如,通过Carbon) 会使得决定使用哪个值简单一些。
不要指望busyness恒久不变。它会经常改变。最终,真正的用户是以非常随机的方式与你的应用进行交互的。推荐使用更长的–cheaper-overload值 (>=30) ,使得尖峰更少。
如果你想要运行这个插件的一些基准,那么你应该使用添加随机性到工作负载的工具。
使用少量的worker (2-3),启动新的worker,或者停止一个worker或许会大大影响busyness。如果你有2个worker,带50%的busyness,那么停止其中一个将会增加busyness到100%。记住,当挑选最小和最大程度的时候,如果大部分时间只有少量的worker在运行,那么max应该超过min的两倍,否则,每当停止一个worker的时候,它将会增加busyness到超过max程度。
使用少量的worker (1-4) 和默认设置,期望这个插件将会保持平静busyness低于最小程度;调整程度来补偿。
使用处理负载所需的更多数量的worker,worker计数器将会稳定在接近最小busyness程度的某处,在这个值附近上下波动。
在对这个插件进行实验的时候,建议启用
--cheaper-busyness-verbose
,从而知道它正在做什么。一个样例日志如下。# These messages are logged at startup to show current settings [busyness] settings: min=20%, max=60%, overload=20, multiplier=15, respawn penalty=3 [busyness] backlog alert is set to 33 request(s) # With --cheaper-busyness-verbose enabled You can monitor calculated busyness [busyness] worker nr 1 20s average busyness is at 11% [busyness] worker nr 2 20s average busyness is at 11% [busyness] worker nr 3 20s average busyness is at 20% [busyness] 20s average busyness of 3 worker(s) is at 14% # Average busyness is under 20%, we start counting idle cycles # we have overload=20 and multiplier=15 so we need to wait 300 seconds before we can stop worker # cycle we just had was counted as idle so we need to wait another 280 seconds # 1 missing second below is just from rounding, master cycle is every 1 second but it also takes some time, this is normal [busyness] need to wait 279 more second(s) to cheap worker # We waited long enough and we can stop one worker [busyness] worker nr 1 20s average busyness is at 6% [busyness] worker nr 2 20s average busyness is at 22% [busyness] worker nr 3 20s average busyness is at 19% [busyness] 20s average busyness of 3 worker(s) is at 15% [busyness] 20s average busyness is at 15%, cheap one of 3 running workers # After stopping one worker average busyness is now higher, which is no surprise [busyness] worker nr 2 20s average busyness is at 36% [busyness] worker nr 3 20s average busyness is at 24% [busyness] 20s average busyness of 2 worker(s) is at 30% # 30% is above our minimum (20%), but it's still far from our maximum (60%) # since this is not idle cycle uWSGI will ignore it when counting when to stop worker [busyness] 20s average busyness is at 30%, 1 non-idle cycle(s), adjusting cheaper timer # After a while our average busyness is still low enough, so we stop another worker [busyness] 20s average busyness is at 3%, cheap one of 2 running workers # With only one worker running we won't see per worker busyness since it's the same as total average [busyness] 20s average busyness of 1 worker(s) is at 16% [busyness] 20s average busyness of 1 worker(s) is at 17% # Shortly after stopping second worker and with only one running we have load spike that is enough to hit our maximum level # this was just few cycles after stopping worker so uWSGI will increase multiplier # now we need to wait extra 3 cycles before stopping worker [busyness] worker(s) respawned to fast, increasing cheaper multiplier to 18 (+3) # Initially we needed to wait only 300 seconds, now we need to have 360 subsequent seconds when workers busyness is below minimum level # 10*20 + 3*20 = 360 [busyness] worker nr 1 20s average busyness is at 9% [busyness] worker nr 2 20s average busyness is at 17% [busyness] worker nr 3 20s average busyness is at 17% [busyness] worker nr 4 20s average busyness is at 21% [busyness] 20s average busyness of 4 worker(s) is at 16% [busyness] need to wait 339 more second(s) to cheap worker
uWSGI Emperor —— 多应用部署¶
如果你需要在单个服务器,或者一组服务器上部署大量的应用,那么Emperor模式就是你的最佳选择。它是一个特殊的uWSGI实例,会监控特定的事件,并且会按需生成/停止/重载实例 (当被一个Emperor管理的时候,被称为 vassals)。
默认情况下,Emperor将会扫描特定目录搜索支持的(.ini,
.xml, .yml, .json, 等等) uWSGI配置文件,但可以使用
imperial monitor 插件来对其进行扩展。 dir://
和 glob://
插件是嵌入到核心中的,因此,不需要加载它们,它们会被自动检测到。 dir://
是默认插件。
- 每当一个imperial监控器检测到一个新的配置文件的时候,将会根据该配置文件生成一个新的uWSGI实例。
- 每当一个配置文件被修改的时候 (它的修改时间发生了改变,因此
touch --no-dereference
或许会是你的好伙伴),就会重载相应的应用。 - 每当一个配置文件被删除的时候,就会停止相应的应用。
- 如果emperor死掉了,那么所有的vassal都会死掉。
- 如果出于某种原因,一个vassal死掉了,那么emperor将会重新生成它。
可以通过多次指定 --emperor
来监控配置的多个来源。
参见
见 Imperial监控器 以获得uWSGI出厂的Imperial Monitor插件列表,以及如何使用它们。
Imperial监控器¶
dir://
– 扫描目录中的uWSGI配置文件¶
简单将你所有的配置文件放到一个目录中,然后将uWSGI emperor指向它。Emperor将会开始扫描这个目录。当它发现了一个有效的配置文件的时候,它将生成一个新的uWSGI实例。
在我们的例子中,我们部署一个 Werkzeug 测试应用,一个 Trac 实例,一个Ruby on Rails应用和一个 Django 应用。
werkzeug.xml
<uwsgi>
<module>werkzeug.testapp:test_app</module>
<master/>
<processes>4</processes>
<socket>127.0.0.1:3031</socket>
</uwsgi>
trac.ini
[uwsgi]
master = true
processes = 2
module = trac.web.main:dispatch_request
env = TRAC_ENV=/opt/project001
socket = 127.0.0.1:3032
rails.yml
uwsgi:
plugins: rack
rack: config.ru
master: 1
processes: 8
socket: 127.0.0.1:3033
post-buffering: 4096
chdir: /opt/railsapp001
django.ini
[uwsgi]
socket = 127.0.0.1:3034
threads = 40
master = 1
env = DJANGO_SETTINGS_MODULE=myapp.settings
module = django.core.handlers.wsgi:WSGIHandler()
chdir = /opt/djangoapp001
把这四个文件放到一个目录中,例如我们例子中的 /etc/uwsgi/vassals
,然后生成Emperor:
uwsgi --emperor /etc/uwsgi/vassals
Emperor将会在那个目录中查找uWSGI实例( dir://
插件是隐式声明的),然后启动所需守护进程来运行它们。
glob://
– 监控一个shell模式¶
glob://
与 dir://
类似,但是必须指定一个glob表达式:
uwsgi --emperor "/etc/vassals/domains/*/conf/uwsgi.xml"
uwsgi --emperor "/etc/vassals/*.ini"
注解
记得用引号将模式括住,否则你的shell将很有可能解析它,然后在调用的时候对其进行展开,这并不是你想要的。
由于Emperor可以搜索子目录层次的配置文件,因此你可以使用一个像这样的结构:
/opt/apps/app1/app1.xml
/opt/apps/app1/...all the app files...
/opt/apps/app2/app2.ini
/opt/apps/app2/...all the app files...
然后这样运行uWSGI:
uwsgi --emperor /opt/apps/app*/app*.*
pg://
– 扫描一个用于配置的PostgreSQL表¶
你可以针对PostgreSQL数据库来运行一个指定查询。它的结果必须是定义一个vassal的由3到6个字段组成的列表:
- 实例名,包含一个有效的uWSGI配置文件扩展名。 (例如
django-001.ini
) - 一个
TEXT
块,包含该vassal的配置,它的格式基于字段1的扩展。 - 一个数字,表示这一行的修改时间,使用UNIX格式 (自纪元起,以秒为单位)。
- vassal实例的UID。只在 Tyrant模式 (安全的多用户托管) 模式下是必须的。
- vassal实例的GID。只在 Tyrant模式 (安全的多用户托管) 模式下是必须的。
- 用于按需vassal激活的socket。如果指定该字段,那么就会在按需模式下运行vassal。如果省略或者为空,那么将会正常运行vassal。查看 按需(On demand)vassal (socket激活) 以获取更多信息。
uwsgi --plugin emperor_pg --emperor "pg://host=127.0.0.1 user=foobar dbname=emperor;SELECT name,config,ts FROM vassals"
- 每当添加了一个新的元组,就会创建一个新的实例,并且使用第二个字段中指定的配置来生成它。
- 每当修改时间字段发生了改变,就会重载该实例。
- 如果移除了一个元组,那么对应的vassal也将会被销毁。
mongodb://
– 扫描用于配置的MongoDB集合¶
uwsgi --plugin emperor_mongodb --emperor "mongodb://127.0.0.1:27107,emperor.vassals,{enabled:1}"
这将会扫描字段 enabled
设置为1的 emperor.vassals
集合中所有的document。一个兼容Emperor的document必须定义三个字段: name
, config
和 ts
。在 Tyrant模式 (安全的多用户托管) 模式下,需要2个额外的字段。对于按需vassal模式,也有可选的 socket
字段。
name
(字符串) 是vassal的名字 (记得给它一个有效的扩展名,例如.ini)config
(多行字符串) 是vassal配置,该配置的格式由name
的扩展名所描述。ts
(日期) 是配置的时间戳 (注意:MongoDB内部将时间戳以毫秒为单位进行存储。)uid
(数字) vassal实例的UID。仅在 Tyrant模式 (安全的多用户托管) 模式下是必须的。gid
(数字) vassal实例的GID。仅在 Tyrant模式 (安全的多用户托管) 模式下是必须的。socket
(字符串) 用于按需vassal激活的Socket。如果指定,那么 vassal将会运行在按需模式。如果省略或者为空,那么vassal将会正常运行。查看 按需(On demand)vassal (socket激活) 以获得更多信息。
amqp://
– 使用一个AMQP兼容的消息队列来宣告事件¶
把你的AMQP (例如,RabbitMQ) 服务器地址作为–emperor参数的值设置:
uwsgi --plugin emperor_amqp --emperor amqp://192.168.0.1:5672
现在,Emperor将会等待 uwsgi.emperor
交换机中的消息。这应该是一个类型为 fanout 的交换机,但是你可以根据你的特殊需求使用其他系统。消息是包含一个有效的uWSGI配置文件的绝对路径的简单字符串。
# The pika module is used in this example, but you're free to use whatever adapter you like.
import pika
# connect to RabbitMQ server
connection = pika.BlockingConnection(pika.ConnectionParameters('192.168.0.1'))
# get the channel
channel = connection.channel()
# create the exchange (if not already available)
channel.exchange_declare(exchange='uwsgi.emperor', type='fanout')
# publish a new config file
channel.basic_publish(exchange='uwsgi.emperor', routing_key='', body='/etc/vassals/mydjangoapp.xml')
你第一次启动脚本的时候,emperor将会添加新的实例 (如果该配置文件可用)。从那时开始,每当你重发布消息,应用将会被重载。当你移除配置文件的时候,应用也会被移除。
小技巧
你可以订购不同服务器上的所有emperor到这个交换机,从而实现集群同步的重载/部署。
使用HTTP的AMQP¶
uWSGI is capable of loading configuration files over HTTP. 这是一种非常方便的为大量主机动态生成配置文件的方式。简单在AMQP消息中声明配置文件的HTTP URL。记住,它必须以有效配置文件的扩展名结尾,但在钩子之下,它可以由脚本生成。如果该HTTP URL返回一个非200状态码,那么将会移除该实例。
channel.basic_publish(exchange='uwsgi.emperor', routing_key='', body='http://example.com/confs/trac.ini')
直接的AMQP配置¶
也可能直接通过AMQP提供配置文件。 routing_key
将是(虚拟)配置文件名,而消息将是配置文件的内容。
channel.basic_publish(
exchange='uwsgi.emperor',
routing_key='mydomain_trac_config.ini',
body="""
[uwsgi]
socket=:3031
env = TRAC_ENV=/accounts/unbit/trac/uwsgi
module = trac.web.main:dispatch_request
processes = 4""")
前一个模式的相同的重载规则是有效的。当你想要移除一个实例的时候,则简单将一个空的body作为“配置”进行设置。
channel.basic_publish(exchange='uwsgi.emperor', routing_key='mydomain_trac_config.ini', body='')
zmq://
– ZeroMQ¶
Emperor把自己绑定到一个ZeroMQ PULL socket,准备好接收命令。
uwsgi --plugin emperor_zeromq --emperor zmq://tcp://127.0.0.1:5252
每个命令都是一条通过PUSH zmq socket发送的多部分消息。一个命令至少由两部分组成: command
和 name
。 command
是要执行的动作,而 name
是vassal的名字。可以指定4个可选部分。
config
(一个包含vassal配置的字符串)uid
(tyrant模式下移除特权的用户id)gid
(tyrant模式下移除特权的组id)socket
(用于按需vassal激活的socket。如果指定,如果指定,那么 vassal将会运行在按需模式。如果省略或者为空,那么vassal将会正常运行。查看 按需(On demand)vassal (socket激活) 以获得更多信息。)
有两类命令 (目前):
touch
destroy
第一个用于创建和重载实例,而第二个用于销毁。如果你不指定一个配置字符串,那么Emperor将会假设你指的是Emperor当前目录下的一个可用的静态文件。
import zmq
c = zmq.Context()
s = zmq.Socket(c, zmq.PUSH)
s.connect('tcp://127.0.0.1:5252')
s.send_multipart(['touch','foo.ini',"[uwsgi]\nsocket=:4142"])
zoo://
– Zookeeper¶
目前正在开发中。
ldap://
– LDAP¶
目前正在开发中。
Emperor协议¶
从1.3开始,你可以通过 Emperor 生成自定义应用。
非uWSGI Vassal应该永不进行守护,以维持与Emperor的链接。如果你想/需要与Emperor更好的集成,那么实现Emperor协议。
协议¶
环境变量 UWSGI_EMPEROR_FD
会被传递给每个vassal,包含一个文件描述符号。
import os
has_emperor = os.environ.get('UWSGI_EMPEROR_FD')
if has_emperor:
print "I'm a vassal snake!"
或者在Perl中,
my $has_emperor = $ENV{'UWSGI_EMPEROR_FD'}
if ($has_emperor) {
print "I am a vassal.\n"
}
或者在C中,
int emperor_fd = -1;
char *has_emperor = getenv("UWSGI_EMPEROR_FD");
if (has_emperor) {
emperor_fd = atoi(has_emperor);
fprintf(stderr, "I am a vassal.\n");
}
从现在开始,你可以通过这个文件描述符从Emperor接收信息(发送信息到Emperor)。
消息时字节大小 (0-255),并且每个数字 (字节) 都有其含义。
0 | 由Emperor发送,用来停止一个vassal |
1 | 由Emperor发送,用来重载一个vassal / 当vassal已经生成时,由其发送 |
2 | 由vassal发送,用来向Emperor请求配置块 |
5 | 当vassal准备好接收请求时,由其发送 |
17 | 在第一个宣告loyalty的请求后,由vassal发送 |
22 | 由vassal发送,用来通知Emperor自愿的关闭 |
26 | vassal发送的心跳。在第一个接收到的心跳之后,Emperor将会期待更多来自该vassal的心跳。 |
30 | 由vassal发送,用来请求 利用Broodlord模式实现自动伸缩 模式。 |
按需(On demand)vassal (socket激活)¶
受古老的xinetd/inetd方法启发,你可以只在第一个到指定socket的连接之后才生成你的vassal。这个特性自1.9.1起可用。与–idle/–die-on-idle选项组合,你可以拥有真正按需应用。
当为特定的vassal激活按需特性的时候,emperor不会在启动(或者它的配置发生改变)之时生成它,但它将会为那个vassal创建socket,然后监控是否有任何东西连接到它上面。
在第一个连接时,会生成vassal,并将socket作为文件描述符0传递。文件描述符0总是由uWSGI检查,因此你无需在vassal文件中指定一个–socket选项。这会对uwsgi socket自动使用,如果你使用其他协议 (例如http或者fastcgi),那么你必须涌–protocol选项来指定它
重要
如果你将在你的vassal配置中定义与emperor用于按需动作相同的socket,那么vassal将会覆盖那个socket文件。那可能会导致非预期行为,例如,那个vassal的按需激活将会只能用一次。
带基于文件系统的帝国监控器的按需vassal¶
对于基于文件系统的帝国监控器,例如 dir://
或者 glob://
,定义按需vassal会调用用于你的emperor的三个额外的设置之一的定义:
–emperor-on-demand-extension <ext>¶
这将会指示Emperor检查一个名为<vassal>+<ext>的文件,如果这个文件可用,那么将会读取它,它的内容将会当成要等待的socket使用:
uwsgi --emperor /etc/uwsgi/vassals --emperor-on-demand-extension .socket
假设/etc/uwsgi/vassals中有一个myapp.ini文件,将会搜索一个/etc/uwsgi/vassals/myapp.ini.socket(然后它的内容会被当成socket名)。注意,myapp.ini.socket并不是一个socket!这个文件只包含实际socket的路径 (tcp或者unix)。
–emperor-on-demand-directory <dir>¶
这是一个不大通用的方法,只支持UNIX socket。基本上,vassal的名字(不带扩展名和路径)会被附加到指定目录之后,然后加上.socket扩展名,并被当成按需socket使用:
uwsgi --emperor /etc/uwsgi/vassals --emperor-on-demand-directory /var/tmp
使用前一个例子,socket /var/tmp/myapp.socket将会被自动绑定。
–emperor-on-demand-exec <cmd>¶
这是为按需动作定义socket的最灵活的方式,并且(非常有可能)你将会把它用在非常大型的部署中。每当添加了一个新的vassal,就会运行提供的命令,将完整的路径作为第一个参数传递给vassal配置文件。这个命令的STDOUT会被当成socket名使用。
使用带其他帝国监控器的按需vassal¶
对于一些帝国监控器来说,例如 pg://
, mongodb://
, zmq://
,按需激活的socket是由帝国监控器自身返回的。例如,对于 pg://
,如果数据库上执行的查询返回了超过5个字段,那么第6个字段将会被当成按需激活的socket使用。见 Imperial监控器 以获取更多信息。
对于一些帝国监控器来说,例如 amqp://
,尚不能使用socket激活。
将按需vassal与 --idle
和 --die-on-idle
组合在一起¶
对于真正的按需应用,你可以添加 --idle
和 --die-on-idle
选项到每个vassal中。这将会允许挂起火灾完全关闭那些不再处理请求的应用。不带 --die-on-idle
使用 --idle
的工作方式将会非常像不用emperor,但是添加了 --die-on-idle
将会赐予你完全关闭应用并返回按需模式的超能力。
当vassal优雅地死掉时,Emperor会简单地将它放回按需模式,当有任何等待的请求或者socket的时候,会将其唤回。
重要
正如前面提到的,你应该 永远不 把那个用于按需模式被传递给emperor的socket放到你的vassal配置文件中。对于unix socket,socket存活的文件路径将会被新的socket覆盖,但是老的socket将仍然连接到你的emperor。Emperor将会监听那个老的socket的连接,但所有的请求将会到新的socket上去。这意味着,如果你的vassal因为idle状态被关闭,那么它将 永远不 会被放回去 (emperor将不会为按需socket接收任何连接)。
对于tcp socket,那将会使得每个请求被处理 两次 。
特殊配置变量¶
将 占位符 与 魔术变量 和Emperor一起使用可能会节省你大量的时间,并且让你的配置更DRY。假设在/opt/apps中,只有 Django 应用。/opt/apps/app.skel ( .skel扩展名并不是uWSGI已知的配置文件类型,因此将会被跳过)
[uwsgi]
chdir = /opt/apps/%n
threads = 20
socket = /tmp/sockets/%n.sock
env = DJANGO_SETTINGS_MODULE=%n.settings
module = django.core.handlers.wsgi:WSGIHandler()
然后,为每个应用,创建一个符号链接:
ln -s /opt/apps/app.skel /opt/apps/app1.ini
ln -s /opt/apps/app.skel /opt/apps/app2.ini
最后,使用 --emperor-nofollow
选项启动Emperor。现在,你可以使用以下命令来分别重载每个vassal了:
touch --no-dereference $INI_FILE
传递配置参数给所有的vassal¶
从1.9.19开始,你可以使用 --vassal-set
来传递选项了
[uwsgi]
emperor = /etc/uwsgi/vassals
vassal-set = processes=8
vassal-set = enable-metrics=1
这将会添加 --set processes=8
和 --set enable-metrics=1
到每个vassal中
你还可以强制Emperor使用环境变量传递选项给uWSGI实例。每个形式为 UWSGI_VASSAL_xxx
的环境变量将会在新的实例中重写为 UWSGI_xxx
,并带有一般的
configuration implications.
例如:
UWSGI_VASSAL_SOCKET=/tmp/%n.sock uwsgi --emperor /opt/apps
会让你避免在配置文件中指定socket选项。
另外,你可以使用 --vassals-include
选项,让每个
vassal自动包含一个完整的配置文件:
uwsgi --emperor /opt/apps --vassals-include /etc/uwsgi/vassals-default.ini
注意,如果你这样做,被包含的文件中的 %n
(及其他魔术变量) 将被解析为被包含的文件的名字,而不是原始的vassal配置文件的名字。如果你想要使用vassal名在被包含的文件中设置选项,那么你必须使用占位符。例如,在vassal配置中,这样写:
[uwsgi]
vassal_name = %n
... more options
在 vassal-defaults.ini
中,这样写:
[uwsgi]
socket = /tmp/sockets/%(vassal_name).sock
Tyrant模式 (安全的多用户托管)¶
emperor通常以root运行,在每个实例的配置中设置UID和GID。然后,vassal实例在处理请求之前删除特权。在这个模式下,如果你的用户可以访问自己的uWSGI配置文件,那么你不能信任他们会设置正确的 uid
和
gid
。你可以以非特权用户运行emperor (使用 uid
和
gid
),但是所有的vassal之后将会运行在相同的用户之下,因因为非特权用户不能自己切换成其他用户。对于这种情况,可以使用Tyrant模式 —— 仅需添加 emperor-tyrant
选项。
在Tyrant模式下,Emperor会使用vassal配置文件(或者对于其他Imperial监控器,通过一些配置的其他方法)的UID/GID来运行vassal。如果使用了Tyrant模式,那么vassal配置文件必须是UID/GID > 0。如果UID或者GID为0,或者一个已经在运行的vassal的配置的UID或GID发生了变化,那么将会出现一个错误。
适用于偏执型系统管理员的Tyrant模式 (仅Linux)¶
如果你构建了一个启用了 设置POSIX capabilities 选项的uWSGI版本,那么你可以以非特权用户运行Emperor,但是维持应用到tyrant模式所需的最小的root能力。
[uwsgi]
uid = 10000
gid = 10000
emperor = /tmp
emperor-tyrant = true
cap = setgid,setuid
Loyalty¶
一旦一个vassal管理了一个请求,那么它将变得“loyal”。Emperor使用这个状态来识别行为不良的vassal并惩罚它们。
节流¶
每当在同一秒中生成了两个或以上的vassal,Emperor就会启动一个节流子系统来避免 fork bombing 。每当这种情况发生的时候,该系统就会增加一个节流差 (通过 OptionEmperorThrottle 选项指定,以毫秒为单位),然后在生成一个新的vassal之前等待该节流差指定的时间。每当一个新的vassal的生成不触发节流的时候,当前的节流持续时间就会减半。
黑名单系统¶
每当一个不loyal的vassal死掉的时候,它就会被放在一个耻辱黑名单中。当vassal在黑名单的时候,它就会被节流至一个最大值 (可以通过 OptionEmperorMaxThrottle 调整),从默认的节流差3开始。每当一个位于黑名单中的vassal死掉的时候,它的节流值就会增加节流差指定的数值 (OptionEmperorThrottle)。
你也可以通过发送信号SIGURG给emperor进程来清空黑名单。这将会重置节流值。
心跳系统¶
vassal可以自愿让Emperor监控它们的状态。启用了心跳的vassal的worker会发送”heartbeat”消息给Emperor。如果超过N(默认 30, OptionEmperorRequiredHeartbeat)秒后, Emperor都没有接收到来自一个实例的心跳,那么就会认为那个实例挂起,因此会对它进行重载。要在一个vassal中启动发送心跳包,则添加 OptionHeartbeat 选项。
重要
如果你所有的worker都卡在处理完全合法的请求,例如缓慢的大文件上传,那么Emperor将会触发重载,仿佛worker都挂起了。触发的重载是优雅地重载,因此你可以为理智的行为调整你的配置/超时时间/容忍度。
将Linux命名空间用于vassal¶
在Linux上,你可以告诉Emperor在“非共享”上下文中运行vassal。那意味着你可以使用文件系统、ipc、uts、网络、pid和uid的专用视图来运行每个vassal。
你通常使用诸如 lxc
或者它的抽象,例如 docker
,来做的事在uWSGI中都是自然而然的。
例如,如果你想在一个新的名字空间内运行每个vassal:
[uwsgi]
emperor = /etc/uwsgi/vassals
emperor-use-clone = fs,net,ipc,pid,uts
现在,每个vassal将能够修改文件系统布局,网络,主机名等等等等,而不会损坏主系统。
uWSGI发布版本中包含了几个辅助守护进程,用来简化监禁vassal的管理。最值得注意的是, TunTap路由器 允许jail中的完整用户空间网络,而 forkpty router
允许jail中的虚拟终端分配。
无需在你的vassal中取消共享所有的子系统,有时候,你只想要把专用的ipc和主机名给一个vassal,并从进程列表中隐藏:
[uwsgi]
emperor = /etc/uwsgi/vassals
emperor-use-clone = fs,ipc,pid,uts
vassal可以是:
[uwsgi]
; set the hostname
exec-as-root = hostname foobar
; umount /proc and remount to hide processes
; as we are in the 'fs' namespace umounting /proc does not interfere with the main one
exec-as-root = umount /proc
exec-as-root = mount -t proc none /proc
; drop privileges
uid = foobar
gid = foobar
; bind to the socket
socket = /tmp/myapp.socket
psgi = myapp.pl
Imperial统计局¶
你可以添加 OptionEmperorStats 选项,将用一个TCP地址作为该选项的值,来为Emperor启用统计/状态服务。通过连接到该地址,你将获得一个JSON格式的统计信息块。
将非uWSGI应用当成vassal运行或者把替代的uWSGI当成vassal使用¶
你可以使用 privileged-binary-patch
/unprivileged-binary-patch
选项,把一个不同的二进制文件当成你的vassal exec()
。第一个选项在socket继承和共享socket初始化后对二进制文件打补丁(这样你就可以使用uWSGI定义的socket了)。第二个选项在特权删除后对二进制文件打补丁。用这种方式,你将能够使用uWSGI的UID/GID/chroot/namespace/jailing选项。使用与Emperor传递给vassal的相同的参数来调用该二进制文件。
; i am a special vassal calling a different binary in a new linux network namespace
[uwsgi]
uid = 1000
gid = 1000
unshare = net
unprivileged-binary-patch = /usr/bin/myfunnyserver
重要
不要守护 你的应用。如果你这样做,那么Emperor将会失去与它们的连接。
会给新的二进制文件传递uWSGI参数。如果你不喜欢这种行为 (或者需要传递自定义参数),那么添加 -arg
到二进制补丁选项:
; i am a special vassal calling a different binary in a new linux network namespace
; with custom options
[uwsgi]
uid = 1000
gid = 1000
unshare = net
unprivileged-binary-patch-arg = ps aux
或者
;nginx example
[uwsgi]
privileged-binary-patch-arg = nginx -g "daemon off;"
参见
你自定义的vassal应用也可以使用 emperor protocol 与emperor通信。
将Emperor和FastRouter集成在一起¶
FastRouter是一个使用 uwsgi协议 的代理/负载均衡器/路由器。来自 Lincoln Loop 的Yann Malet发布了 a draft about massive Emperor + Fastrouter deployment (PDF),使用 uWSGI缓存框架 作为socket映射存储的主机名。
注意¶
启动时,emperor
chdir()
到vassal目录。所有的vassal实例将从这里启动。如果uwsgi二进制文件不在你的系统路径中,那么你可以使用
binary-path
来强制其路径:./uwsgi --emperor /opt/apps --binary-path /opt/uwsgi/uwsgi
发送
SIGUSR1
到emperor将会在其日志中打印vassal状态。停止 (
SIGINT
/SIGTERM
/SIGQUIT
) Emperor将会调用 Ragnarok,并杀死所有vassal。发送
SIGHUP
到Emperor将会重载所有的vassal。发送
SIGURG
到Emperor将会把所有的vassal从黑名单中移除。一般来讲,不应该带
--master
运行emperor,除非有特别需要的master特性,例如高级日志记录。一般来讲,emperor应该在服务器启动时启动,并且保持不动,不重载/重启,除非uWSGI升级;emperor重载有点猛,会一次性重载所有的vassal。相反,vassal应该在需要的时候单独重载,以使用imperial监控器的方式。
待办事项¶
- 文档待办事项:使用非文件系统监控器理清”启动时更改目录”行为做了什么。
- 导出更多魔术变量
- 增加对xml/ini/yaml文件中多部分的支持 (这将允许将单个配置文件用于多个实例中)
利用Broodlord模式实现自动伸缩¶
Broodlord (来自于Starcraft,就像 Zerg模式 模式) 是vassal向Emperor请求“加固”的方式。“加固”是按需生成新的vassal,一般会绑定到同一个socket上。单独的Broodlord模式并不是那么有用。但是,当与 Zerg模式, Idle
和 uWSGI Emperor —— 多应用部署 组合在一起,它可以被用来实现你的应用的自动伸缩。
警告:如果你在寻找的是最红动态调整实例worker数的方式,那么看看 uWSGI cheaper子系统 —— 适应性进程生成 模式,Broodlord模式是用来生成完全新的实例。
一个“简单的”例子¶
我们将启动一个只有一个worker的应用,按需增加资源。Broodlord模式要求你的配置文件中要有一个额外的节,用于zerg。
[uwsgi]
socket = :3031
master = true
vassal-sos-backlog = 10
module = werkzeug.testapp:test_app
processes = 1
zerg-server = /tmp/broodlord.sock
disable-logging = true
[zerg]
zerg = /tmp/broodlord.sock
master = true
module = werkzeug.testapp:test_app
processes = 1
disable-logging = true
idle = 30
die-on-idle = true
vassal-sos-backlog
选项 (仅适用于Linux和TCP socket) 将在监听队列高过给定的值的时候向Emperor请求zerg。默认情况,这个值是10.未来将会添加更多的”vassal-sos-“选项,从而允许更具体的过载检测系统。
[zerg]
节是当一个vassal需要资源的时候,Emperor将运行的配置。 die-on-idle
选项将在zerg超过30秒不活跃的时候销毁它。这个配置显示如何结合不同的uWSGI特性来实现不同缩放手段。要运行这个
Emperor,我们需要指定可以运行多少个zerg实例:
uwsgi --emperor /etc/vassals --emperor-broodlord 40
这将允许你为应用运行多达40个额外的zerg worker。
–vassal-sos¶
注解
这个标识已于2.0.7添加。
–vassal-sos 允许vassal一旦其所有的worker都处于忙碌状态,立即请求加固。
这个选项接收一个整型值,请求一个新的加固之间等待的秒数。
钩子之下 (或:黑入broodlord模式)¶
技术上,broodlord模式只是由vassal发送的一个简单的消息,用来“强制”Emperor生成另一个实例名中带’:zerg’后缀的vassal。
即使后缀是’:zerg’,这也不意味着你需要使用Zerg模式。一个’zerg’实例可以是一个简单订阅到一个路由器,或者绑定到一个SO_REUSEPORT socket的完全独立的实例。
这是一个使用订阅系统的例子。
[uwsgi]
socket = 127.0.0.1:0
subscribe2 = server=127.0.0.1:4040,key=foobar.it
psgi = app.pl
processes = 4
vassal-sos = 3
[zerg]
socket = 127.0.0.1:0
subscribe2 = server=127.0.0.1:4040,key=foobar.it
psgi = app.pl
idle = 60
processes = 1
Zerg模式¶
注解
是哒,那就像“重质不重量”的星际争霸赛中的Zerg。如果你还没有玩过星际,那么做好准备,你会看到一些废话。
注解
请注意,这些废话大多仅限于命名。Zerg模式是一个严肃的事。
当你的站点负载时可变的时候,能够动态添加worker将会是不错的
显然,你可以编辑你的配置,增加 workers
,然后重载你的uWSGI实例,但是对于负载很多的应用而言,这是不可取的,并且老实说 —— 谁会想要做那样的手工活来扩展应用呢?
启用Zerg模式,你可以允许”uwsgi-zerg”实例附加到你已经运行的服务器上,然后在工作中帮助它。
Zerg模式显然只能用于本地。你不能用它来添加远程实例 —— 这个工作已经由 uWSGI FastRouter , HTTP plugin 或者你的web服务器的负载均衡器更好地完成了。
启用zerg服务器¶
如果你想要zerg冲进一个uWSGI实例,那么必须启用Zerg服务器。它将会绑定到一个UNIX socket上,并且会传递uwsgi socket文件描述符到连接到它上面的Zerg worker。
警告
socket必须是一个UNIX socket,因为它必须能够通过文件描述符传递。一个TCP socket就是不能用。
出于安全,UNIX socket并不会继承 chmod-socket
选项,当将总会使用当前的umask。
如果你有文件系统权限问题,那么在Linux上,通过附加一个 @
到socket名前面,你可以使用抽象名字空间中的UNIX socket。
一个正常的UNIX socket:
./uwsgi -M -p 8 --module welcome --zerg-server /var/run/mutalisk
Linux抽象名字空间内的socket:
./uwsgi -M -p 8 --module welcome --zerg-server @nydus
附加zerg到zerg服务器¶
要添加一个新的实例到你的zerg池中,执行使用–zerg选项
./uwsgi --zerg /var/run/mutalisk --master --processes 4 --module welcome
# (or --zerg @nydus, following the example above)
通过这种方式,4个新的worker将会开始为请求提供服务。
当你的负载回到正常值的时候,你可以简单关闭所有的uwsgi-zerg实例,不会有任何问题。
你可以附加无限数量的uwsgi-zerg实例。
在zerg服务器不可用的时候回退¶
默认情况下,如果Zerg服务器不能用的话,一个Zerg客户端将不会运行。因此,如果你的zerg服务器死掉了,并且重载zerg客户端,那么这个客户端将会简单关闭。
如果你想要避免这种行为,那么添加一个 --socket
指令,映射到所需的socket (那个应该由zerg服务器管理的),并且添加 --zerg-fallback
选项。
通过这个设置,如果一个Zerg服务器不可用,那么Zerg客户端将会继续正常绑定到指定的socket。
将Zerg当成测试器使用¶
你可以用的一个不错的技巧是,用 SIGTSTP
信号挂起主要的实例,然后在Zerg中加载你的应用的一个新的版本。如果代码不能用,那么你可以简单关闭掉这个Zerg,然后恢复主要的实例。
Zerg池¶
Zerg池是特殊的Zerg服务器,只对Zerg客户端提供服务,仅此而已。
你可以用它们来构建高可用性的系统,减少测试/重载期间的挂机时间。
你可以运行无限数量的zerg池 (在几个UNIX socket上),并且将无限数量的socket映射到它们上。
[uwsgi]
master = true
zergpool = /tmp/zergpool_1:127.0.0.1:3031,127.0.0.1:3032
zergpool = /tmp/zergpool_2:192.168.173.22:3031,192.168.173.22:3032
使用一个像这样的配置,你会拥有两个zerg池,每个为两个socket服务。
现在,你可以把实例附加到它们上了。
uwsgi --zerg /tmp/zergpool_1 --wsgi-file myapp.wsgi --master --processes 8
uwsgi --zerg /tmp/zergpool_2 --rails /var/www/myapp --master --processes 4
或者你可以附加一个单一的实例到多个Zerg服务器上。
uwsgi --zerg /tmp/zergpool_1 --zerg /tmp/zergpool_2 --wsgi-file myapp.wsgi --master --processes 8
动态添加应用¶
注意:这并不是托管多个应用的最好的方法。你最好为每个应用运行一个uWSGI实例。
你可以在没有配置应用的情况下启动uWSGI服务器。
要加载一个新的应用,可以使用uwsgi包中的这些变量:
UWSGI_SCRIPT
– 传递定义了一个application
可调用对象的WSGI脚本名- 或者
UWSGI_MODULE
和UWSGI_CALLABLE
– 模块名 (可导入路径) 以及可调用对象的名字来从该模块调用
在Cherokee, Nginx, Apache, cgi_dynamic上有对动态应用的官方支持。可以很容易添加它们到Tomcat和Twisted处理器中。
定义带动态应用的VirtualEnv¶
Virtualenvs是基于 Py_SetPythonHome()
函数的。只有在 Py_Initialize()
之前调用这个函数才有用,因此不能将其用于动态应用。
要定义一个带DynamicApps的VirtualEnv,hack是唯一的解决方法。
首先,你必须告诉python不要导入 site
模块。这个模块添加所有的 site-packages
到 sys.path
中。要模拟virtualenvs,我们必须只在子解析器初始化后加载site模块。跳过第一次 import site
,我们现在可以在动态应用加载时简单设置 sys.prefix
和
sys.exec_prefix
,然后调用
PyImport_ImportModule("site");
// Some users would want to not disable initial site module loading, so the site module must be reloaded:
PyImport_ReloadModule(site_module);
现在,我们可以使用 UWSGI_PYHOME
变量动态设置VirtualEnv:
location / {
uwsgi_pass 192.168.173.5:3031;
include uwsgi_params;
uwsgi_param UWSGI_SCRIPT mytrac;
uwsgi_param UWSGI_PYHOME /Users/roberto/uwsgi/VENV2;
}
扩展SSL连接 (uWSGI 1.9)¶
在一个集群中分发SSL服务器是个难点。最大的问题是在不同节点之间共享SSL会话。
由于OpenSSL对会话管理方式的限制,在非阻塞服务器上,问题被放大了。
例如,你不能在Memcached服务器上共享会话,并且以非阻塞方式访问它们。
时至今日,一种常见的解决方法 (嗯,好吧,或许是一种妥协)是使用单个SSL终止器来平衡到多个非加密后端的请求。这个解决方法还挺有效的,但是显然,它不能够扩展。
从uWSGI 1.9-dev起,已添加了一个分布式缓存的实现(基于 stud 项目)。
设置1:使用uWSGI缓存来存储SSL会话¶
你可以配置uWSGI的SSL子系统来使用共享缓存。根据缓存项的过期值,SSL会话会超时。这样,缓存清道夫线程(由master管理)将会销毁缓存中的会话。
重要
操作的次序是重要的。 cache
选项必须在 ssl-sessions-use-cache
和 https
选项之前指定。
[uwsgi]
; spawn the master process (it will run the cache sweeper thread)
master = true
; store up to 20k sessions
cache = 20000
; 4k per object is enough for SSL sessions
cache-blocksize = 4096
; force the SSL subsystem to use the uWSGI cache as session storage
ssl-sessions-use-cache = true
; set SSL session timeout (in seconds)
ssl-sessions-timeout = 300
; set the session context string (see later)
https-session-context = foobar
; spawn an HTTPS router
https = 192.168.173.1:8443,foobar.crt,foobar.key
; spawn 8 processes for the HTTPS router (all sharing the same session cache)
http-processes = 8
; add a bunch of uwsgi nodes to relay traffic to
http-to = 192.168.173.10:3031
http-to = 192.168.173.11:3031
http-to = 192.168.173.12:3031
; add stats
stats = 127.0.0.1:5001
现在,启动你的HTTPS路由器,然后telnet到端口5001。在JSON输出的”cache”对象下,你应该看到值”items”和”hits”正在增长。值”miss”在每次于缓存中找不到一个会话的时候就会增长。它是用户可以期望的SSL性能的一个好度量。
设置2:同步不同HTTPS路由器的缓存¶
目标是同步每个分布缓存中的每个新会话。要完成这个目标,你必须在每个实例中生成一个特殊的线程 (cache-udp-server
),然后列出所有应该被同步的远程服务器。
可以使用一个纯TCP负载均衡器 (例如HAProxy或者uWSGI的Rawrouter)来在各种HTTPS路由器之间均衡负载。
下面是一个可能的Rawrouter配置。
[uwsgi]
master = true
rawrouter = 192.168.173.99:443
rawrouter-to = 192.168.173.1:8443
rawrouter-to = 192.168.173.2:8443
rawrouter-to = 192.168.173.3:8443
现在,你可以配置第一个节点 (新的选项位于.ini配置的尾部)
[uwsgi]
; spawn the master process (it will run the cache sweeper thread)
master = true
; store up to 20k sessions
cache = 20000
; 4k per object is enough for SSL sessions
cache-blocksize = 4096
; force the SSL subsystem to use the uWSGI cache as session storage
ssl-sessions-use-cache = true
; set SSL session timeout (in seconds)
ssl-sessions-timeout = 300
; set the session context string (see later)
https-session-context = foobar
; spawn an HTTPS router
https = 192.168.173.1:8443,foobar.crt,foobar.key
; spawn 8 processes for the HTTPS router (all sharing the same session cache)
http-processes = 8
; add a bunch of uwsgi nodes to relay traffic to
http-to = 192.168.173.10:3031
http-to = 192.168.173.11:3031
http-to = 192.168.173.12:3031
; add stats
stats = 127.0.0.1:5001
; spawn the cache-udp-server
cache-udp-server = 192.168.173.1:7171
; propagate updates to the other nodes
cache-udp-node = 192.168.173.2:7171
cache-udp-node = 192.168.173.3:7171
以及其他两个……
[uwsgi]
; spawn the master process (it will run the cache sweeper thread)
master = true
; store up to 20k sessions
cache = 20000
; 4k per object is enough for SSL sessions
cache-blocksize = 4096
; force the SSL subsystem to use the uWSGI cache as session storage
ssl-sessions-use-cache = true
; set SSL session timeout (in seconds)
ssl-sessions-timeout = 300
; set the session context string (see later)
https-session-context = foobar
; spawn an HTTPS router
https = 192.168.173.1:8443,foobar.crt,foobar.key
; spawn 8 processes for the HTTPS router (all sharing the same session cache)
http-processes = 8
; add a bunch of uwsgi nodes to relay traffic to
http-to = 192.168.173.10:3031
http-to = 192.168.173.11:3031
http-to = 192.168.173.12:3031
; add stats
stats = 127.0.0.1:5001
; spawn the cache-udp-server
cache-udp-server = 192.168.173.2:7171
; propagate updates to the other nodes
cache-udp-node = 192.168.173.1:7171
cache-udp-node = 192.168.173.3:7171
[uwsgi]
; spawn the master process (it will run the cache sweeper thread)
master = true
; store up to 20k sessions
cache = 20000
; 4k per object is enough for SSL sessions
cache-blocksize = 4096
; force the SSL subsystem to use the uWSGI cache as session storage
ssl-sessions-use-cache = true
; set SSL session timeout (in seconds)
ssl-sessions-timeout = 300
; set the session context string (see later)
https-session-context = foobar
; spawn an HTTPS router
https = 192.168.173.1:8443,foobar.crt,foobar.key
; spawn 8 processes for the HTTPS router (all sharing the same session cache)
http-processes = 8
; add a bunch of uwsgi nodes to relay traffic to
http-to = 192.168.173.10:3031
http-to = 192.168.173.11:3031
http-to = 192.168.173.12:3031
; add stats
stats = 127.0.0.1:5001
; spawn the cache-udp-server
cache-udp-server = 192.168.173.3:7171
; propagate updates to the other nodes
cache-udp-node = 192.168.173.1:7171
cache-udp-node = 192.168.173.2:7171
开始访问Rawrouter (记得使用一个支持持久化SSL会话的客户端,例如你的浏览器),并从每个HTTPS终止器节点的统计数据服务器获得缓存统计信息。如果”hits”的次数比”miss”值高得多,那么系统工作良好,并且你的负载被分布,并处于超级高的性能模式。
所以,你问,什么是 https-session-context
呢?基本上,每个SSL会话在使用之前都会经过一个固定的字符串(会话上下文)检查。如果该会话不匹配那个字符串,那么它会被拒绝。默认情况下,会话上下文会被初始化为一个从HTTP服务器地址构建的值。将其强制为一个共享值将会避免一个节点中创建的一个会话被另一个节点拒绝。
使用命名缓存¶
从uWSGI 1.9起,你可以有多个缓存。这是一个有2个节点,使用名为”ssl”的新一代缓存的设置。
cache2
选项也允许设置一个自定义的键大小。由于SSL会话键并非非常长,因此我们可以用它来优化内存使用。在这个例子中,我们使用128字节键大小限制,这对于会话ID而言应该够了。
[uwsgi]
; spawn the master process (it will run the cache sweeper thread)
master = true
; store up to 20k sessions
cache2 = name=ssl,items=20000,keysize=128,blocksize=4096,node=127.0.0.1:4242,udp=127.0.0.1:4141
; force the SSL subsystem to use the uWSGI cache as session storage
ssl-sessions-use-cache = ssl
; set sessions timeout (in seconds)
ssl-sessions-timeout = 300
; set the session context string
https-session-context = foobar
; spawn an HTTPS router
https = :8443,foobar.crt,foobar.key
; spawn 8 processes for the HTTPS router (all sharing the same session cache)
http-processes = 8
module = werkzeug.testapp:test_app
; add stats
stats = :5001
以及第二个节点……
[uwsgi]
; spawn the master process (it will run the cache sweeper thread)
master = true
; store up to 20k sessions
cache2 = name=ssl,items=20000,blocksize=4096,node=127.0.0.1:4141,udp=127.0.0.1:4242
; force the SSL subsystem to use the uWSGI cache as session storage
ssl-sessions-use-cache = ssl
; set session timeout
ssl-sessions-timeout = 300
; set the session context string
https-session-context = foobar
; spawn an HTTPS router
https = :8444,foobar.crt,foobar.key
; spawn 8 processes for the HTTPS router (all sharing the same sessions cache)
http-processes = 8
module = werkzeug.testapp:test_app
; add stats
stats = :5002
注意事项¶
如果你不想要手工配置缓存UDP节点,并且你的网络配置支持,那么你可以使用UDP多播。
[uwsgi]
...
cache-udp-server = 225.1.1.1:7171
cache-udp-node = 225.1.1.1:7171
- 一个新的网关服务器正在开发中,它的名字是”udprepeater”。基本上,它将转发所有它接收的UDP包到订阅的后端节点。它将允许你维护订阅系统的零配置风格 (基本上,你只需要配置单个指向repeater的缓存UDP节点)。
- 目前,缓存节点之间并没有安全性。对于一些用户来说,这或许是一个巨大的问题,所以一个安全模式 (加密包) 正在开发中。
安全的uWSGI¶
设置POSIX capabilities¶
POSIX capabilities 允许为进程细化权限。除了标准的UNIX权限方案之外,它们为系统资源定义了新的一组权限。要启用capabilities支持 (仅Linux),构建uWSGI之前,你必须安装 libcap
头文件 (在基于Debian的发行版上,则是 libcap-dev
)。像往常一样,在 setuid
调用之后,你的进程将会失去几乎所有的capabilities。uWSGI的 cap
选项允许你定义一个通过调用保持的capabilities列表。
例如,要允许你的非特权应用绑定到特权端口,并且设置系统时钟,你将使用以下选项。
uwsgi --socket :1000 --uid 5000 --gid 5000 --cap net_bind_service,sys_time
uWSGI生成的所有进程稍后将继承此行为。如果你的系统支持在uWSGI列表中不可用的capabilities,那么你可以简单指定常数数:
uwsgi --socket :1000 --uid 5000 --gid 5000 --cap net_bind_service,sys_time,42
除了 net_bind_service
和 sys_time
之外,添加了一个数字为“42”的新capability。
可用capabilities¶
这是可用capabilities列表。
audit_control | CAP_AUDIT_CONTROL |
audit_write | CAP_AUDIT_WRITE |
chown | CAP_CHOWN |
dac_override | CAP_DAC_OVERRIDE |
dac_read_search | CAP_DAC_READ_SEARCH |
fowner | CAP_FOWNER |
fsetid | CAP_FSETID |
ipc_lock | CAP_IPC_LOCK |
ipc_owner | CAP_IPC_OWNER |
kill | CAP_KILL |
lease | CAP_LEASE |
linux_immutable | CAP_LINUX_IMMUTABLE |
mac_admin | CAP_MAC_ADMIN |
mac_override | CAP_MAC_OVERRIDE |
mknod | CAP_MKNOD |
net_admin | CAP_NET_ADMIN |
net_bind_service | CAP_NET_BIND_SERVICE |
net_broadcast | CAP_NET_BROADCAST |
net_raw | CAP_NET_RAW |
setfcap | CAP_SETFCAP |
setgid | CAP_SETGID |
setpcap | CAP_SETPCAP |
setuid | CAP_SETUID |
sys_admin | CAP_SYS_ADMIN |
sys_boot | CAP_SYS_BOOT |
sys_chroot | CAP_SYS_CHROOT |
sys_module | CAP_SYS_MODULE |
sys_nice | CAP_SYS_NICE |
sys_pacct | CAP_SYS_PACCT |
sys_ptrace | CAP_SYS_PTRACE |
sys_rawio | CAP_SYS_RAWIO |
sys_resource | CAP_SYS_RESOURCE |
sys_time | CAP_SYS_TIME |
sys_tty_config | CAP_SYS_TTY_CONFIG |
syslog | CAP_SYSLOG |
wake_alarm | CAP_WAKE_ALARM |
在Linux CGroup中运行uWSGI¶
Linux cgroup是在最近的Linux内核中提供的一个惊人的特性。它们允许你在约束环境中,使用有限的CPU, 内存,调度优先级, IO等”jail”你的进程……
注解
要使用cgroup,uWSGI必须作为root运行。 uid
和 gid
是非常非常必要的。
启用cgroup¶
首先,你需要在你的系统中启用cgroup支持。创建/cgroup目录,然后添加这个到你的/etc/fstab中:
none /cgroup cgroup cpu,cpuacct,memory
然后,挂载/cgroup,这样,你就会拥有带有已控制CPU和内存使用的jail了。有一些其他的Cgroup子系统,但是CPU和内存使用时最有用的约束。
让我们在一个cgroup中运行uWSGI:
./uwsgi -M -p 8 --cgroup /cgroup/jail001 -w simple_app -m --http :9090
Cgroup是简单的目录。使用这个命令,你的uWSGI服务器和它的worker就会“限制”在’cgroup/jail001’这个cgroup中。如果你对服务器发起一堆请求,那么,你将会看到cgroup目录中的使用计数器 —— cpuacct.*和memoryfiles.*增长。通过制定一个已经存在的目录,你还可以使用预先存在的cgroup。
一个真实的例子:为你的客户计划QoS¶
假设你为4个客户托管应用。其中两个每个月支付你$100,一个支付$200,而最后一个支付$400。为了拥有一个好的服务质量实现,$100的应用应该获得1/8,或者12.5%的CPU功率,$200的应用应该获得1/4 (25%),而最后一个应该获得50%。要实现这点,我们必须创建4个cgroup,一个应用一个,然后限制其调度权重。
./uwsgi --uid 1001 --gid 1001 -s /tmp/app1 -w app1 --cgroup /cgroup/app1 --cgroup-opt cpu.shares=125
./uwsgi --uid 1002 --gid 1002 -s /tmp/app2 -w app1 --cgroup /cgroup/app2 --cgroup-opt cpu.shares=125
./uwsgi --uid 1003 --gid 1003 -s /tmp/app3 -w app1 --cgroup /cgroup/app3 --cgroup-opt cpu.shares=250
./uwsgi --uid 1004 --gid 1004 -s /tmp/app4 -w app1 --cgroup /cgroup/app4 --cgroup-opt cpu.shares=500
``cpu.shares``值是简单地根据彼此进行计算的,因此,你可以使用任何你喜欢的方案,例如 (125, 125, 250, 500)或者甚至是(1, 1, 2, 4)。处理了CPU,我们接着限制内存。让我们使用和之前一样的方案,它们中最大是2GB。
./uwsgi --uid 1001 --gid 1001 -s /tmp/app1 -w app1 --cgroup /cgroup/app1 --cgroup-opt cpu.shares=125 --cgroup-opt memory.limit_in_bytes=268435456
./uwsgi --uid 1002 --gid 1002 -s /tmp/app2 -w app1 --cgroup /cgroup/app2 --cgroup-opt cpu.shares=125 --cgroup-opt memory.limit_in_bytes=268435456
./uwsgi --uid 1003 --gid 1003 -s /tmp/app3 -w app1 --cgroup /cgroup/app3 --cgroup-opt cpu.shares=250 --cgroup-opt memory.limit_in_bytes=536870912
./uwsgi --uid 1004 --gid 1004 -s /tmp/app4 -w app1 --cgroup /cgroup/app4 --cgroup-opt cpu.shares=500 --cgroup-opt memory.limit_in_bytes=1067459584
在uWSGI中使用Linux KSM¶
Kernel Samepage Merging 是Linux 内核 >= 2.6.32 的一个特性,允许进程共享具有相同内容的内存页。这是由一个内核守护进程周期性执行扫描,对比,以及在可能的情况下,对特定内存区域进行合并来完成的。KVM生而为增强,它可以被用于使用公共数据的进程 (例如,带有语言解释器和标准库的uWSGI进程)。
如果你幸运的话,使用KSM可能会成倍减少你的uWSGI实例的内存使用。特别是在大量的 Emperor 部署中:为每个vassal启用KSM可能会节省大量的内存。uWSGI中的KSM是 Asidev s.r.l. 的Giacomo Bagnoli的想法。非常感谢他。
启用KSM守护进程¶
要启用KSM守护进程 (ksmd
),只需设置 /sys/kernel/mm/ksm/run
为1,如下所示:
echo 1 > /sys/kernel/mm/ksm/run
注解
记得在机器启动的时候做这个,因为KSM守护进程并不是默认运行的。
注解
KSM是一个可选功能,必须由进程明确要求,因此只是启用KSM将不会拯救你的机器上的一切东西。
在uWSGI中启用KSM支持¶
如果你已经在一个内核中编译了带KSM支持的uWSGI了,那么你将能够使用 ksm
选项。这个选项将会指示uWSGI在每个请求或者master周期后注册经常内存映射 (通过 madvise
系统调用)。如果自上一次扫描后,并没有页面映射发生改变,那么就不会使用昂贵的系统调用。
性能影响¶
检查进程映射要求在每次请求之后解析 /proc/self/maps
文件。在一些设置中,这将降低性能。你可以通过传递一个参数给 ksm
选项来调整uWSGI页面扫描的频率。
# Scan for process mappings every 10 requests (or 10 master cycles)
./uwsgi -s :3031 -M -p 8 -w myapp --ksm=10
检查KSM是否工作良好¶
/sys/kernel/mm/ksm/pages_shared
和 /sys/kernel/mm/ksm/pages_sharing
文件包含了关于KSM效率的统计信息。值越高,你的uWSGI实例消耗的内存越少。
使用collectd的KSM统计¶
一个像这样的简单的Bash脚本可以用来密切关注KSM的效率:
#!/bin/bash
export LC_ALL=C
if [ -e /sys/kernel/mm/ksm/pages_sharing ]; then
pages_sharing=`cat /sys/kernel/mm/ksm/pages_sharing`;
page_size=`getconf PAGESIZE`;
saved=$(echo "scale=0;$pages_sharing * $page_size"|bc);
echo "PUTVAL <%= cn %>/ksm/gauge-saved interval=60 N:$saved"
fi
在你的 collectd 配置中,添加诸如以下的东西:
LoadPlugin exec
<Plugin exec>
Exec "nobody" "/usr/local/bin/ksm_stats.sh"
</Plugin>
使用Linux名字空间监禁(jailing)你的应用¶
如果你有最新的Linux内核 (>2.6.26),并且不是使用Itanium架构,那么你可以使用名字空间支持。
名字空间是什么?¶
它们是一种从内核特定层“分离”你的进程,并且将它们分配给新的内核层的优雅的(比大多数你可能会在其他操作系统发现的jail系统更优雅)方式。
Posix系统中可用的’chroot’系统是名字空间的原始形式:一个进程看到的是一个全新的文件系统root,并且无法访问原始的。
Linux将这个概念扩展到了其他OS层 (PID, user, IPC, 网络等等。),因此,特定的进程可以存活在一个“虚拟操作系统”中,它均有一组新的pid,一组新的用户,一个完全不共享的IPC系统 (信号量,贡献内存等等。),专用网络接口及其自身的主机名。
uWSGI在1.9/2.0开发周期中得到了完全的名字空间支持。
支持的名字空间¶
fs
-> CLONE_NEWNS,文件系统ipc
-> CLONE_NEWIPC, sysv ipcpid
-> CLONE_NEWPID,当与unshare()一起使用的时候,需要额外的fork()
。使用–refork-*中任意一个选项。uts
-> CLONE_NEWUTS,主机名net
-> CLONE_NEWNET,来自不同名字空间的新的网络,UNIX socket仍然可用,它们是名字空间之间通信的好方式user
-> CLONE_NEWUSER,仍然管理复杂 (并在不同的内核版本之间表现不同),慎用
setns()¶
除了为新的进程创建新的名字空间,你还可以使用 setns()
调用附加到已经运行的(名字空间)。
每个进程通过 /proc/self/ns
目录公开它的名字空间。setns()系统调用使用从那个目录中的文件获得的文件描述符来附加到名字空间上。
正如我们已经看到的那样,UNIX socket是名字空间之间进行通信的一种好方式,uWSGI的 setns()
特性的工作方式是创建一个UNIX socket,用来接收那些想要加入它的名字空间的进程的请求。由于UNIX socket允许传递文件描述符,因此“客户端”只需要在它们之上调用setns()。
setns-socket <addr>
公开了指定unix socket地址上的/proc/self/nssetns <addr>
连接到指定的unix socket地址,获取文件描述符,并在它们上面使用setns()setns-preopen
如果启用了,那么会在启动的时候(移除特权之前)打开并缓存/proc/self/ns文件。这对于避免作为root运行主要实例是有用的。setns-socket-skip <name>
/proc/self/ns中的一些文件会制造问题(大多数是’user’那个)。你可以指定名字来跳过它们. (你可以多次指定这个选项)
pivot_root¶
这个选项允许你的修改你当前运行实例的rootfs。
它比chroot好,因为它允许你在(手工)取消挂载老的文件系统树之前访问它。
要正确掌控它会有点复杂,因为它需要几个假设:
pivot_root <new> <old>
<new>是要作为新的rootfs挂载的目录,而<old>是访问旧的文件系统树的路径。
<new>必须是一个已挂载的文件系统,而<old>必须在这个文件系统之下。
一个常见的模式是:
[uwsgi]
unshare = fs
hook-post-jail = mount:none /distros/precise /ns bind
pivot_root = /ns /ns/.old_root
...
(记得创建 /ns
和 /distro/precise/.old_root
。)
当你创建了新的文件系统布局,你就可以递归取消挂载/.old_root:
[uwsgi]
unshare = fs
hook-post-jail = mount:none /distros/precise /ns bind
pivot_root = /ns /ns/.old_root
; bind mount some useful fs like /dev and /proc
hook-as-root = mount:proc none /proc nodev hidepid=2
hook-as-root = mount:none /.old_root/dev /dev bind
hook-as-root = mount:none /.old_root/dev/pts /dev/pts bind
; umount the old tree
hook-as-root = umount:/.old_root rec,detach
为什么不使用lxc?¶
LXC (LinuX Containers)是一个允许你使用Linux名字空间构建完整子系统的项目。你可能会问,既然LXC实现了一个完整的“虚拟”系统,那么为什么还要“另起炉灶”。风马牛不相及……
LXC的目标是提供用户一个虚拟服务器视图。uWSGI的名字空间支持更低层次 —— 你可以用它来分离单个部件 (例如,你也许只想要取消共享IPC),从而提高安全性和隔离性。
并非所有的场景都要求一个完整的类系统视图 (在大量的场景下,是次优的,而在其他场景下则是最佳方案),试着把名字空间当成一种提高安全性和隔离性的方法,当你需要/可以分离一个组件时,使用克隆/取消共享来完成。当你想要让用户访问一个完整的类系统时,使用LXC。
老方法:–namespace选项¶
在1.9/2.0之前,添加了一个全功能的类系统名字空间支持。它的工作方式就像增强型chroot()。
很快,它就应该被移动成为一个外部的插件,但是将总会是主要发行的一部分,因为由于它的简单,很多人用它。
基本上,你需要设置一个根文件系统和一个主机名,在一个新的名字空间中启动你的实例:
让我们以为我们的jail创建一个新的根文件系统开始。你会需要 debootstrap
(或者用于你的发行版的等价包)。我们将rootfs放在 /ns/001
,然后创建一个’uwsgi’用户,这个用户会运行uWSGI服务器。我们会使用chroot命令来在新的rootfs中’adduser’,并且我们会安装Flask包,这是uwsgicc要求的。
(所有这些都必须以root执行)
mkdir -p /ns/001
debootstrap maverick /ns/001
chroot /ns/001
# in the chroot jail now
adduser uwsgi
apt-get install mercurial python-flask
su - uwsgi
# as uwsgi now
git clone https://github.com/unbit/uwsgicc.git .
exit # out of su - uwsgi
exit # out of the jail
现在,在你真实系统上,运行
uwsgi --socket 127.0.0.1:3031 --chdir /home/uwsgi/uwsgi --uid uwsgi --gid uwsgi --module uwsgicc --master --processes 4 --namespace /ns/001:mybeautifulhostname
如果一切顺利,uWSGI将会设置 /ns/001
作为新的root文件系统,分配 mybeautifulhostname
作为主机名,并且隐藏主机系统的PID和IPC。
你应该注意的第一件事是,在新的名字空间中,uWSGI master变成了PID 1 (“init”进程)。如果出现错误,那么所有由该uWSGI栈生成的进程将会重新更改父亲为这个master。如果master死掉了,那么所有被jail的进程都会死掉。
现在,把你的web浏览器指向你的web服务器,你应该会看到uWSGI Control Center界面。
注意下信息区域。节点名 (为集群子系统所用) 匹配真实的主机名,因为在同一个集群组里拥有多个jail并无意义。相反,在主机名字段,你会看到你设置的主机名。
另一个重点是,你可以看到来自你真实系统的所有jail进程(它们将会拥有一个不同的PID集合),英寸,如果你想要控制jail,那么可以容易做到。
重载uWSGI¶
当运行在一个jail的时候,uWSGI使用另一个系统来重载:它会简单告诉worker开溜,然后退出。存活在名字空间外的父进程将会看到,并在一个新的jail中重新生成栈。
这种jail有多安全?¶
很难说!所有的软件在发现漏洞之前都倾向于安全的。
其他文件系统¶
当把应用困在名字空间内时,它只能访问它的虚拟jail根文件系统。如果在jail目录中,有其他挂载着的文件系统,那么它们将不能被访问,除非你使用 namespace-keep-mount
。
# app1 jail is located here
namespace = /apps/app1
# nfs share mounted on the host side
namespace-keep-mount = /apps/app1/nfs
这将会绑定/apps/app1/nfs到jail,因此,被困住的应用可以在/nfs目录下访问它
# app1 jail is located here
namespace = /apps/app1
# nfs share mounted on the host side
namespace-keep-mount = /mnt/nfs1:/nfs
如果我们想要绑定的文件系统是被挂载在我们的jail里不包含的路径上,那么我们可以对–namespace-keep-mount使用”<source>:<dest>”语法。在这种情况下,/mnt/nfs1将会被绑定到jail内的/nfs目录上。
FreeBSD Jails¶
uWSGI 1.9.16引入了原生的FreeBSD jails支持。
FreeBSD jails可以被看成新一代的chroot(),能够细粒度调整“jail”可以访问的东西。
即使在更高一点的级别上,它们都非常类似于Linux的名字空间 (从API的角度来看)。
自FreeBSD 4起,jails就可用了
为什么用uWSGI管理jails?¶
一般来说,jails是用系统工具“jail”及其实用程序管理的。
时至今日,在FreeBSD jails中运行uWSGI是非常常见的,但是对于真正大规模的设置 (即,托管业务),其中,Emperor (比方说) 管理数百个无关的uWSGI实例,设置真真会磨死人。
在uWSGI配置文件中直接管理jails极大降低了系统管理员成本,并且帮助更好的组织整个基础设施。
老式的jails (FreeBSD < 8)¶
FreeBSD公开了2个主要的API,用于管理jails。老的(更早的)那个是基于jail()函数的。
它自FreeBSD 4起可用,并且允许你设置rootfs、主机名和一个或多个ipv4/ipv6地址。
在一个jail中运行一个uWSGI实例需要2个选项:–jail和–jail-ip4/–jail-ip6 (实际上是3个,如果你使用IPv6的话)
--jail <rootfs> [hostname] [jailname]
--jail-ip4 <address>
(can be specified multiple times)
--jail-ip6 <address>
(can be specified multiple times)
显示如何为你的jail创建rootfs并非本文档的目的,但是就我个人而言,我讨厌从源代码重新构建,因此一般而言,我只是从官方库搞到base.tgz文件,并且chroot()到它那里,以进行微调。
一个你必须记得的事情是,附加到jail的ip地址必须在系统中可用(作为别名)。与往常一样,我们往往滥用uWSGI设施。在我们的例子中,–exec-pre-jail hook将会获得成功
[uwsgi]
; create the jail with /jails/001 as rootfs and 'foobar' as hostname
jail = /jails/001 foobar
; create the alias on 'em0'
exec-pre-jail = ifconfig em0 192.168.0.40 alias
; attach the alias to the jail
jail-ip4 = 192.168.0.40
; bind the http-socket (we are now in the jail)
http-socket = 192.168.0.40:8080
; load the application (remember we are in the jail)
wsgi-file = myapp.wsgi
; drop privileges
uid = kratos
gid = kratos
; common options
master = true
processes = 2
新式的jails (FreeBSD >= 8)¶
FreeBSD 8引入了一个用于管理jails的新的高级的api。基于jail_set()系统调用,libjail公开了几十个功能,并且允许jails的微调。要使用这个新的api,你需要–jail2选项 (别名为–libjail)
--jail2 <key>[=value]
每个–jail2选项都和一个jail属性1:1映射,因此你基本可以调整一切!
[uwsgi]
; create the jail with /jails/001 as rootfs
jail2 = path=/jails/001
; set hostname to 'foobar'
jail2 = host.hostname=foobar
; create the alias on 'em0'
exec-pre-jail = ifconfig em0 192.168.0.40 alias
; attach the alias to the jail
jail2 = ip4.addr=192.168.0.40
; bind the http-socket (we are now in the jail)
http-socket = 192.168.0.40:8080
; load the application (remember we are in the jail)
wsgi-file = myapp.wsgi
; drop privileges
uid = kratos
gid = kratos
; common options
master = true
processes = 2
关于FreeBSD >= 8.4 但是 < 9.0 的注意事项¶
uWSGI在FreeBSD < 9上使用ipc信号量 (较新的FreeBSD发行版支持POSIX信号量)。
从FreeBSD 8.4起,你需要明确在jails中允许sysvipc。因此,确保这样
[uwsgi]
...
jail2 = allow.sysvipc=1
...
DevFS¶
DevFS虚拟文件系统管理FreeBSD上的/dev目录。
/dev文件系统并没有挂载在jail中,但是出于数以百计的原因,你可能需要它。
有两个可用的主要方法:在创建jail之前,将其挂载在roots的/dev/目录中,或者允许jail挂载它
[uwsgi]
; avoid re-mounting the file system every time
if-not-exists = /jails/001/dev/zero
exec-pre-jail = mount -t devfs devfs /jails/001/dev
endif =
; create the jail with /jails/001 as rootfs
jail2 = path=/jails/001
; set hostname to 'foobar'
jail2 = host.hostname=foobar
; create the alias on 'em0'
exec-pre-jail = ifconfig em0 192.168.0.40 alias
; attach the alias to the jail
jail2 = ip4.addr=192.168.0.40
; bind the http-socket (we are now in the jail)
http-socket = 192.168.0.40:8080
; load the application (remember we are in the jail)
wsgi-file = myapp.wsgi
; drop privileges
uid = kratos
gid = kratos
; common options
master = true
processes = 2
或者 (允许jail自身挂载它)
[uwsgi]
; create the jail with /jails/001 as rootfs
jail2 = path=/jails/001
; set hostname to 'foobar'
jail2 = host.hostname=foobar
; create the alias on 'em0'
exec-pre-jail = ifconfig em0 192.168.0.40 alias
; attach the alias to the jail
jail2 = ip4.addr=192.168.0.40
; allows mount of devfs in the jail
jail2 = enforce_statfs=1
jail2 = allow.mount
jail2 = allow.mount.devfs
; ... and mount it
if-not-exists = /dev/zero
exec-post-jail = mount -t devfs devfs /dev
endif =
; bind the http-socket (we are now in the jail)
http-socket = 192.168.0.40:8080
; load the application (remember we are in the jail)
wsgi-file = myapp.wsgi
; drop privileges
uid = kratos
gid = kratos
; common options
master = true
processes = 2
重载¶
重载(或者二进制补丁)的管理有点烦人,因为uWSGI需要重新执行自身,所以在你的jail中,需要一个二进制文件、插件和配置文件的拷贝 (除非你可以牺牲掉优雅重载和简单指定Emperor来重新生成实例)
另一个方法是 (就和devfs一样) 在jail自身中,使用uwsgi二进制文件(和最终插件)挂载该目录,并且使用–binary-path来指示uWSGI使用这个新的路径
jid文件¶
每个jail可以由一个唯一的名字 (可选的) 或者它的”jid”应用。这类似于一个”pid”,因为你可以用它来发送命令(和更新)到一个已经运行的jail。–jidfile <file>选项允许你存储jid到一个文件中,用于和外部应用使用。
附加到一个jail¶
你可以使用–jail-attach <id>将uWSGI实例附加到已经运行的jails(也可以是标准的持久化jail)上
id参数可以是jid或者jail的名字。
这个特性需要FreeBSD 8
Debian/kFreeBSD¶
这是一个官方Debian项目,旨在构建一个带有FreeBSD内核和常见Debian用户空间的操作系统。
它工作良好,也支持jail。
让我们用debootstrap创建一个jail
debootstrap wheezy /jails/wheezy
添加一个网络别名
ifconfig em0 192.168.173.105 netmask 255.255.255.0 alias
(将em0改为你的网络接口名)
然后运行它
uwsgi --http-socket 192.168.173.105:8080 --jail /jails/wheezy -jail-ip4 192.168.173.105
使用Forkpty Router的Jails¶
你可以使用 Forkpty路由器 轻松附加到FreeBSD jails
只是记得将/dev (嗯,/dev/ptmx) 挂载到你的jail,以允许forkpty()调用
学习如何处理devfs_ruleset以增加你的devfs的安全性
注意事项¶
当jail中运行的最后一个进程死掉的时候,会销毁这个jail
默认情况下,所有挂载在rootfs(在进入jail之前)的东东将对jail自身可见 (前面在处理devfs的时候,我们已经看到了)
Forkpty路由器¶
处理容器现在是一个常见的部署模式。处理jails/名字空间时的最烦人的任务之一是‘附加’到已经运行的实例。
forkpty路由器旨在简化这个过程,提供一个伪终端服务器给你的uWSGI实例。
一个客户端连接到由forkpty路由器公开的socket,然后获得一个连接到进程的新的伪终端 (一般是一个shell,但是可以是任何你想要的)
uwsgi模式 VS 原始模式¶
连接到forkpty路由器的客户端可以用两个协议来进行数据交换:uwsgi和原始模式。
原始模式简单映射socket到pty,出于这样一个原因,你将不能够调整你的终端大小,或者发送特定的信号。这个模式的优点在于性能:每个字符没有开销。
uwsgi模式把每个指令 (stdin, signals, window changes) 封装在一个uwsgi包中。这是非常类似于ssh的工作方式的,所以,如果你计划对shell会话使用forkpty路由器,那么uwsgi模式是最佳选择 (在用户体验方面)。
uwsgi协议的开销 (最差情况) 是每个stdin事件的5个字节 (单个字符)
运行forkpty路由器¶
这个插件不是默认内建的,因此你必须编译它:
uwsgi --build-plugin plugins/forkptyrouter
或者,使用老的插件构建系统:
python uwsgiconfig.py --plugin plugins/forkptyrouter
一般来说,也需要编译pty插件 (为了客户端访问)
uwsgi --build-plugin plugins/pty
或者再次,使用老的构建系统:
python uwsgiconfig.py --plugin plugins/pty
又或者,你可以一次性构建:
UWSGI_EMBED_PLUGINS=pty,forkptyrouter make
现在,你可以将forkptyrouter作为一个标准网关运行 (我们使用UNIX socket,因为我们想要一个与jails的通信信道,并且不共享uts名字空间,以提供一个新的主机名)
[uwsgi]
master = true
unshare = uts
exec-as-root = hostname iaminajail
uid = kratos
gid = kratos
forkpty-router = /tmp/fpty.socket
并且用pty客户端连接:
uwsgi --pty-connect /tmp/fpty.socket
现在,在uWSGI实例中,你有了一个shell (默认是/bin/sh)。运行 hostname
将会给你’iaminajail’
最终,你可以避免使用uWSGI附加到pty上,取而代之,你可以依赖于这个简单的python脚本:
import socket
import sys
import os
import select
import copy
from termios import *
import atexit
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
s.connect(sys.argv[1])
tcattr = tcgetattr(0)
orig_tcattr = copy.copy(tcattr)
atexit.register(tcsetattr, 0, TCSANOW, orig_tcattr)
tcattr[0] |= IGNPAR
tcattr[0] &= ~(ISTRIP | IMAXBEL | BRKINT | INLCR | IGNCR | ICRNL | IXON | IXANY | IXOFF);
tcattr[0] &= ~IUCLC;
tcattr[3] &= ~(ICANON | ECHO | ECHOE | ECHOK | ECHONL);
tcattr[3] &= ~IEXTEN;
tcattr[1] &= ~OPOST;
tcattr[6][VMIN] = 1;
tcattr[6][VTIME] = 0;
tcsetattr(0, TCSANOW, tcattr);
while True:
(rl, wl, xl) = select.select([0, s], [], [])
if s in rl:
buf = s.recv(4096)
if not buf: break
os.write(1, buf)
if 0 in rl:
buf = os.read(0, 4096)
if not buf: break
s.send(buf)
前一个例子使用原始模式,如果调整客户端客户端大小,你将看不到任何更新。
要使用’uwsgi’模式,则添加一个’u’:
[uwsgi]
master = true
unshare = uts
exec-as-root = hostname iaminajail
uid = kratos
gid = kratos
forkpty-urouter = /tmp/fpty.socket
uwsgi --pty-uconnect /tmp/fpty.socket
单个实例可以在不同的socket上公开两个协议
[uwsgi]
master = true
unshare = uts
exec-as-root = hostname iaminajail
uid = kratos
gid = kratos
forkpty-router = /tmp/raw.socket
forkpty-urouter = /tmp/uwsgi.socket
修改默认命令¶
默认情况下,forkpty路由器在新的连接上运行/bin/sh。
你可以使用–forkptyrouter-command修改命令
[uwsgi]
master = true
unshare = uts
exec-as-root = hostname iaminajail
uid = kratos
gid = kratos
forkpty-router = /tmp/raw.socket
forkpty-urouter = /tmp/uwsgi.socket
forkptyrouter-command= /bin/zsh
TunTap路由器¶
TunTap路由器是为运行在一个专用网络名字空间中的linux进程提供网络连接的ad-hoc方案。好吧,显然它有其他用途,但是非常有可能这是最有趣的,也是为什么会开发它的原因。目前,它只构建在Linux平台上。
TunTap路由器默认不编译。
要一次性拥有它:
UWSGI_EMBED_PLUGINS=tuntap make
(是哒,这个插件只命名为’tuntap’,因为它有效公开了各种tuntap设备特性)
使用它的最佳方式是将其绑定到一个unix socket上,允许新的名字空间中的进程访问它 (一般来说,unix socket是用于linux名字空间的最佳通信通道)。
第一个配置¶
我们想要让我们的vassal使用192.168.0.0/24网络,并且将192.168.0.1作为默认网关。
默认网关 (也就是说,tuntap路由器) 是由Emperor自身管理的
[uwsgi]
; create the tun device 'emperor0' and bind it to a unix socket
tuntap-router = emperor0 /tmp/tuntap.socket
; give it an ip address
exec-as-root = ifconfig emperor0 192.168.0.1 netmask 255.255.255.0 up
; setup nat
exec-as-root = iptables -t nat -F
exec-as-root = iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
; enable linux ip forwarding
exec-as-root = echo 1 >/proc/sys/net/ipv4/ip_forward
; force vassals to be created in a new network namespace
emperor-use-clone = net
emperor = /etc/vassals
由这个Emperor生成的vassal将在无网络连接的情况下产生。
要让它们访问公共网络,我们创建一个新的tun设备 (它将只存在于vassal网络名字空间中),指示它路由流量到Emperor的tuntap unix socket:
[uwsgi]
; we need it as the vassal have no way to know it is jailed
; without it post_jail plugin hook would be never executed
jailed = true
; create uwsgi0 tun interface and force it to connect to the Emperor exposed unix socket
tuntap-device = uwsgi0 /tmp/tuntap.socket
; bring up loopback
exec-as-root = ifconfig lo up
; bring up interface uwsgi0
exec-as-root = ifconfig uwsgi0 192.168.0.2 netmask 255.255.255.0 up
; and set the default gateway
exec-as-root = route add default gw 192.168.0.1
; classic options
uid = customer001
gid = customer001
socket = /var/www/foobar.socket
psgi-file = foobar.pl
...
内嵌防火墙¶
TunTap路由器由一个非常简单的防火墙,用来管理vassal的流量
防火墙基于2个链 (入链和出链),而每条规则是由3个参数组成的:<action> <src> <dst>
会将这个防火墙应用于从客户端到tuntap设备(out)以及反过来(in)的流量
第一条匹配的规则终止这个链,如果没有规则应用上,那么策略就是”allow”
以下规则允许从vassal到互联网的访问,但是阻塞vassal的内部通信
[uwsgi]
tuntap-router = emperor0 /tmp/tuntap.socket
tuntap-router-firewall-out = allow 192.168.0.0/24 192.168.0.1
tuntap-router-firewall-out = deny 192.168.0.0/24 192.168.0.0/24
tuntap-router-firewall-out = allow 192.168.0.0/24 0.0.0.0
tuntap-router-firewall-out = deny
tuntap-router-firewall-in = allow 192.168.0.1 192.168.0.0/24
tuntap-router-firewall-in = deny 192.168.0.0/24 192.168.0.0/24
tuntap-router-firewall-in = allow 0.0.0.0 192.168.0.0/24
tuntap-router-firewall-in = deny
exec-as-root = ifconfig emperor0 192.168.0.1 netmask 255.255.255.0 up
; setup nat
exec-as-root = iptables -t nat -F
exec-as-root = iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
; enable linux ip forwarding
exec-as-root = echo 1 >/proc/sys/net/ipv4/ip_forward
; force vassals to be created in a new network namespace
emperor-use-clone = net
emperor = /etc/vassals
安全性¶
TunTap路由器的“切换”部分 (即,映射ip地址到vassal)是灰常简单的:TunTap路由器接收到的第一个来自于vassal的包为vassal注册那个ip地址。一个好方法 (从安全的角度来说) 是在vassal中的网络设置之后立即发送一个ping包:
[uwsgi]
; create uwsgi0 tun interface and force it to connect to the Emperor exposed unix socket
tuntap-device = uwsgi0 /tmp/tuntap.socket
; bring up loopback
exec-as-root = ifconfig lo up
; bring up interface uwsgi0
exec-as-root = ifconfig uwsgi0 192.168.0.2 netmask 255.255.255.0 up
; and set the default gateway
exec-as-root = route add default gw 192.168.0.1
; ping something to register
exec-as-root = ping -c 1 192.168.0.1
; classic options
...
在注册了一个vassal/ip对之后,只有那个组合才是有效的 (因此,其他vassal将不能够使用那个地址,直到持有该地址的vassal死掉)
未来¶
这正成为unbit.it网络栈的一个非常重要的部分。我们现在致力于:
- 动态防火墙规则 (luajit搞了一个用于编写快速网络规则的很棒的工具)
- tuntap路由器的联合/代理 (tuntaprouter可以在一个tcp连接上多路传输vassal网络到一个外部的tuntap路由器 [这就是为什么你可以绑定一个tuntap路由器到一个tcp地址上])
- vassal鉴权 (或许老的UNIX配套凭证就够了)
- 用于网络统计数据(rx/tx/errors)的统计数据服务器
- 基于blastbeat项目的bandwidth shaper
密切关注你的应用¶
使用Nagios监控uWSGI¶
官方的uWSGI发行版包含了一个插件,用来添加 Nagios-友好型输出。
要通过Nagios监控,并最终获得告警消息,请使用以下命令,其中, node
是要监控的socket (UNIX或者TCP)。
uwsgi --socket <node> --nagios
设置告警消息¶
你可以使用 uwsgi.set_warning_message()
函数,直接从你的应用设置一个告警消息。所有的ping响应 (Nagios也使用) 将会报告这个消息。
内嵌SNMP服务器¶
uWSGI服务器内嵌了一个小小的SNMP服务器,你可以用它来把你的web应用和监控基础设施集成在一起。
要启用SNMP支持,你必须运行uWSGI UDP服务器,然后选择一个SNMP community字符串 (这是SNMP使用的基本鉴权系统)。
./uwsgi -s :3031 -w staticfilesnmp --udp 192.168.0.1:2222 --snmp --snmp-community foo
# or the following. Using the SNMP option to pass the UDP address is a lot more elegant. ;)
./uwsgi -s :3031 -w myapp --master --processes 4 --snmp=192.168.0.1:2222 --snmp-community foo
这将会在TCP端口3031和UDP端口2222上运行uWSGI服务器,启用SNMP,并使用”foo”作为community字符串。
请注意,SNMP服务器是在移除特权之后,在master进程中启动的。如果你想要它监听一个特权端口,那么你可以在linux上使用 Capabilities ,或者使用 master-as-root
选项,作为root运行master经常。发行版中包含了 staticfilesnmp.py
文件,它是一个通过SNMP公开了一个计数器的简单应用。
uWSGI SNMP服务器公开了2组信息:
- 由uWSGI服务器自身管理的一般信息。访问uWSGI SNMP信息的基本OID是
1.3.6.1.4.1.35156.17
(iso.org.dod.internet.private.enterprise.unbit.uwsgi
)。常规选项被映射到1.3.6.1.4.1.35156.17.1.x
。 - 自定义信息由应用管理,并且通过
1.3.6.1.4.1.35156.17.2.x
访问。
所以,要获取uWSGI服务器管理的请求数,你可以
snmpget -v2c -c foo 192.168.0.1:2222 1.3.6.1.4.1.35156.17.1.1 # 1.1 corresponds to ``general.requests``
导出自定义值¶
要从你的应用管理自定义值,你有这些Python函数,
uwsgi.snmp_set_counter32()
uwsgi.snmp_set_counter64()
uwsgi.snmp_set_gauge()
uwsgi.snmp_incr_counter32()
uwsgi.snmp_incr_counter64()
uwsgi.snmp_incr_gauge()
uwsgi.snmp_decr_counter32()
uwsgi.snmp_decr_counter64()
uwsgi.snmp_decr_gauge()
所以,如果你想要将当前登录用户数量(这是一个计量,因为它可以降低)作为自定义OID 40公开,那么调用
users_logged_in = random.randint(0, 1024) # a more predictable source of information would be better.
uwsgi.snmp_set_gauge(40, users_logged_in)
然后查询它,
snmpget -v2c -c foo 192.168.0.1:2222 1.3.6.1.4.1.35156.17.2.40
可以配置系统snmp守护进程 (net-snmp) 来代理SNMP请求到uwsgi。这允许你同时运行系统守护进程和uwsgi,并且首先通过系统守护进程运行所有SNMP请求。要配置系统snmp守护进程 (net-snmp) 来代理连接到uwsgi,则添加这些行到/etc/snmp/snmpd.conf底部,然后重启守护进程:
proxy -v 2c -c foo 127.0.0.1:2222 .1.3.6.1.4.1.35156.17
view systemview included .1.3.6.1.4.1.35156.17
用uwsgi中配置的community和端口替换’foo’和‘2222’。
推送统计信息 (从1.4起)¶
重要:度量子系统提供了对以下概念更好的介绍。见 度量(Metrics)子系统
从uWSGI 1.4起,你可以通过各种系统(称之为统计信息推送器)推送统计数据 (与你通过 uWSGI Stats服务器 获得的相同的JSON块)。
会定期 (默认是3秒) 对统计数据进行推送。
‘file’统计信息推送器¶
默认情况下,直到1.9.18,就可以使用’file’ 统计信息推送器了。从1.9.19起,它可以作为一个插件 (stats_pusher_file) 使用。
它允许你保存json数据块到一个文件中 (以附加模式打开)
[uwsgi]
socket = :3031
module = foobar
master = true
stats-push = file:path=/tmp/foobar,freq=10
这个配置将会每10秒附加JSON到/tmp/foobar文件
‘mongodb’统计信息推送器¶
这是第一个开发的统计信息推送器插件,它允许你直接在一个mongodb集合上存储JSON数据
[uwsgi]
plugins = stats_pusher_mongodb
socket = :3031
module = foobar
master = true
stats-push = mongodb:addr=127.0.0.1:5151,collection=uwsgi.mystats,freq=4
这个配置将会每4秒把JSON数据插入到mongodb服务器127.0.0.1:5151上的集合uwsgi.mystats中。
要构建这个插件,你需要mongodb开发头文件 (Debian/Ubuntu上是mongodb-dev)
python uwsgiconfig.py --plugin plugins/stats_pusher_mongodb
将会达到目的
小抄¶
你可以配置所有你需要的统计数据推送器,只需指定多个stats-push选项
[uwsgi]
plugins = stats_pusher_mongodb
socket = :3031
module = foobar
master = true
stats-push = mongodb:addr=127.0.0.1:5151,collection=uwsgi.mystats,freq=4
stats-push = mongodb:addr=127.0.0.1:5152,collection=uwsgi.mystats,freq=4
stats-push = mongodb:addr=127.0.0.1:5153,collection=uwsgi.mystats,freq=4
stats-push = mongodb:addr=127.0.0.1:5154,collection=uwsgi.mystats,freq=4
与Graphite/Carbon集成¶
Graphite 是一个强大的实时图形应用,构建于3个组件之上:
- Whisper – 一个数据存储系统
- Carbon – 一个用来接收数据的服务器
- 用于图形渲染和管理的Python web应用。
uWSGI Carbon插件允许你发送uWSGI的内部统计数据到一个或多个Carbon服务器上。自uWSGI 1.0起,默认编译它,尽管它也可以作为插件构建。
快速开始¶
为了说明起见,假设你的Carbon服务器正监听
127.0.0.1:2003
,而你的uWSGI实例位于机器 debian32
之上,有4个进程监听 127.0.0.1:3031
。通过添加 --carbon
选项到你的uWSGI中,你将指示它定时发送它的统计数据到Carbon服务器。默认的周期是60秒。
uwsgi --socket 127.0.0.1:3031 --carbon 127.0.0.1:2003 --processes 4
度量的命名类似于 uwsgi.<hostname>.<id>.requests
和
uwsgi.<hostname>.<id>.worker<n>.requests
,其中:
hostname
– 机器的主机名id
– 第一个uWSGI socket的名字 (会用下划线来替换点)n
– worker进程的数目 (基于1)。
uWSGI生成的Carbon度量的名字举例:
uwsgi.debian32.127_0_0_1:3031.requests
(uwsgi.<hostname>.<id>.requests
)uwsgi.debian32.127_0_0_1:3031.worker1.requests
(uwsgi.<hostname>.<id>.worker<n>.requests
)uwsgi.debian32.127_0_0_1:3031.worker2.requests
(uwsgi.<hostname>.<id>.worker<n>.requests
)uwsgi.debian32.127_0_0_1:3031.worker3.requests
(uwsgi.<hostname>.<id>.worker<n>.requests
)uwsgi.debian32.127_0_0_1:3031.worker4.requests
(uwsgi.<hostname>.<id>.worker<n>.requests
).
uWSGI Stats服务器¶
除了 SNMP ,uWSGI还支持一个Stats服务器机制,它将uWSGI状态作为一个JSON对象导出到一个socket。
只需使用 stats
选项,后面跟着一个有效的socket地址。如果你想通过HTTP提供stats,那么还需要添加 stats-http
选项。
--stats 127.0.0.1:1717
--stats /tmp/statsock
--stats :5050
--stats @foobar
# Any of the above socket types can also return stats using HTTP
--stats 127.0.0.1:1717 --stats-http
如果客户端连接到一个指定的socket,那么在连接结束之前,它将获得一个包含uWSGI内部统计数据的JSON对象。
uwsgi --socket :3031 --stats :1717 --module welcome --master --processes 8
然后
nc 127.0.0.1 1717
# or for convenience...
uwsgi --connect-and-read 127.0.0.1:1717
将返回像这样的东东:
{
"workers": [{
"id": 1,
"pid": 31759,
"requests": 0,
"exceptions": 0,
"status": "idle",
"rss": 0,
"vsz": 0,
"running_time": 0,
"last_spawn": 1317235041,
"respawn_count": 1,
"tx": 0,
"avg_rt": 0,
"apps": [{
"id": 0,
"modifier1": 0,
"mountpoint": "",
"requests": 0,
"exceptions": 0,
"chdir": ""
}]
}, {
"id": 2,
"pid": 31760,
"requests": 0,
"exceptions": 0,
"status": "idle",
"rss": 0,
"vsz": 0,
"running_time": 0,
"last_spawn": 1317235041,
"respawn_count": 1,
"tx": 0,
"avg_rt": 0,
"apps": [{
"id": 0,
"modifier1": 0,
"mountpoint": "",
"requests": 0,
"exceptions": 0,
"chdir": ""
}]
}, {
"id": 3,
"pid": 31761,
"requests": 0,
"exceptions": 0,
"status": "idle",
"rss": 0,
"vsz": 0,
"running_time": 0,
"last_spawn": 1317235041,
"respawn_count": 1,
"tx": 0,
"avg_rt": 0,
"apps": [{
"id": 0,
"modifier1": 0,
"mountpoint": "",
"requests": 0,
"exceptions": 0,
"chdir": ""
}]
}, {
"id": 4,
"pid": 31762,
"requests": 0,
"exceptions": 0,
"status": "idle",
"rss": 0,
"vsz": 0,
"running_time": 0,
"last_spawn": 1317235041,
"respawn_count": 1,
"tx": 0,
"avg_rt": 0,
"apps": [{
"id": 0,
"modifier1": 0,
"mountpoint": "",
"requests": 0,
"exceptions": 0,
"chdir": ""
}]
}, {
"id": 5,
"pid": 31763,
"requests": 0,
"exceptions": 0,
"status": "idle",
"rss": 0,
"vsz": 0,
"running_time": 0,
"last_spawn": 1317235041,
"respawn_count": 1,
"tx": 0,
"avg_rt": 0,
"apps": [{
"id": 0,
"modifier1": 0,
"mountpoint": "",
"requests": 0,
"exceptions": 0,
"chdir": ""
}]
}, {
"id": 6,
"pid": 31764,
"requests": 0,
"exceptions": 0,
"status": "idle",
"rss": 0,
"vsz": 0,
"running_time": 0,
"last_spawn": 1317235041,
"respawn_count": 1,
"tx": 0,
"avg_rt": 0,
"apps": [{
"id": 0,
"modifier1": 0,
"mountpoint": "",
"requests": 0,
"exceptions": 0,
"chdir": ""
}]
}, {
"id": 7,
"pid": 31765,
"requests": 0,
"exceptions": 0,
"status": "idle",
"rss": 0,
"vsz": 0,
"running_time": 0,
"last_spawn": 1317235041,
"respawn_count": 1,
"tx": 0,
"avg_rt": 0,
"apps": [{
"id": 0,
"modifier1": 0,
"mountpoint": "",
"requests": 0,
"exceptions": 0,
"chdir": ""
}]
}, {
"id": 8,
"pid": 31766,
"requests": 0,
"exceptions": 0,
"status": "idle",
"rss": 0,
"vsz": 0,
"running_time": 0,
"last_spawn": 1317235041,
"respawn_count": 1,
"tx": 0,
"avg_rt": 0,
"apps": [{
"id": 0,
"modifier1": 0,
"mountpoint": "",
"requests": 0,
"exceptions": 0,
"chdir": ""
}]
}]
}
uwsgitop¶
uwsgitop
是一个类似于top的命令,它使用stats服务器。可以在PyPI找到,因此使用 easy_install
或者 pip
来安装它 (自然而然地,包名是 uwsgitop
)。
可以在Github找到其源码。https://github.com/unbit/uwsgitop
异步和循环引擎¶
uWSGI异步/非堵塞模式 (已更新至uWSGI 1.9)¶
警告
注意!异步模式并不会加速你的应用,它们旨在提高并发性。不要指望启用某些模式会完美运行,异步/事件/非阻塞系统需要应用的配合,因此,如果你的应用是在没有考虑特有的异步引擎的规则下开发的,那么你就错了。不要相信那些建议你盲目使用异步/事件/非阻塞系统的人!
词汇表¶
uWSGI,遵循其模块化方法,将异步引擎分成两类。
挂起/恢复引擎¶
它们简单实现了协程(coroutine)/绿色线程(green thread)技术。它们并无事件引擎,因此,你必须使用由uWSGI提供的。一个事件引擎通常是一个为平台无关的非阻塞I/O(libevent, libev, libuv等等)导出基元的库。使用 --async <n>
选项启用uWSGI事件引擎。
目前,uWSGI发布版本包含了以下挂起/恢复引擎:
uGreen
- Unbit的绿色线程实现 (基于swapcontext()
)Greenlet
- Python greenlet模块Stackless
- Stackless PythonFiber
- Ruby 1.9 fibers
在没有合适的挂起/恢复引擎的情况下运行uWSGI异步模式将会引发告警,因此,对于一个最小的无阻塞应用,你将需要这样的东东:
uwsgi --async 100 --ugreen --socket :3031
挂起/恢复引擎的一个重要的方面是,如果没有意识到它们的存在的话,它们可以轻而易举的摧毁你的进程。一些语言插件(最明显的是Python)有与协程/绿色线程完美合作的钩子。其他语言可能惨遭失败。务必经常检查uWSGI邮件列表或者IRC频道,以获取更新信息。
较老的uWSGI版本支持额外的系统:回调。回调是诸如node.js这样的流行系统使用的方法。该方法要求 大量 应用的协作,而对于像uWSGI这样的复杂项目,处理这个是非常复杂的。出于这个原因, 并不支持 回调方法 (即使技术上是可行的) 基于回调的软件 (像 Tornado循环引擎) 可以用来将它们与某种形式的挂起引擎结合在一起。
I/O引擎(或事件系统)¶
uWSGI有一个高度优化的事件触发技术,但也可以使用其他方法。
I/O引擎都需要一些挂起/恢复引擎,否则会发生糟糕透的事情 (整个uWSGI代码库都是协程友好的,因此,你可以非常容易地玩转栈)。
目前支持的I/O引擎是:
- Tornado循环引擎
libuv
(工作正在进行中)libev
(工作正在进行中)
循环引擎¶
循环引擎是既导出挂起/恢复技术,又导出事件系统的包/库。加载后,它们uWSGI管理连接和信号处理器 (uWSGI信号, 不是 POSIX信号)的方式。
目前,uWSGI支持以下循环引擎:
Gevent
(Python, libev, greenlet)Coro::AnyEvent
(Perl, coro, anyevent)
虽然它们通常由特定的语言使用,但是纯C的uWSGI插件 (像CGI) 可以用它们正常地提高并发性。
异步开关¶
要启用异步模式,你得使用 --async
选项 (或者它的一些快捷方式,由循环引擎插件导出)。
--async
选项的参数是要初始化的“核心”数。每个核可以管理一个单一的请求,因此,生成越多的核,就能管理越多的请求 (并且会使用越多的内存)。挂起/恢复引擎的工作的是停止当前的请求管理,移到另一个核,最后回到旧的核(等等)。
从技术上讲,核只是保存请求数据的内存结构,但为了给用户多线程系统的错觉,我们使用了这个术语。
核之间的切换需要应用的协作。有多种方式来完成,通常,如果你正使用一个循环引擎,那么全都是自动的 (或者只需很少的努力)。
警告
如果你怀疑,那么 不要使用异步模式 。
异步模式下运行uWSGI¶
要以异步模式启动,需要传递 --async
选项以及你想要的“异步核”数。
./uwsgi --socket :3031 -w tests.cpubound_async --async 10
这将会启动uWSGI,其中,uWSGI使用10个异步核。每个异步核可以管理一个请求,因此,有了这一步,只需1个进程就可以接受10个并发请求。你还可以启动更多请求 (使用 --processes
选项),每个将会有它们自己的异步核池。
当使用 harakiri 模式的时候,每当一个异步核接受一个请求的时,就会重置harakiri定时器。因此,即使请求阻塞了异步系统,harakiri也会救你一命。
源代码发布版本中包含了 tests.cpubound_async
应用。它非常简单:
def application(env, start_response):
start_response('200 OK', [('Content-Type', 'text/html')])
for i in range(1, 10000):
yield "<h1>%s</h1>" % i
每当应用在响应函数中执行了 yield
,就会停止应用的执行,而另一个异步核上的一个新的请求或者前一个挂起的请求将会接管。这意味着异步核的数目就是可以排队的请求数。
如果在一个非异步服务器上运行 tests.cpubound_async
应用,那么它将阻塞所有的进程:不会接收其他请求,直到10000个 <h1>
组成的循环完成。
等待I/O¶
如果你不处于循环引擎之下,那么可以使用uWSGI API来等待I/O事件。
当前,只导出了2个函数:
可以连续调用这些函数,以等待多个文件描述符:
uwsgi.wait_fd_read(fd0)
uwsgi.wait_fd_read(fd1)
uwsgi.wait_fd_read(fd2)
yield "" # yield the app, let uWSGI do its magic
休眠¶
有时,你可能想要在你的应用中休眠,例如要限制带宽。
使用 uwsgi.async_sleep(N)
取代堵塞的 time.sleep(N)
函数来生成N秒的控制。
参见
参见样例 tests/sleeping_async.py
。
挂起/恢复¶
从主应用生成并不非常实用,因为大部分时间,你的应用比一个简单的可回调更高级,并且由大量的函数和不同层次的调用深度构成。
别担心!你可以通过简单调用 uwsgi.suspend()
来强制挂起(使用协程/绿色线程):
uwsgi.wait_fd_read(fd0)
uwsgi.suspend()
uwsgi.suspend()
会自动调用已选的挂起引擎 (uGreen, greenlet, 等等。)。
Gevent循环引擎¶
Gevent 是一个令人惊奇的非阻塞Python网络库,构建在
libev
和 greenlet
之上。虽然uWSGI支持Greenlet作为挂起-恢复/绿色线程/协程库,但是还需要大量的努力和代码修改才能对gevent起作用。gevent插件要求gevent
1.0.0 和 uWSGI异步/非堵塞模式 (已更新至uWSGI 1.9) 模式。
注意¶
SignalFramework
完全对Gevent模式有效。每个处理函数将会在一个专用的greenlet中执行。看看tests/ugevent.py
这个例子。- uWSGI多线程模式 (
threads
选项) 对Gevent无效。支持在你的应用中运行Python多线程。 - 把uWSGI的异步API和gevent混在一起是**明确禁止的**。
构建插件 (uWSGI < 1.4)¶
可以在 buildconf
目录中找到一个”gevent”构建配置文件。
python uwsgiconfig --build gevent
# or...
UWSGI_PROFILE=gevent make
# or...
UWSGI_PROFILE=gevent pip install git+git://github.com/unbit/uwsgi.git
# or...
python uwsgiconfig --plugin plugins/gevent # external plugin
以gevent模式运行uWSGI¶
uwsgi --gevent 100 --socket :3031 --module myapp
或者对于模块化构建:
uwsgi --plugins python,gevent --gevent 100 --socket :3031 --module myapp
–gevent的参数是要生成的异步核数
一个疯狂的例子¶
以下例子显示如何在请求中休眠,如何发起异步网络请求,以及如何在一个请求已经被关闭之后继续进行一些逻辑处理。
import gevent
import gevent.socket
def bg_task():
for i in range(1,10):
print "background task", i
gevent.sleep(2)
def long_task():
for i in range(1,10):
print i
gevent.sleep()
def application(e, sr):
sr('200 OK', [('Content-Type','text/html')])
t = gevent.spawn(long_task)
t.join()
yield "sleeping for 3 seconds...<br/>"
gevent.sleep(3)
yield "done<br>"
yield "getting some ips...<br/>"
urls = ['www.google.com', 'www.example.com', 'www.python.org', 'projects.unbit.it']
jobs = [gevent.spawn(gevent.socket.gethostbyname, url) for url in urls]
gevent.joinall(jobs, timeout=2)
for j in jobs:
yield "ip = %s<br/>" % j.value
gevent.spawn(bg_task) # this task will go on after request end
猴子补丁¶
uWSGI使用原生gevent api,因此,并不需要猴子补丁。即便如此,你的代码也可能需要它,因此,记得在你的应用的开头调用 gevent.monkey.patch_all()
。自uWSGI 1.9起,便利的
--gevent-monkey-patch
选项将会为你完成这个工作。
请注意,uWSGI是在你的应用 启动 的时候进行猴子补丁的,而不是在你的应用 加载 之前。因此,如果你在加载应用的时候加载其他模块,那么你或许仍然需要自己调用
gevent.monkey.patch_all()
。
一个常见的例子是将 psycopg2_gevent
用于django。Django会为每个线程对postgres发起连接 (将其存储在线程变量中)。
随着uWSGI gevent插件运行在一个单一的线程中,这个方法将会导致psycopg中的死锁。启用猴子补丁将会让你映射线程局部变量到greenlet (虽然你可以避免完全猴子补丁,并只调用
gevent.monkey.patch_thread()
) ,然后解决这个问题:
import gevent.monkey
gevent.monkey.patch_thread()
import gevent_psycopg2
gevent_psycopg2.monkey_patch()
或者 (为所有东西打上猴子补丁)
import gevent.monkey
gevent.monkey.patch_all()
import gevent_psycopg2
gevent_psycopg2.monkey_patch()
客户端和前端的一些注意事项¶
- 如果你正在测试一个生成一连串数据的WSGI应用,那么你应该知道,默认情况下
curl
缓存数据知道一个新行出现。因此,你要么确保使用-N
标记来禁用curl的缓存,要么在你的输出中确保有常规的新行。 - 如果你在uWSGI之前使用Nginx,并且希望从你的应用流数据,那么你可能会想要禁用Nginx的缓存。
uwsgi_buffering off;
Tornado循环引擎¶
可用版本自 `uWSGI 1.9.19-dev`
起
支持挂起引擎: `greenlet`
支持CPython版本: `所有支持tornado的版本`
tornado循环引擎允许你将你的uWSGI栈与Tornado IOLoop类集成。
基本上,服务器的每一个I/O操作会被映射到一个tornado IOLoop回调。进行RPC、远程缓存或者简单的写入响应则由Tornado引擎管理。
由于uWSGI不是用一个基于回调的编程方法写的,因此与那种类型的库集成需要某些类型“挂起”引擎 (绿色线程/协程)
目前唯一支持的挂起引擎是”greenlet”。Stackless python也能用 (需要测试)。
当前并不支持PyPy (尽管因为continulet,技术上是可行的)。如果你感兴趣的话,给Unbit员工发邮件吧。
为什么?¶
Tornado项目自己包含了一个简单的WSGI服务器。在于Gevent插件相同的思想下,Loop引擎的目的是允许外部项目使用(尽情使用)uWSGI api,从而获得更好的性能、多功能性和 (也许是最重要的) 资源使用。
在你的tornado应用中,可以使用所有的uWSGI子系统 (从缓存,到websockets,到度量),而WSGI引擎则是其中一个久经考验的uWSGI。
安装¶
当前默认不内置tornado插件。要在单个二进制文件中同时拥有tornado和greenlet,你可以这样
UWSGI_EMBED_PLUGINS=tornado,greenlet pip install tornado greenlet uwsgi
或者 (来自uWSGI源代码,如果你已经安装了tornado和greenlet的话)
UWSGI_EMBED_PLUGINS=tornado,greenlet make
运行之¶
--tornado
选项是由tornado插件公开的,允许你设置最佳参数:
uwsgi --http-socket :9090 --wsgi-file myapp.py --tornado 100 --greenlet
这将会在http端口9090上运行一个uWSGI实例,使用tornado作为I/O(和时间)管理,greenlet作为挂起引擎
会分配100个异步核心,允许你管理多达100个并发请求
集成WSGI和tornado api¶
出于WSGI的工作方式,处理基于回调的编程是相当难的 (如果有可能的话)。
有了greenlet,我们可以挂起我们的WSGI可调用的执行,直到一个tornado IOLoop事件可用:
from tornado.httpclient import AsyncHTTPClient
import greenlet
import functools
# this gives us access to the main IOLoop (the same used by uWSGI)
from tornado.ioloop import IOLoop
io_loop = IOLoop.instance()
# this is called at the end of the external HTTP request
def handle_request(me, response):
if response.error:
print("Error:", response.error)
else:
me.result = response.body
# back to the WSGI callable
me.switch()
def application(e, sr):
me = greenlet.getcurrent()
http_client = AsyncHTTPClient()
http_client.fetch("http://localhost:9191/services", functools.partial(handle_request, me))
# suspend the execution until an IOLoop event is available
me.parent.switch()
sr('200 OK', [('Content-Type','text/plain')])
return me.result
欢迎来到回调地狱¶
一如既往,判断编程方法并非uWSGI的工作。它是为系统管理员提供的工具,而系统管理员应该宽容开发者的选择。
使用这个方法,你将很快体验到的事情之一是回调地狱。
让我们扩展前面的例子,在发送响应回客户端之前等待10秒
from tornado.httpclient import AsyncHTTPClient
import greenlet
import functools
# this gives us access to the main IOLoop (the same used by uWSGI)
from tornado.ioloop import IOLoop
io_loop = IOLoop.instance()
def sleeper(me):
#TIMED OUT
# finally come back to WSGI callable
me.switch()
# this is called at the end of the external HTTP request
def handle_request(me, response):
if response.error:
print("Error:", response.error)
else:
me.result = response.body
# add another callback in the chain
me.timeout = io_loop.add_timeout(time.time() + 10, functools.partial(sleeper, me))
def application(e, sr):
me = greenlet.getcurrent()
http_client = AsyncHTTPClient()
http_client.fetch("http://localhost:9191/services", functools.partial(handle_request, me))
# suspend the execution until an IOLoop event is available
me.parent.switch()
# unregister the timer
io_loop.remove_timeout(me.timeout)
sr('200 OK', [('Content-Type','text/plain')])
return me.result
这里,我们链接了两个回调,最后一个负责将控制权交还WSGI可调用
代码可能看起来丑或者过于复杂 (与其他诸如gevent的方法相比),但是,这基本上是提高并发性最有效的方法 (同时在内存使用和性能方面)。诸如node.js这样的技术由于它们允许完成的结果,它们正变得流行起来。
WSGI生成器 (aka yield all over the place)¶
以下面的WSGI应用为例:
def application(e, sr):
sr('200 OK', [('Content-Type','text/html')])
yield "one"
yield "two"
yield "three"
如果你已经使用uWSGI异步模式,那么你就会知道每次yield内部调用使用的挂起引擎 (在我们的例子中,是greenlet.switch())。
那意味着,我们在调用”application()”后会立即进入tornado IOLoop引擎。如果我们不在等待事件,那么能如何将控制权交还给我们的可回调对象?
已扩展uWSGI异步API来支持”schedule_fix”钩子。它允许你在调用挂起引擎后立即调用一个钩子。
在tornado这种情况下,这个钩子会被映射到某些像这样的东东:
io_loop.add_callback(me.switch)
通过这种方式,在每次yield之后,一个me.switch()函数就会被调用,从而让可回调对象恢复。
有了这个钩子,你可以透明地托管标准的WSGI应用,而无需更改它们。
绑定和监听Tornado¶
在每一个worker中,在fork()之后会执行Tornado IOLoop。如果你想把Tornado绑定到网络地址上,那么记得为每个worker使用不同的端口:
from uwsgidecorators import *
import tornado.web
# this is our Tornado-managed app
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write("Hello, world")
t_application = tornado.web.Application([
(r"/", MainHandler),
])
# here happens the magic, we bind after every fork()
@postfork
def start_the_tornado_servers():
application.listen(8000 + uwsgi.worker_id())
# this is our WSGI callable managed by uWSGI
def application(e, sr):
...
记住:不要启动IOLoop类。一旦安装完成,uWSGI将会自己启动它。
uGreen – uWSGI绿色线程(green thread)¶
uGreen是在 uWSGI async platform 之上的一个 green threads 实现。
它与Python的greenlet非常相似,但是是构建在POSIX的 swapcontext()
函数之上的。要利用uGreen,你必须设置将会映射到green线程的异步核心数。
例如,如果你想要生成30个green线程:
./uwsgi -w tests.cpubound_green -s :3031 --async 30 --ugreen
ugreen
选项将会在async模式之上启用uGreen。
现在,当你在应用中调用 uwsgi.suspend()
时,你将会被切换到另一个green线程。
安全性和性能¶
为确保绿色线程的(相对)隔离,每个堆栈区都由所谓的“保护页”保护。
在绿色线程的堆栈区外尝试写入将会导致一个分段错误/总线错误 (而进程管理器,如果启用的话,将会在无太多损害的情况下重新生成worker)。
上下文切换非常快,我们可以将其视为:
- 转换时
- 保存Python帧指针
- 保存Python环境的递归深度 (它只是一个int)
- 切换到主堆栈
- 返回时
- 重置uGreen堆栈
- 重置递归深度
- 重置帧指针
堆栈/寄存器开关切换是由POSIX的 swapcontext()
调用完成的,我们无需担心它。
异步I/O¶
要管理异步I/O,你可以使用异步模式FD等待函数 uwsgi.wait_fd_read()
和 uwsgi.wait_fd_write()
。
栈大小¶
你可以使用 ugreen-stacksize <pages>
选项选择uGreen堆栈大小。参数是以页为单位的,不是字节。
它比Greenlet或者Stackless Python更好吗?¶
嗯……看情况。uGreen更快 (堆栈是预先分配的),但需要更多内存 (为每个核分配一个堆栈区域)。而Stackless和Greenlet也许需要较少的内存……但是Stackless要求使用Python的一个重度打补丁版本。
如果你正投入巨大精力让你的应用约异步越好,那么做一些测试来选择最好的一个总是最好的。至于uWSGI,你可以在无需修改代码的情况下从异步引擎移到到另一个。
python-coev
又如何?
uGreen的许多部分是受其启发的。作者将Python线程映射到它们的实现的方式允许 python-coev
比Stackless Python更“值得信赖”点。然而,正如Stackless,它要求Python的一个补丁版本…… :(
我可以使用uGreen来写Comet应用吗?¶
是哒!当然!前进吧。在发行版中,你将会找到 ugreenchat.py
脚本。它是一个简单的/基本的多用户Comet聊天应用。如果你想要测试它 (例如,30个用户),那么这样运行它
./uwsgi -s :3031 -w ugreenchat --async 30 --ugreen
对于每一行ugreen相关的代码,都有注释。你会需要 Bottle ,一个惊人的Python web微框架来使用它。
Psycopg2改进¶
uGreen可以从新的psycopg2异步扩展和psycogreen项目受益。见 tests/psycopg2_green.py
和 tests/psycogreen_green.py
文件获得样例信息。
asyncio循环引擎 (CPython >= 3.4, uWSGI >= 2.0.4)¶
警告
状态:实验中,有许多隐含式,特别是关于WSGI标准
asyncio
插件公开了一个建立在 asyncio
CPython API (https://docs.python.org/3.4/library/asyncio.html#module-asyncio)顶部的一个循环引擎。
由于uWSGI并不是基于回调的,因此你需要一个挂起引擎(目前只支持“greenlet”)来管理WSGI callable。
为什么不把WSGI callable映射到一个协程呢?¶
理由很简单:这会以各种可能的方式终端。(这里就不深入细节了。)
出于这个原因,每个uWSGI核被映射到一个greenlet (运行WSGI回调)上。
这个greenlet在asyncio事件循环中注册事件和协程。
Callable VS. 协程¶
当开始使用asyncio时,你可能会对Callable和协程之间感到困惑。
当一个特定的事件引发的时候(例如,当一个文件描述符准备用于读的时候),会执行Callable。它们基本上是在主greenlet中执行的标准函数 (最终,它们可以切换控制回特定的uWSGI核上)。
协程更复杂:它们很接近greenlet,但在内部,它们运行在Python帧之上,而不是C堆栈上。自一个Python程序员看来,协程是非常特别的生成器,你的WSGI callable可以生成协程。
利用asyncio支持构建uWSGI¶
可以在官方源代码树(也将构建greenlet支持)中找到一个’asyncio’构建配置文件。
CFLAGS="-I/usr/local/include/python3.4" make PYTHON=python3.4 asyncio
或者
CFLAGS="-I/usr/local/include/python3.4" UWSGI_PROFILE="asyncio" pip3 install uwsgi
一定要使用Python 3.4+作为Python版本,并且添加greenlet include目录到 CFLAGS
(如果你从发行包中安装了greenlet支持,那么这可能并不需要)。
第一个例子:一个简单的回调¶
让我们从一个简单的WSGI callable开始,它在该 callable返回之后2秒后触发一个函数(神奇!)。
import asyncio
def two_seconds_elapsed():
print("Hello 2 seconds elapsed")
def application(environ, start_response):
start_response('200 OK', [('Content-Type','text/html')])
asyncio.get_event_loop().call_later(2, two_seconds_elapsed)
return [b"Hello World"]
一旦被调用,那么该应用函数将在asyncio事件循环中注册一个 callable,然后会返回到客户端。
在2秒后,事件循环将会运行该函数。
你可以这样运行这个例子:
uwsgi --asyncio 10 --http-socket :9090 --greenlet --wsgi-file app.py
--asyncio
是一个快捷方式,它启用10个uWSGI异步核,让你能够用一个单一的进程就可以管理多达10个并发请求。
但是,如何在WSGI callable中等待一个回调完成呢?我们可以使用greenlet来挂起我们的WSGI函数 (记住,我们的WSGI callable是封装在一个greenlet中的):
import asyncio
import greenlet
def two_seconds_elapsed(me):
print("Hello 2 seconds elapsed")
# back to WSGI callable
me.switch()
def application(environ, start_response):
start_response('200 OK', [('Content-Type','text/html')])
myself = greenlet.getcurrent()
asyncio.get_event_loop().call_later(2, two_seconds_elapsed, myself)
# back to event loop
myself.parent.switch()
return [b"Hello World"]
我们可以更进一步,为WSGI生成器尽情使用uWSGI支持:
import asyncio
import greenlet
def two_seconds_elapsed(me):
print("Hello 2 seconds elapsed")
me.switch()
def application(environ, start_response):
start_response('200 OK', [('Content-Type','text/html')])
myself = greenlet.getcurrent()
asyncio.get_event_loop().call_later(2, two_seconds_elapsed, myself)
myself.parent.switch()
yield b"One"
asyncio.get_event_loop().call_later(2, two_seconds_elapsed, myself)
myself.parent.switch()
yield b"Two"
另一个例子:Future与协程¶
你可以使用 asyncio.Task
从你的 WSGI callable中生成协程:
import asyncio
import greenlet
@asyncio.coroutine
def sleeping(me):
yield from asyncio.sleep(2)
# back to callable
me.switch()
def application(environ, start_response):
start_response('200 OK', [('Content-Type','text/html')])
myself = greenlet.getcurrent()
# enqueue the coroutine
asyncio.Task(sleeping(myself))
# suspend to event loop
myself.parent.switch()
# back from event loop
return [b"Hello World"]
有了Future,我们甚至可以从协程中获取结果……
import asyncio
import greenlet
@asyncio.coroutine
def sleeping(me, f):
yield from asyncio.sleep(2)
f.set_result(b"Hello World")
# back to callable
me.switch()
def application(environ, start_response):
start_response('200 OK', [('Content-Type','text/html')])
myself = greenlet.getcurrent()
future = asyncio.Future()
# enqueue the coroutine with a Future
asyncio.Task(sleeping(myself, future))
# suspend to event loop
myself.parent.switch()
# back from event loop
return [future.result()]
一个更高级的使用 aiohttp
模块的例子 (记住执行 pip install aiohttp
来安装它,它并不是一个标准库模块)
import asyncio
import greenlet
import aiohttp
@asyncio.coroutine
def sleeping(me, f):
yield from asyncio.sleep(2)
response = yield from aiohttp.request('GET', 'http://python.org')
body = yield from response.read_and_close()
# body is a byterray !
f.set_result(body)
me.switch()
def application(environ, start_response):
start_response('200 OK', [('Content-Type','text/html')])
myself = greenlet.getcurrent()
future = asyncio.Future()
asyncio.Task(sleeping(myself, future))
myself.parent.switch()
# this time we use yield, just for fun...
yield bytes(future.result())
状态¶
- 该插件被认为是实验性的 (WSGI中使用asyncio的影响目前尚未清楚)。未来,当检测到Python >= 3.4的时候,可能会默认构建。
- 虽然(或多或少)技术上是可行的,但是在不久的将来,并不期望将一个WSGI callable映射到一个Python 3协程上。
- 该插件为非阻塞的读/写和定时器注册钩子。这意味着,你可以自动使用uWSGI API和asyncio。看看 https://github.com/unbit/uwsgi/blob/master/tests/websockets_chat_asyncio.py 这个例子。
Web服务器支持¶
Apache支持¶
目前,有三个可用的uwsgi协议相关的apache2模块。
mod_uwsgi¶
这是原始模块。它可靠,但是却令人难以置信地丑,并且不遵循许多apache编码规范。
可以以两种方式使用 mod_uwsgi
:
- “assbackwards”方式 (默认的方式)。它是最快的方式,但是离Apache2 API有点远。如果你不对uWSGI生成的内容使用Apache2过滤器 (包括gzip),那么使用这个模式。
- “cgi”模式。这个相对有点慢,但是更Apache更好的集成。要使用CGI模式,则传递
-C
给uWSGI服务器。
选项¶
注解
所有的选项都可以被设置在每个host或者每个location上。
uWSGISocket <path> [timeout] uwsgi服务器socket的绝对路径和可选超时时间(以秒为单位)。
uWSGISocket2 <path> 容错uwsgi服务器socket的绝对路径
uWSGIServer <host:port> 一个UWSGI服务器的地址和端口 (例如,localhost:4000)
uWSGIModifier1 <int> 设置uWSGI modifier1
uWSGIModifier2 <int> 设置uWSGI modifier2
uWSGIForceScriptName <value> 强制使用 SCRIPT_NAME
(应用名)
uWSGIForceCGIMode <on/off> 强制uWSGI CGI模式,以与apache过滤器完美契合
uWSGIForceWSGIScheme <value> 强制使用WSGI scheme变量 (默认设置为”http”)
uWSGIMaxVars <int> 设置uwsgi协议变量的最大允许数量 (默认是128)
要传递自定义变量,请使用 SetEnv
指令:
SetEnv UWSGI_SCRIPT yourapp
mod_proxy_uwsgi¶
这是最新的模块,并且可能是未来最好的选择。它是一个“代理”模块,因此你会获得由mod_proxy公开的所有特性。它是完全“兼容apache API”的,因此应该很容易将其与可用模块集成。使用它是很方便的;只是记得在你的apache配置中加载mod_proxy和 mod_proxy_uwsgi模块。
ProxyPass /foo uwsgi://127.0.0.1:3032/
ProxyPass /bar uwsgi://127.0.0.1:3033/
ProxyPass / uwsgi://127.0.0.1:3031/
头两个分别设置到/foo和/bar的SCRIPT_NAME,而最后一个使用一个空的SCRIPT_NAME。你可以使用SetEnv指令设置额外的uwsgi变量,使用mod_proxy_balancer设置负载均衡请求。
<Proxy balancer://mycluster>
BalancerMember uwsgi://192.168.1.50:3031/
BalancerMember uwsgi://192.168.1.51:3031/
</Proxy>
ProxyPass / balancer://mycluster
注意成员/节点定义中的最后一个斜杠。对于非空的SCRIPT_NAME/挂载点,它是可选的,但是对于挂载在域的根上的应用,则是必须的。目前,该模块缺乏设置modifier的能力,虽然这将会很快被修复。一个替代方法是设置你想要的插件作为第一个(0)使用:
plugins = 0:php
注解
mod_proxy_uwsgi is considered stable starting from uWSGI 2.0.6
注解
如果你想要使用这个模块 (并且帮助uWSGI项目),那么报告任何你找到的错误,而不是回到古老(并且丑陋)的mod_uwsgi
从Apache 2.4.9起,添加了对Unix socket的支持。语法相当简单:
ProxyPass / unix:/var/lib/uwsgi/app1.sock|uwsgi://uwsgi-uds-app1/
ProxyPass / unix:/var/lib/uwsgi/app2.sock|uwsgi://uwsgi-uds-app2/
注解
根据Apache文档,代理地址的主机名部分会被忽略。然而,如果有人想要在同一个apache实例内,对不同的应用使用不同的socket,那么代理地址会被apache用来标识不同的代理worker:如果代理地址相同,那么只有第一个声明的unix域socket会获得一个worker,而第二个声明的应用将不会拥有worker,并且不可达。关于这个apache限制(问题?)的一个变通是使用不同的主机名。
Cherokee支持¶
注解
Cherokee的最新官方版本有一个uWSGI配置向导。如果你想要使用它,那么你必须在系统的 PATH
所包含的目录下安装uWSGI。
- 为你的目标设置UWSGI处理器。
- 如果你使用的是默认的目标 (
/
),那么记得取消检查check_file
属性。 - 配置类型”Remote”的“信息源”,指定uWSGI的socket名。如果你的uWSGI支持TCP,那么你可以通过在一个不同的机器上生成uWSGI服务器来构建集群。
注解
记得用合适的处理器为你所有包含静态文件 (例如 /media /images ...) 的URI添加目标
动态应用¶
如果你想要热添加应用,则在uWSGI的handler选项中指定 UWSGI_SCRIPT
变量:
- 在 Add new custom environment variable 中指定
UWSGI_SCRIPT
名,并且将你的WSGI脚本名 (不带.py扩展) 作为值。
你的应用将会在第一个请求中自动被加载。
原生HTTP支持¶
HTTPS支持 (自1.3起)¶
使用 https <socket>,<certificate>,<key>
选项。这个选项可能会被多次指定。首先使用OpenSSL工具生成你服务器的密钥,证书签名请求,以及自注册证书:
注解
你会想要一个用于生产的真正的SSL证书的。
openssl genrsa -out foobar.key 2048
openssl req -new -key foobar.key -out foobar.csr
openssl x509 -req -days 365 -in foobar.csr -signkey foobar.key -out foobar.crt
然后使用刚刚生成的SSL证书和密钥启动服务器:
uwsgi --master --https 0.0.0.0:8443,foobar.crt,foobar.key
由于端口443(这个端口通常用于HTTPS)是特权端口 (即非root进程可能不会绑定它),因此你可以使用共享socket机制,像这样绑定后去除特权:
uwsgi --shared-socket 0.0.0.0:443 --uid roberto --gid roberto --https =0,foobar.crt,foobar.key
uWSGI将会绑定到任何IP的443端口,然后删除那些 roberto
的特权,接着讲共享socket 0 (=0
) 用于HTTPS。
注解
=0语法目前未公开。
设置SSL/TLS加密¶
https
选项的第四个参数是可选的,你可以用它来指定OpenSSL加密套件。
[uwsgi]
master = true
shared-socket = 0.0.0.0:443
uid = www-data
gid = www-data
https = =0,foobar.crt,foobar.key,HIGH
http-to = /tmp/uwsgi.sock
这将会为你的SSL/TLS事务(尽可能)设置所有的 HIGHest(最高的) 密码。
客户端证书认证¶
https
还可以有可选的第五个参数。你可以用它来指定一个CA证书来对你的客户端进行鉴权。生成你的CA密钥和证书 (这次,密钥将会是4096位,并且有密保):
openssl genrsa -des3 -out ca.key 4096
openssl req -new -x509 -days 365 -key ca.key -out ca.crt
生成服务器密钥和CSR (如前):
openssl genrsa -out foobar.key 2048
openssl req -new -key foobar.key -out foobar.csr
使用你的新CA注册服务器证书:
openssl x509 -req -days 365 -in foobar.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out foobar.crt
为你的客户端创建一个密钥和一个CSR,使用CA进行对其进行注册,然后将其打包为 PKCS#12。为每个客户端重复这些步骤。
openssl genrsa -des3 -out client.key 2048
openssl req -new -key client.key -out client.csr
openssl x509 -req -days 365 -in client.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out client.crt
openssl pkcs12 -export -in client.crt -inkey client.key -name "Client 01" -out client.p12
然后为证书客户端鉴权配置uWSGI
[uwsgi]
master = true
shared-socket = 0.0.0.0:443
uid = www-data
gid = www-data
https = =0,foobar.crt,foobar.key,HIGH,!ca.crt
http-to = /tmp/uwsgi.sock
注解
如果你不想要强制客户端证书鉴权,那么移除https选项中ca.crt之前的’!’。
HTTP socket¶
http-socket <bind>
选项将会让uWSGI和原生HTTP通信。如果你的web服务器不支持 uwsgi protocol ,但是可以与上游HTTP代理通信,或者如果你正在用诸如Webfaction或者Heroku这样的服务来托管你的应用,那么你可以使用 http-socket
。如果你计划只通过uWSGI开放你的应用,那么用 http
选项来代替,因为路由器/代理/负载均衡器将会保护你。
uWSGI HTTP/HTTPS路由器¶
uWSGI包含一个HTTP/HTTPS路由器/代理/负载均衡器,它能转发请求到uWSGI worker。可以以两种方式使用服务器:嵌入或独立使用。在嵌入模式下,它会自动生成worker,并安装通信socket。在独立使用模式下,你必须指定要连接的uwsgi socket地址。
嵌入模式:
./uwsgi --http 127.0.0.1:8080 --master --module mywsgiapp --processes 4
这将会生成一个监听8080端口的HTTP服务器,它会转发请求到由master进程管理的4个uWSGI worker组成的池中。
独立使用模式:
./uwsgi --master --http 127.0.0.1:8080 --http-to /tmp/uwsgi.sock
这将会生成一个HTTP路由器(出于安全起见,由master管理),它将转发请求到uwsgi socket /tmp/uwsgi.sock
。你可以绑定到多个地址/端口。
[uwsgi]
http = 0.0.0.0:8080
http = 192.168.173.17:8181
http = 127.0.0.1:9090
master = true
http-to = /tmp/uwsgi.sock
以及,到多个节点的负载均衡:
[uwsgi]
http = 0.0.0.0:8080
http = 192.168.173.17:8181
http = 127.0.0.1:9090
master = true
http-to = /tmp/uwsgi.sock
http-to = 192.168.173.1:3031
http-to = 192.168.173.2:3031
http-to = 192.168.173.3:3031
- 如果你想大规模使用 (虚拟主机和零配置扩展),那么将HTTP路由器和 uWSGI订阅服务器 结合起来。
- 通过
http-var KEY=VALUE
选项,你可以让HTTP服务器传递自定义的uwsgi变量给worker。 - 你可以用
http-modifier1
选项来传递一个自定义的 modifier1 值给worker。
HTTPS支持¶
see HTTPS支持 (自1.3起)
HTTP Keep-Alive¶
如果后端设置了正确的HTTP头,那么你可以使用 http-keepalive
选项。要么后端将需要在每个响应中设置有效的 Content-Length
,要么你可以通过 http-auto-chunked
使用块编码。简单设置”Connection: close”并 不够 。还要记住在响应中设置”Connection: Keep-Alive”。你可以通过使用 add-header = Connection: Keep-Alive
选项来自动设置。
自uWSGI 2.1 (master分支)起,你可以使用 http11-socket
选项。 http11-socket
可替换 add-header
和 http-keepalive
选项 (但它并不像 so-keepalive
那样会接触到tcp相关的东东)。一旦设置了,如果遵循一堆规则,那么服务器将会试着保持连接打开。这并不是一个智能http 1.1解析器 (避免解析整个响应),但它假设开发者生成正确的头部。已添加 http11-socket
来支持用于视频流的RTSP协议。
HTTP自动gzip¶
使用 http-auto-gzip
选项,如果uWSGI-encoding头设置为gzip,并且未设置 Content-Length
和 Content-Encoding
,那么uWSGI可以自动的压缩(gzip)内容。
我可以在生产上使用uWSGI的HTTP功能吗?¶
如果你需要一个负载均衡器/代理,那么这会是一个非常棒的想法。它将会自动查找新的uWSGI实例,并且以多种方式进行负载均衡。如果你想将其当成一个真正的web服务器使用,那么你应该考虑到,在uWSGI实例中提供静态文件是可能的,但不如使用一个专用的全功能web服务器那么好用。如果你把静态资产托管到云或者CDN上,使用uWSGI的HTTP功能,你绝对可以避免配置一个完整的web服务器。
注解
如果你以HTTP模式,在uWSGI(使用HTTP模式)前端使用Amazon的ELB (弹性负载均衡器,Elastic Load Balancer),那么要么 必须设置 一个有效的 Content-Length
,要么必须使用区块编码,例如,使用
http-auto-chunked
。ELB的”健康测试”在HTTP模式下也许仍然会失败,在这种情况下,可以使用一个TCP健康测试来代替。
注解
特别是,默认情况下,Django后端并不设置 Content-Length
,而其他会。如果在ELB后运行,那么要么使用如上的块解码,要么通过”Conditional GET” Django中间件,强制Django指定 Content-Length
。
SPDY路由器 (uWSGI 1.9)¶
自uWSGI 1.9起,HTTPS路由器已被扩展,支持SPDY协议的版本3。
要运行带SPDY支持的HTTPS路由器,则使用 --https2
选项:
uwsgi --https2 addr=0.0.0.0:8443,cert=foobart.crt,key=foobar.key,spdy=1 --module werkzeug.testapp:test_app
这将会在端口8443上启动一个带有哦SPDY支持的HTTPS路由器,转发请求到实例正在运行的Werkzeug测试应用。如果你通过一个启用了SPDY的浏览器访问https://address:8443/,那么你会看到由 Werkzeug 报告的额外的WSGI变量:
SPDY
–on
SPDY.version
– 协议版本 (一般是3
)SPDY.stream
– 流标识符 (一个奇数)。
作为非root用户打开特权端口将需要使用 shared-socket 选项,以及一个稍微不同的语法:
uwsgi --shared-socket :443 --https2 addr==0,cert=foobart.crt,key=foobar.key,spdy=1 --module werkzeug.testapp:test_app --uid user
可以同时使用HTTP和HTTPS (=0 和 =1 是由 shared-socket 命令打开的特权端口的引用):
uwsgi --shared-socket :80 --shared-socket :443 --http =0 --https2 addr==1,cert=foobart.crt,key=foobar.key,spdy=1 --module werkzeug.testapp:test_app --uid user
注意事项¶
- 要使用SPDY,你需要至少OpenSSL 1.x (所有现代的Linux发行版都应该有它)。
- 在上传期间,窗口大小会被不断更新。
--http-timeout
指令被用来设置SPDY超时时间。这是在SPDY连接被关闭之后的最大数量的不活跃时间。- 来自浏览器的
PING
请求 全都 被确认。 - 连接时,SPDY路由器发送一个带最优值的设置包给客户端。
- 如果流以某种灾难性方式失败了,那么整个连接会被硬关闭。
- 总是遵循
RST
消息。
待办事项¶
- 添加旧的SPDY v2支持 (值得吗?)
- 允许推送来自uWSGI缓存的资源
- 允许调整内部缓冲
Lighttpd支持¶
注解
Lighttpd支持是实验性的。
用于Lighttpd的uwsgi处理器位于uWSGI发行版的 /lighttpd
目录下。
构建模块¶
首先下载lighttpd的源代码,然后解压缩它。然后拷贝uWSGI发行版中的
lighttpd/mod_uwsgi.c
文件到Lighttpd的
/src
目录下。添加以下内容到lighttpd src/Makefile.am
文件中的accesslog块之后:
lib_LTLIBRARIES += mod_uwsgi.la
mod_uwsgi_la_SOURCES = mod_uwsgi.c
mod_uwsgi_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
mod_uwsgi_la_LIBADD = $(common_libadd)
然后启动
autoreconf -fi
接着和往常一样,
./configure && make && make install
配置Lighttpd¶
修改你的配置文件:
server.modules = (
...
"mod_uwsgi",
...
)
# ...
uwsgi.server = (
"/pippo" => (( "host" => "192.168.173.15", "port" => 3033 )),
"/" => (( "host" => "127.0.0.1", "port" => 3031 )),
)
如果你在相同的虚拟路径/URI之下指定多个主机,那么负载均衡将会被激活,并且使用”Fair”算法。
附加uWSGI到Mongrel2¶
Mongrel2 是下下一代的web服务器,关注于现代web应用。
就像uWSGI,它完全语言无关,集群友好,并且富有争议 :)
它使用令人惊奇的 ZeroMQ 库来通信,允许可靠容易的消息队列,以及免配置的可扩展性。
从版本0.9.8-dev起,uWSGI可以被用作Mongrel2处理器。
要求¶
要在uWSGI中启用对ZeroMQ/Mongrel2的支持,你需要zeromq库 library (2.1+)和uuid库。
Mongrel2可以使用JSON或者tnetstring来将数据 (例如头部和各种其他信息) 传递给处理器。uWSGI默认支持tnetstring,但需要 Jansson 库来解析JSON数据。如果你没安装jansson,或者不想使用JSON,那么确保你在Mongrel2中的Handler部分指定了 protocol='tnetstring'
,因为默认是使用JSON的。这会导致uWSGI日志中一条相当模糊的“JSON支持未启用。跳过请求”。
配置Mongrel2¶
find你可以发现uWSGI源代码配备了 mongrel2-uwsgi.conf
。你可以以这个文件为基础来配置Mongrel2。
main = Server(
uuid="f400bf85-4538-4f7a-8908-67e313d515c2",
access_log="/logs/access.log",
error_log="/logs/error.log",
chroot="./",
default_host="192.168.173.11",
name="test",
pid_file="/run/mongrel2.pid",
port=6767,
hosts = [
Host(name="192.168.173.11", routes={
'/': Handler(send_spec='tcp://192.168.173.11:9999',
send_ident='54c6755b-9628-40a4-9a2d-cc82a816345e',
recv_spec='tcp://192.168.173.11:9998', recv_ident='',
protocol='tnetstring')
})
]
)
settings = {'upload.temp_store':'tmp/mongrel2.upload.XXXXXX'}
servers = [main]
这是一个相当标准的Mongrel2配置,启动了上传流。
为Mongrel2配置uWSGI¶
要将uWSGI附加到Mongrel2,只需简单实用 OptionZeromq 选项:
uwsgi --zeromq tcp://192.168.173.11:9999,tcp://192.168.173.11:9998
你可以生成多个进程 (每个都会订阅到Mongrel2,并使用一个不同的uuid)
uwsgi --zeromq tcp://192.168.173.11:9999,tcp://192.168.173.11:9998 -p 4
你也可以使用线程。每个线程将订阅Mongrel2队列,但是响应者socket将会由所有线程共享,并由互斥锁包含。
uwsgi --zeromq tcp://192.168.173.11:9999,tcp://192.168.173.11:9998 -p 4 --threads 8
# This will spawn 4 processes with 8 threads each, totaling 32 threads.
一起测试¶
添加一个应用到uWSGI (一如既往,我们将使用werkzeug.testapp)
uwsgi --zeromq tcp://192.168.173.11:9999,tcp://192.168.173.11:9998 -p 4 --threads 8 --module werkzeug.testapp:test_app
现在,在所有你想要的服务器上启动命令,Mongrel2将会自动分发请求给它们。
异步模式¶
警告
对ZeroMQ的异步支持仍然在开发中,因为ZeroMQ使用边沿触发事件,这些事件会复杂化uWSGI异步架构中的东东。
Chroot¶
默认情况下,Mongrel2会 chroot()
。这对于安全是件好事,但是对于文上传流会让人头疼。记住,Mongrel2将会在它自己的chroot jail中上传文件,因此,如果你的uWSGI实例并不处于同一个chroot jail中,那么你将必须小心选择路径。在这个例子中,我们的Mongrel2配置文件使用了一个相对路径来轻松让uWSGI访问文件。
性能¶
Mongrel2即使是在巨大负载的情况下也是非常快且可靠的。tnetstring和JSON是基于文本的 (因此,它们比二进制 uwsgi protocol 稍微低效率点。然而,因为Mongrel2不需要昂贵的一请求一连接方法,因此与(例如) Nginx + uWSGI 方法相比,你应该会得到几乎相同的(如果不是更高的)的结果。
uWSGI集群 + ZeroMQ¶
你可以轻松地将uWSGI clustering
和ZeroMQ混在一起。
选择主节点,然后运行
uwsgi --zeromq tcp://192.168.173.11:9999,tcp://192.168.173.11:9998 -p 4 --threads 8 --module werkzeug.testapp:test_app --cluster 225.1.1.1:1717
然后在所有其他节点上,简单运行
uwsgi --cluster 225.1.1.1:1717
用ZeroMQ混合标准socket¶
除了ZeroMQ,你还可以添加uwsgi/HTTP/FastCGI/... socket到你的uWSGI服务器中,但如果你这样做,记得禁用线程!这个限制未来可能会被修复。
通过ZeroMQ进行日志记录¶
参见
ZeroMQLogging
Nginx支持¶
自0.8.40版本起,Nginx本身就包含了对使用 uwsgi protocol 的上游服务器的的支持。
配置Nginx¶
一般来说,你只需包含uwsgi_params文件 (包含在nginx发行版本中),使用uwsgi_pass指令来设置uWSGI socket的地址。
uwsgi_pass unix:///tmp/uwsgi.sock;
include uwsgi_params;
—— 或者如果你使用的是TCP socket,
uwsgi_pass 127.0.0.1:3031;
include uwsgi_params;
然后,只需重载Nginx,你就准备好了经过Nginx的,由uWSGI驱动的应用。
uwsgi_params
文件是啥?
它就是为了方便,仅此而已!为了让你阅读愉快,自uWSGI 1.3起,该文件的内容是:
uwsgi_param QUERY_STRING $query_string;
uwsgi_param REQUEST_METHOD $request_method;
uwsgi_param CONTENT_TYPE $content_type;
uwsgi_param CONTENT_LENGTH $content_length;
uwsgi_param REQUEST_URI $request_uri;
uwsgi_param PATH_INFO $document_uri;
uwsgi_param DOCUMENT_ROOT $document_root;
uwsgi_param SERVER_PROTOCOL $server_protocol;
uwsgi_param REMOTE_ADDR $remote_addr;
uwsgi_param REMOTE_PORT $remote_port;
uwsgi_param SERVER_ADDR $server_addr;
uwsgi_param SERVER_PORT $server_port;
uwsgi_param SERVER_NAME $server_name;
参见
集群¶
对于所有的上游处理程序,Nginx支持漂亮的集群集成。
添加一个 upstream 指令到server配置块外:
upstream uwsgicluster {
server unix:///tmp/uwsgi.sock;
server 192.168.1.235:3031;
server 10.0.0.17:3017;
}
然后修改你的uwsgi_pass指令:
uwsgi_pass uwsgicluster;
这样,你的请求将会在配置的uWSGI服务器之间进行均衡。
动态应用¶
当传递特殊变量的使用,uWSGI服务器可以按需加载应用。
可以在不传递任何应用配置的情况下启动uWSGI:
./uwsgi -s /tmp/uwsgi.sock
如果请求设置了 UWSGI_SCRIPT
变量,那么服务器将会加载指定的模块:
location / {
root html;
uwsgi_pass uwsgicluster;
uwsgi_param UWSGI_SCRIPT testapp;
include uwsgi_params;
}
你甚至还可以在每个location内配置多个应用:
location / {
root html;
uwsgi_pass uwsgicluster;
uwsgi_param UWSGI_SCRIPT testapp;
include uwsgi_params;
}
location /django {
uwsgi_pass uwsgicluster;
include uwsgi_params;
uwsgi_param UWSGI_SCRIPT django_wsgi;
}
在同一个进程中托管多个应用 (亦称管理SCRIPT_NAME和PATH_INFO)¶
WSGI标准决定了 SCRIPT_NAME
是一个用来选择特定应用的变量。不幸的是,
nginx不能够根据SCRIPT_NAME重写PATH_INFO。出于这样的原因,你需要指示uWSGI在所谓的“挂载点”中映射特定的应用,并且自动重写SCRIPT_NAME和PATH_INFO:
[uwsgi]
socket = 127.0.0.1:3031
; mount apps
mount = /app1=app1.py
mount = /app2=app2.py
; rewrite SCRIPT_NAME and PATH_INFO accordingly
manage-script-name = true
考虑到应用本身 (最终使用WSGI/Rack/PSGI中间件) 可以重写SCRIPT_NAME和PATH_INFO。
你也可以使用内部路由子系统来重写请求变量。特别是对于动态应用,这会是一种不错的方法。
注意:古老的uWSGI版本习惯支持所谓的”uwsgi_modifier1 30”方法。不要这样做。它实际上是一种丑陋的hack
SCRIPT_NAME是一个方便的惯例,但是允许你使用任何“映射方法”,例如,可以使用UWSGI_APPID变量在挂载点表中设置一个键。
[uwsgi]
socket = 127.0.0.1:3031
; mount apps
mount = the_app1=app1.py
mount = the_app2=app2.py
还记得吗,你可以使用nginx变量作为变量值,因此你可以使用Host头来实行某种形式的应用路由:
现在,只需在uWSGI挂载你的应用,将域名作为挂载键
[uwsgi]
socket = 127.0.0.1:3031
; mount apps
mount = example.com=app1.py
mount = foobar.it=app2.py
静态文件¶
为了最佳性能和安全性,记得配置Nginx来提供静态文件服务,而不是让你可怜的应用自己来处理它。
uWSGI服务器可以完美提供静态文件服务,但是并不如一个专用的web服务器,例如Nginx,那么快速有效。
例如,可以像这样映射Django的 /media
路径:
location /media {
alias /var/lib/python-support/python2.6/django/contrib/admin/media;
}
只有在请求的文件名不存在的时候,一些应用需要传递控制权给UWSGI服务器:
if (!-f $request_filename) {
uwsgi_pass uwsgicluster;
}
WARNING
如果使用不当,那么像这样的配置可能会引发安全性问题。理智考虑,请务必三番五次检查你的应用文件、配置文件和其他敏感文件位于静态文件的根目录之外。
虚拟主机¶
你可以使用Nginx虚拟主机,这并无什么特殊问题。
如果你运行“不可信的”web应用 (如果你恰巧是ISP,那么就如你那些客户一样),那么你应该限制它们的内存/地址空间使用,并且对每个主机/应用使用不同的 uid
server {
listen 80;
server_name customersite1.com;
access_log /var/log/customersite1/access_log;
location / {
root /var/www/customersite1;
uwsgi_pass 127.0.0.1:3031;
include uwsgi_params;
}
}
server {
listen 80;
server_name customersite2.it;
access_log /var/log/customersite2/access_log;
location / {
root /var/www/customersite2;
uwsgi_pass 127.0.0.1:3032;
include uwsgi_params;
}
}
server {
listen 80;
server_name sivusto3.fi;
access_log /var/log/customersite3/access_log;
location / {
root /var/www/customersite3;
uwsgi_pass 127.0.0.1:3033;
include uwsgi_params;
}
}
现在,可以在为每个socket使用不同的uid和受限(如果你想的话)地址空间来运行客户应用 (使用你选择的进程管理器,例如 rc.local, 通过Upstart运行uWSGI, Supervisord 或者任何激起你想象的工具)
uwsgi --uid 1001 -w customer1app --limit-as 128 -p 3 -M -s 127.0.0.1:3031
uwsgi --uid 1002 -w customer2app --limit-as 128 -p 3 -M -s 127.0.0.1:3032
uwsgi --uid 1003 -w django3app --limit-as 96 -p 6 -M -s 127.0.0.1:3033
将OpenBSD httpd作为代理使用¶
从版本5.7起,OpenBSD就包含了一个带有FastCGI支持的最小化(真正最小化)的web服务器
(http://www.openbsd.org/cgi-bin/man.cgi/OpenBSD-current/man8/httpd.8?query=httpd&sec=8)
启用它的第一步是编写它的配置文件 `/etc/httpd.conf`
server "default" {
listen on 0.0.0.0 port 80
fastcgi socket ":3031"
}
然后通过 `rcctl`
工具来启用并启动它:
rcctl enable httpd
rcctl start httpd
这个最小化配置将会在端口80上生成一个chroot的web服务器,作为用户’www’运行,并且使用FastCGI协议将每个请求转发到地址127.0.0.1:3031上。
现在,你只需在FastCGI地址上生成uWSGI:
[uwsgi]
fastcgi-socket = 127.0.0.1:3031
; a simple python app (eventually remember to load the python plugin)
wsgi-file = app.py
显然,你可以将uWSGI作为一个全功能的CGI服务器使用 (当然,实际上,它比现有的任何一个cgi服务器都具有更多的特性 :P),仅需记住强制modifier1为‘9’:
[uwsgi]
fastcgi-socket = 127.0.0.1:3031
fastcgi-modifier1 = 9
; a simple cgi-bin directory (eventually remember to load the cgi plugin)
cgi = /var/www/cgi-bin
现在,你可以将你的cgi脚本放到/var/www/cgi-bin下了 (记得给它们可执行权限)
你也可以使用UNIX域socket,仅需记住,httpd服务器运行的根目录是/var/www,因此你必须在其下的一个目录中绑定uWSGI socket:
[uwsgi]
fastcgi-socket = /var/www/run/uwsgi.socket
fastcgi-modifier1 = 9
; a simple cgi-bin directory
cgi = /var/www/cgi-bin
server "default" {
listen on 0.0.0.0 port 80
fastcgi socket "/run/uwsgi.socket"
}
如果你想只转发指定的路径到uWSGI,那么可以使用一个location指令:
server "default" {
listen on 0.0.0.0 port 80
location "/foo/*" {
fastcgi socket ":3031"
}
location "/cgi-bin/*" {
fastcgi socket ":3032"
}
}
注意¶
目前 (2015年五月),httpd仅能连接到unix域socket和绑定到地址127.0.0.1上的tcp fastcgi socket
语言支持¶
Python支持¶
uwsgi Python模块¶
uWSGI服务器自动添加一个 uwsgi
模块到你的Python应用中。
这对于配置uWSGI服务器是有用的,使用它的内部函数,并获取分析数据(以及检测你是否真的在uWSGI下运行)。
注解
许多这些函数当前不幸未公开。
模块级别的全局¶
-
uwsgi.
numproc
¶ 当前运行的进程/worker数。
-
uwsgi.
buffer_size
¶ 当前配置的缓冲区大小,以字节为单位。
-
uwsgi.
started_on
(int)¶ uWSGI启动的Unix时间戳。
-
uwsgi.
fastfuncs
¶ 这是一个用来定义
FastFuncs
的字典。
-
uwsgi.
applist
¶ 这是当前配置的应用列表。
-
uwsgi.
message_manager_marshal
¶ 当uWSGI服务器接收到一个已编组消息( marshalled message)时运行的回调。
-
uwsgi.
magic_table
¶ 配置占位符的魔术表。
-
uwsgi.
opt
¶ 当前配置选项,包括任何自定义占位符。
缓存函数¶
-
uwsgi.
cache_get
(key[, cache_name])¶ 从缓存中获取一个值。
参数: - key – 要读取的缓存键。
- cache_name – 多缓存模式中的缓存名字 (可以是name@address这样的格式)。可选。
-
uwsgi.
cache_set
(key, value[, expire, cache_name])¶ 设置缓存中的一个值。
参数: - key – 要写的缓存键。
- value – 要写的缓存值。
- expire – 值的失效时间,以秒为单位。
- cache_name – 多缓存模式中的缓存名字 (可以是name@address这样的格式)。可选。
-
uwsgi.
cache_update
(key, value[, expire, cache_server])¶
-
uwsgi.
cache_del
(key[, cache_name])¶ 从缓存中删除给定的缓存值。
参数: - key – 要删除的缓存键
- cache_name – 多缓存模式中的缓存名字 (可以是name@address这样的格式)。可选。
-
uwsgi.
cache_exists
(key[, cache_name])¶ 快速检查缓存中是否有与给定键关联的值。
参数: - key – 要坚持的缓存键
- cache_name – 多缓存模式中的缓存名字 (可以是name@address这样的格式)。可选。
-
uwsgi.
cache_clear
()¶
队列函数¶
-
uwsgi.
queue_get
()¶
-
uwsgi.
queue_set
()¶
-
uwsgi.
queue_last
()¶
-
uwsgi.
queue_push
()¶
-
uwsgi.
queue_pull
()¶
-
uwsgi.
queue_pop
()¶
-
uwsgi.
queue_slot
()¶
-
uwsgi.
queue_pull_slot
()¶
SNMP函数¶
-
uwsgi.
snmp_set_community
(str)¶ 参数: str – 包含新的community值的字符串。 设置SNMP community字符串。
-
uwsgi.
snmp_set_counter32
(oidnum, value)¶
-
uwsgi.
snmp_set_counter64
(oidnum, value)¶
-
uwsgi.
snmp_set_gauge
(oidnum, value)¶ 参数: - oidnum – 一个包含oid数字目标的整数
- value – 一个包含计数器或测量新值的整数。
设置计数器或测量为一个指定的值。
-
uwsgi.
snmp_incr_counter32
(oidnum, value)¶
-
uwsgi.
snmp_incr_counter64
(oidnum, value)¶
-
uwsgi.
snmp_incr_gauge
(oidnum, value)¶
-
uwsgi.
snmp_decr_counter32
(oidnum, value)¶
-
uwsgi.
snmp_decr_counter64
(oidnum, value)¶
-
uwsgi.
snmp_decr_gauge
(oidnum, value)¶ 参数: - oidnum – 一个包含oid数字目标的整数。
- value – 一个包含增加/减少计数器或测量的量整数值。如果未指定,默认为1.
增加或减少计数器或测量一个特定的量。
注解
uWSGI OID树从1.3.6.1.4.1.35156.17开始
spooler函数¶
-
uwsgi.
send_to_spooler
(message_dict=None, spooler=None, priority=None, at=None, body=None, **kwargs)¶ 参数: - message_dict – 发送到spool的消息 (字符串键,字符串值)。要么这样,要么设置**kwargs。
- spooler – 要使用的spooler (id或者目录)
- priority – 消息的优先级。越大越不重要。
- at – 应该处理这条信息的最小的UNIX时间戳。
- body – 除了消息字典本身,添加到消息的一个二进制 (字节字符串)体。它的值可通过消息中的
body
键访问。
发送数据到 uWSGI Spooler. 又名 spool().
注解
所有的关键字参数也可以在消息字典中传递。这意味着它们是保留字,嗯,某种程度上……
-
uwsgi.
set_spooler_frequency
(seconds)¶ 设置spooler运行的频率。
-
uwsgi.
spooler_jobs
()¶
-
uwsgi.
spooler_pid
()¶
-
uwsgi.
spooler_get_task
(path)¶ 参数: path – 读取任务的相对/绝对路径
高级方法¶
-
uwsgi.
send_message
()¶ 使用 uwsgi协议 发送一般消息。
注解
这个函数被称为
send_uwsgi_message()
,直到版本 2f970ce58543278c851ff30e52758fd6d6e69fdc.
-
uwsgi.
route
()¶
-
uwsgi.
send_multi_message
()¶ 使用 uwsgi协议 发送一条一般消息到多个接收者
注解
这个函数被称为
send_multi_uwsgi_message()
,直到 2f970ce58543278c851ff30e52758fd6d6e69fdc 版本。参见
查看
Clustering
获得更多例子
-
uwsgi.
accepting
(accepting=True)¶ 设置当前worker的接受标志的值。在同时使用 `Overriding Workers`_ 和加载时建立链的时候必须。
参见
-
uwsgi.
reload
()¶ 优雅地重载uWSGI服务器栈。
参见
Reload
-
uwsgi.
stop
()¶
-
uwsgi.
workers
() → dict¶ 为当前服务器获取所有worker的统计数据字典。返回一个字典。
-
uwsgi.
masterpid
() → int¶ 返回uWSGI master进程的进程标识符 (PID)。
-
uwsgi.
total_requests
() → int¶ 返回的至今由uWSGI worker池管理的请求总数。
-
uwsgi.
get_option
()¶ 也可作为 getoption() 使用。
-
uwsgi.
set_option
()¶ 也可作为 setoption() 使用。
-
uwsgi.
sorry_i_need_to_block
()¶
-
uwsgi.
request_id
()¶
-
uwsgi.
worker_id
()¶
-
uwsgi.
mule_id
()¶
-
uwsgi.
log
()¶
-
uwsgi.
log_this_request
()¶
-
uwsgi.
set_logvar
()¶
-
uwsgi.
get_logvar
()¶
-
uwsgi.
disconnect
()¶
-
uwsgi.
grunt
()¶
-
uwsgi.
lock
(locknum=0)¶ 参数: locknum – 要锁的锁号。Lock 0总是可用的。
-
uwsgi.
is_locked
()¶
-
uwsgi.
unlock
(locknum=0)¶ 参数: locknum – 要解锁的锁号。Lock 0总是可用的。
-
uwsgi.
cl
()¶
-
uwsgi.
setprocname
()¶
-
uwsgi.
listen_queue
()¶
-
uwsgi.
register_signal
(num, who, function)¶ :param num:配置的信号数 :param who: 一个魔法字符串,会设置哪个/些进程接收该信号。
worker
/worker0
将发送信号给第一个可用worker。如果你指定过一个空字符串,那么这是默认值。workers
会发送信号给每个worker。workerN
(N > 0) 会发送信号给worker N。mule
/mule0
会发送信号给第一个可用mule。 (见 uWSGI Mule)mules
会发送信号给所有mule。muleN
(N > 0) 会发送信号给mule N。cluster
会发送信号给集群中的所有节点。警告:未实现。subscribed
会发送信号给所有订阅节点。警告:未实现。spooler
会发送信号给spooler。
cluster
和subscribed
特殊,因为它们会将信号发送给所有集群/订购节点的master。其他节点将不得不定义一个本地处理器,以避免可怕的信号风暴循环。参数: function – 一个回调,接收一个数字参数。
-
uwsgi.
signal
(num)¶ 参数: num – 引发的信号
-
uwsgi.
signal_wait
([signum])¶ 阻塞进程/线程/异步核心直到接收到了一个信号。使用
signal_received
来获取接收到的信号值。 如果已经为该信号注册了一个处理函数,那么signal_wait
将被终端,而实际的处理函数将处理该信号。参数: signum – 可选 - 要等待的信号
-
uwsgi.
signal_registered
()¶
-
uwsgi.
signal_received
()¶ 获取最后收到的信号值。结合
signal_wait
使用。
-
uwsgi.
add_file_monitor
()¶
-
uwsgi.
add_timer
(signum, seconds)¶ 参数: - signum – 引发的信号值。
- seconds – 引发信号的时间间隔。
-
uwsgi.
add_probe
()¶
-
uwsgi.
add_rb_timer
(signum, seconds[, iterations=0])¶ Add an user-space (red-black tree backed) timer.
参数: - signum – 引发的信号值。
- seconds – 引发信号的时间间隔。
- iterations – 引发信号的次数。0 (默认) 表示无数次。
-
uwsgi.
add_cron
(signal, minute, hour, day, month, weekday)¶ 对于时间参数,你可以使用语法
-n
来表示“每n”。例如,hour=-2
将声明会每隔一个小时发送一次信号。参数: - signal – 引发的信号值。
- minute – 运行该事件的分钟。
- hour – 运行该事件的小时。
- day – 运行该事件的天。这会和
weekday
进行或运算。 - month – 运行该事件的月。
- weekday – 运行该事件的工作日。这会与
day
进行或运算。 (根据POSIX标准,0是星期天,6是星期一)
-
uwsgi.
register_rpc
()¶
-
uwsgi.
rpc
()¶
-
uwsgi.
rpc_list
()¶
-
uwsgi.
call
()¶
-
uwsgi.
sendfile
()¶
-
uwsgi.
set_warning_message
()¶
-
uwsgi.
mem
()¶
-
uwsgi.
has_hook
()¶
-
uwsgi.
logsize
()¶
-
uwsgi.
send_multicast_message
()¶
-
uwsgi.
cluster_nodes
()¶
-
uwsgi.
cluster_node_name
()¶
-
uwsgi.
cluster
()¶
-
uwsgi.
cluster_best_node
()¶
-
uwsgi.
connect
()¶
-
uwsgi.
connection_fd
()¶
-
uwsgi.
is_connected
()¶
-
uwsgi.
send
()¶
-
uwsgi.
recv
()¶
-
uwsgi.
recv_block
()¶
-
uwsgi.
recv_frame
()¶
-
uwsgi.
close
()¶
-
uwsgi.
i_am_the_spooler
()¶
-
uwsgi.
fcgi
()¶
-
uwsgi.
parsefile
()¶
-
uwsgi.
embedded_data
(symbol_name)¶ 参数: string – 要提取的符号名。 从uWSGI二进制镜像中提取一个符号。
-
uwsgi.
extract
()¶
-
uwsgi.
mule_msg
(string[, id])¶ 参数: - string – 要发送的字节字符串消息。
- id – 可选 - 接收该消息的mule ID。如果你不指定一个ID,那么这条消息就会被发送到第一个可用的编程mule。
发送一条消息给一个mule。
-
uwsgi.
farm_msg
()¶
-
uwsgi.
mule_get_msg
()¶ 返回: 一旦接收到一条mule消息,则返回。 阻塞,直到接收到了一条Mule消息,并返回这条消息。可以在同一个编程mule中从多个线程中调用该函数。
-
uwsgi.
farm_get_msg
()¶
-
uwsgi.
in_farm
()¶
-
uwsgi.
ready
()¶
-
uwsgi.
set_user_harakiri
()¶
异步函数¶
-
uwsgi.
async_sleep
(seconds)¶ 暂停处理当前请求
seconds
秒,并将控制权传给下一个异步核心。参数: seconds – 休眠时间,以秒为单位。
-
uwsgi.
async_connect
()¶
-
uwsgi.
async_send_message
()¶
-
uwsgi.
green_schedule
()¶
-
uwsgi.
suspend
()¶ 挂起处理当前请求,并将控制权传给下一个要求控制权的异步核心。
-
uwsgi.
wait_fd_read
(fd[, timeout])¶ 挂起处理当前请求,直到文件描述符
fd
上有可读数据。在挂起以添加更多文件描述符到被监控的集合中之前可能会被多次调用。参数: - fd – 文件描述符号。
- timeout – 可选的超时 (如果省略,则是无限).
-
uwsgi.
wait_fd_write
(fd[, timeout])¶ 挂起处理当前请求,直到文件描述符
fd
上没有更多可写的内容。 在添加更多文件描述符到被监控的集合中之前可能会被多次调用。参数: - fd – 文件描述符号。
- timeout – 可选的超时 (如果省略,则是无限).
uWSGI API - Python装饰器¶
The uWSGI API 非常底层,因为它必须是语言无关的。
也就是说,对于许多语言,例如Python,太底层并不是一件好事。
以吾之愚见,装饰器是Python较为牛逼的特性之一,因此,在uWSG源代码树中,你会发现有个模块导出了一堆的装饰器,它们涵盖了很大一部分uWSGI API。
小抄¶
基于信号量的装饰器在第一个可用worker中执行信号处理器。如果你已经启动了spooler,那么你可以在其中执行信号处理器,让worker不用管它,只要管理正常的请求即可。简单传递 target='spooler'
给装饰器。
@timer(3, target='spooler')
def hello(signum):
print("hello")
例子:一个Django会话清理器和视频解码器¶
让我们定义一个 task.py
模块,然后将其放进Django项目目录中。
from uwsgidecorators import *
from django.contrib.sessions.models import Session
import os
@cron(40, 2, -1, -1, -1)
def clear_django_session(num):
print("it's 2:40 in the morning: clearing django sessions")
Session.objects.all().delete()
@spool
def encode_video(arguments):
os.system("ffmpeg -i \"%s\" image%%d.jpg" % arguments['filename'])
这个会话清理器在每天的2:40会执行一次,要排队一个视频编码器,我们只需简单地在某个地方spool一下。
from task import encode_video
def index(request):
# launching video encoding
encode_video.spool(filename=request.POST['video_filename'])
return render_to_response('enqueued.html')
现在,启用spooler,运行uWSGI:
[uwsgi]
; a couple of placeholder
django_projects_dir = /var/www/apps
my_project = foobar
; chdir to app project dir and set pythonpath
chdir = %(django_projects_dir)/%(my_project)
pythonpath = %(django_projects_dir)
; load django
module = django.core.handlers:WSGIHandler()
env = DJANGO_SETTINGS_MODULE=%(my_project).settings
; enable master
master = true
; 4 processes should be enough
processes = 4
; enable the spooler (the mytasks dir must exist!)
spooler = %(chdir)/mytasks
; load the task.py module
import = task
; bind on a tcp socket
socket = 127.0.0.1:3031
唯一一个特别重要的选项是 import
这个。它的工作方式与 module
相同,但跳过了WSGI的可调用搜索。你可以用它在加载WSGI应用之前预加载模块。你可以指定无限数目的 ‘’‘import’‘’ 指令。
例子:web2py + spooler + timer¶
首先,定义你的spooler和timer函数 (我们将称其为:file:mytasks.py
)
from uwsgidecorators import *
@spool
def a_long_task(args):
print(args)
@spool
def a_longer_task(args)
print("longer.....")
@timer(3)
def three_seconds(signum):
print("3 seconds elapsed")
@timer(10, target='spooler')
def ten_seconds_in_the_spooler(signum):
print("10 seconds elapsed in the spooler")
现在,运行web2py。
uwsgi --socket :3031 --spooler myspool --master --processes 4 --import mytasks --module web2py.wsgihandler
一旦加载了应用,你就会在日志中看到两个运行着的timer。
现在,我们想要从我们的web2py控制器排队任务。
编辑它们其中一个,然后添加
import mytasks # be sure mytasks is importable!
def index(): # this is a web2py action
mytasks.a_long_task.spool(foo='bar')
return "Task enqueued"
uwsgidecorators API参考¶
-
uwsgidecorators.
postfork
(func)¶ uWSGI是一个预启动 (或者说是”尽情使用fork”)的服务器,因此,你可能需要在每次
fork()
之后执行一个修正任务。这就是postfork
装饰器的用武之处。你可以声明多个postfork
任务。每个被装饰器装饰的函数将在每个fork()
之后依次执行。@postfork def reconnect_to_db(): myfoodb.connect() @postfork def hello_world(): print("Hello World")
-
uwsgidecorators.
spool
(func)¶ uWSGI的 spooler 是非常有用的。与Celery或其他队列相比,它非常“原始”。
spool
装饰器会帮到你!@spool def a_long_long_task(arguments): print(arguments) for i in xrange(0, 10000000): time.sleep(0.1) @spool def a_longer_task(args): print(args) for i in xrange(0, 10000000): time.sleep(0.5) # enqueue the tasks a_long_long_task.spool(foo='bar',hello='world') a_longer_task.spool({'pippo':'pluto'})
上面的函数将会自动返回
uwsgi.SPOOL_OK
,因此,根据其返回状态,它们将独立执行一次。
-
uwsgidecorators.
spoolforever
(func)¶ 当你想要持续的执行一个spool任务时,使用
spoolforever
。一个@spoolforever
任务将总是返回uwsgi.SPOOL_RETRY
。@spoolforever def a_longer_task(args): print(args) for i in xrange(0, 10000000): time.sleep(0.5) # enqueue the task a_longer_task.spool({'pippo':'pluto'})
-
uwsgidecorators.
spoolraw
(func)¶ 高级用户也许想要控制一个任务的返回值。
@spoolraw def a_controlled_task(args): if args['foo'] == 'bar': return uwsgi.SPOOL_OK return uwsgi.SPOOL_RETRY a_controlled_task.spool(foo='bar')
-
uwsgidecorators.
rpc
("name", func)¶ uWSGI的 uWSGI RPC栈 是远程调用uWSGI实例中托管的应用中的函数最快的方式。使用@rpc装饰器,你可以容易地定义导出函数。
@rpc('helloworld') def ciao_mondo_function(): return "Hello World"
-
uwsgidecorators.
signal
(num)(func)¶ 你可以轻松地为 信号框架 注册信号。
@signal(17) def my_signal(num): print("i am signal %d" % num)
-
uwsgidecorators.
timer
(interval, func)¶ 定期执行一个函数。
@timer(3) def three_seconds(num): print("3 seconds elapsed")
-
uwsgidecorators.
rbtimer
(interval, func)¶ 像@timer,但是使用红黑定时器。
-
uwsgidecorators.
cron
(min, hour, day, mon, wday, func)¶ 为
CronInterface
轻松注册函数。@cron(59, 3, -1, -1, -1) def execute_me_at_three_and_fiftynine(num): print("it's 3:59 in the morning")
从1.2起,支持一种新的语法来模拟类
crontab
间隔 (每个第N分钟,等等。)。在uWSGI中,可以像这样指定*/5 * * * *
:@cron(-5, -1, -1, -1, -1) def execute_me_every_five_min(num): print("5 minutes, what a long time!")
-
uwsgidecorators.
filemon
(path, func)¶ 每次一个文件/目录被修改的适合,执行一个函数。
@filemon("/tmp") def tmp_has_been_modified(num): print("/tmp directory has been modified. Great magic is afoot")
-
uwsgidecorators.
erlang
(process_name, func)¶ 将一个函数映射为一个 Erlang 进程。
@erlang('foobar') def hello(): return "Hello"
-
uwsgidecorators.
thread
(func)¶ 标记函数在一个单独的线程中执行。
@thread def a_running_thread(): while True: time.sleep(2) print("i am a no-args thread") @thread def a_running_thread_with_args(who): while True: time.sleep(2) print("Hello %s (from arged-thread)" % who) a_running_thread() a_running_thread_with_args("uWSGI")
你也可以将
@thread
和@postfork
结合在一起,从而在一个新生成的worker中的一个新线程里生成postfork处理器。@postfork @thread def a_post_fork_thread(): while True: time.sleep(3) print("Hello from a thread in worker %d" % uwsgi.worker_id())
-
uwsgidecorators.
lock
(func)¶ 这个装饰器将会在一个完全锁定的环境中执行一个函数,从而阻止其他worker或者线程(或者是master,如果你够蠢或者够勇敢的话)同时运行它。显然,这也可以跟@postfork组合在一起。
@lock def dangerous_op(): print("Concurrency is for fools!")
-
uwsgidecorators.
mulefunc
([mulespec, ]func)¶ 卸载函数的执行到 mule .当卸载函数被调用,它将会立即返回,而执行将会被委托给一个mule。
@mulefunc def i_am_an_offloaded_function(argument1, argument2): print argument1,argument2
你也可以指定一个mule ID或者mule farm来运行该函数。请务必记住用一个uwsgi import配置选项来注册你的函数。
@mulefunc(3) def on_three(): print "I'm running on mule 3." @mulefunc('old_mcdonalds_farm') def on_mcd(): print "I'm running on a mule on Old McDonalds' farm."
-
uwsgidecorators.
harakiri
(time, func)¶ 从uWSGI 1.3-dev开始,添加了一个可定制的二次 harakiri 子系统。如果一个给定的调用执行时间太长,那么你可以使用这个装饰器去灭掉一个worker。
@harakiri(10) def slow_function(foo, bar): for i in range(0, 10000): for y in range(0, 10000): pass # or the alternative lower level api uwsgi.set_user_harakiri(30) # you have 30 seconds. fight! slow_func() uwsgi.set_user_harakiri(0) # clear the timer, all is well
Pump支持¶
注解
Pump并不是PEP,也不是标准。
Pump 是一个新项目,旨在“更好的”WSGI。
为你方便起见,一个样例Pump应用:
def app(req):
return {
"status": 200,
"headers": {"content_type": "text/html"},
"body": "<h1>Hello!</h1>"
}
要加载一个Pump应用,只需使用 pump
选项来声明回调。
uwsgi --http-socket :8080 -M -p 4 --pump myapp:app
myapp
是模块的名字(必须是可导入的!),而app是回调。回调部分是可选的,默认情况下,uWSGI将会搜索一个名为’application’的回调。
Python Tracebacker¶
1.3-dev 新版功能.
通常,如果你想要获取你的app的实时回溯,那么你必须修改你的代码,为其添加一个hook或者入口,正如:doc:`TipsAndTricks`页面上描述的。
从1.3-dev开始,uWSGI包括了一个类似的技术,允许你通过一个UNIX socket获取实时回溯。
要启用这个回溯器,你需要添加选项``py-tracebacker=<socket>``,,其中,``<socket>``是已创建UNIX socket的_basename_。
如果你有4个uWSGI worker,并且添加了``py-tracebacker=/tmp/tbsocket``,那么将会创建名字从``/tmp/tbsocket1``到``/tmp/tbsocket4``的4个socket。
连接到其中任意一个都将会返回worker中运行的线程的当前回溯。你可以使用你最喜欢的应用或方法来连接到那些socket,但是uWSGI有一个供你使用的方便的选项``connect-and-read``:
uwsgi –connect-and-read /tmp/tbsocket1
一个例子¶
让我们写一个名为``slow.py``的蠢蠢的测试应用:
import time
def dormi():
time.sleep(60)
def dormi2():
dormi()
def dormi3():
dormi2()
def dormi4():
dormi3()
def dormi5():
dormi4()
def application(e, start_response):
start_response('200 OK', [('Content-Type', 'text/html')])
dormi5()
return "hello"
然后运行它:
uwsgi --http :8080 -w slow --master --processes 2 --threads 4 --py-tracebacker /tmp/tbsocket.
然后创建一堆到它的请求:
curl http://localhost:8080 &
curl http://localhost:8080 &
curl http://localhost:8080 &
curl http://localhost:8080 &
现在,当这些请求运行的时候 (每个都会花费几乎一分钟来完成),你就可以检索回溯,比方说,前两个worker:
./uwsgi --connect-and-read /tmp/tbsocket.1
./uwsgi --connect-and-read /tmp/tbsocket.2
回溯器的输出将会是这样的:
*** uWSGI Python tracebacker output ***
thread_id = uWSGIWorker1Core1 filename = ./slow.py lineno = 22 function = application line = dormi5()
thread_id = uWSGIWorker1Core1 filename = ./slow.py lineno = 14 function = dormi5 line = def dormi5(): dormi4()
thread_id = uWSGIWorker1Core1 filename = ./slow.py lineno = 13 function = dormi4 line = def dormi4(): dormi3()
thread_id = uWSGIWorker1Core1 filename = ./slow.py lineno = 12 function = dormi3 line = def dormi3(): dormi2()
thread_id = uWSGIWorker1Core1 filename = ./slow.py lineno = 11 function = dormi2 line = def dormi2(): dormi()
thread_id = uWSGIWorker1Core1 filename = ./slow.py lineno = 9 function = dormi line = time.sleep(60)
thread_id = uWSGIWorker1Core3 filename = ./slow.py lineno = 22 function = application line = dormi5()
thread_id = uWSGIWorker1Core3 filename = ./slow.py lineno = 14 function = dormi5 line = def dormi5(): dormi4()
thread_id = uWSGIWorker1Core3 filename = ./slow.py lineno = 13 function = dormi4 line = def dormi4(): dormi3()
thread_id = uWSGIWorker1Core3 filename = ./slow.py lineno = 12 function = dormi3 line = def dormi3(): dormi2()
thread_id = uWSGIWorker1Core3 filename = ./slow.py lineno = 11 function = dormi2 line = def dormi2(): dormi()
thread_id = uWSGIWorker1Core3 filename = ./slow.py lineno = 9 function = dormi line = time.sleep(60)
thread_id = MainThread filename = ./slow.py lineno = 22 function = application line = dormi5()
thread_id = MainThread filename = ./slow.py lineno = 14 function = dormi5 line = def dormi5(): dormi4()
thread_id = MainThread filename = ./slow.py lineno = 13 function = dormi4 line = def dormi4(): dormi3()
thread_id = MainThread filename = ./slow.py lineno = 12 function = dormi3 line = def dormi3(): dormi2()
thread_id = MainThread filename = ./slow.py lineno = 11 function = dormi2 line = def dormi2(): dormi()
thread_id = MainThread filename = ./slow.py lineno = 9 function = dormi line = time.sleep(60)
别名化Python模块¶
拥有一个Python包/模块/文件的多个版本是非常常见的。
操作PYTHONPATH,或者使用virtualenv是在无需修改代码的情况下使用不同版本的一种方式。
但是亲爱的,为什么不使用一个别名系统,以让你随心所欲的将模块名映射到文件呢?这就是为嘛我们有 pymodule-alias
选项的原因了!
案例1 - 映射一个简单的文件到一个虚拟模块¶
假设 swissknife.py
包含了许多有用的类和函数。
在你的应用中成千上万的地方都对其进行了导入。现在,我们想要修改它,但是出于任何原因,我们想要保留原文件完好,称其为 swissknife_mk2
。
你的选择是:
- 修改你所有的代码来导入和使用swissknife_mk2,而不是swissknife。是啊,不,这不会发生。
- 修改你所有文件的第一行:
import swissknife_mk2 as swissknife
。好多了,但是你写软件是为了赚钱的……而时间就是金钱,所以不特么用些更强大的东西呢?
所以,不要碰你的文件 —— 只需重新映射!
./uwsgi -s :3031 -w myproject --pymodule-alias swissknife=swissknife_mk2
# Kapow! uWSGI one-two ninja punch right there!
# You can put the module wherever you like, too:
./uwsgi -s :3031 -w myproject --pymodule-alias swissknife=/mnt/floppy/KNIFEFAC/SWISSK~1.PY
# Or hey, why not use HTTP?
./uwsgi -s :3031 -w myproject --pymodule-alias swissknife=http://uwsgi.it/modules/swissknife_extreme.py
你可以指定多个 pymodule-alias
指令。
uwsgi:
socket: :3031
module: myproject
pymodule-alias: funnymodule=/opt/foo/experimentalfunnymodule.py
pymodule-alias: uglymodule=/opt/foo/experimentaluglymodule.py
案例2 - 映射一个包到目录¶
你拥有这个金光闪闪的漂亮的Django项目,但是有些事情发生在你身上:它能和Django主干完美配合吗?创建一个新的虚拟环境……还是别吧。让我们只使用 pymodule-alias
!
./uwsgi -s :3031 -w django_uwsgi --pymodule-alias django=django-trunk/django
案例3 - 覆盖特定的子模块¶
你有一个Werkzeug项目,你想要覆盖它 - 出于随意什么理由 - 带你其中一个自己的设计的 werkzeug.test_app
。当然,这很容易!
./uwsgi -s :3031 -w werkzeug.testapp:test_app() --pymodule-alias werkzeug.testapp=mytestapp
应用字典¶
你可以使用应用字典机制来避免在配置中设置你的应用。
import uwsgi
import django.core.handlers.wsgi
application = django.core.handlers.wsgi.WSGIHandler()
def myapp(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/plain')])
yield 'Hello World\n'
uwsgi.applications = {
'': application,
'/django': 'application',
'/myapp': myapp
}
将这个Python模块名 (即,它应该是可导入的,并且没有 .py
扩展名)传递给uWSGI的 module
/ wsgi
选项,uWSGI将会为URL前缀/可回调映射搜索 uwsgi.applications
字典。
每一个项的值可以使一个可回调对象,或者字符串类型的名字。
Virtualenv支持¶
virtualenv 是一种机制,它允许你彼此隔离一个 (或多个) Python应用的库 (当不使用uWSGI时,还有解释器)。任何可敬的现代Python应用都应该使用virtualenv。
快速入门¶
创建你的virtualenv:
$ virtualenv myenv New python executable in myenv/bin/python Installing setuptools...............done. Installing pip.........done.
安装所有所需的模块 (以 Flask 为例):
$ ./myenv/bin/pip install flask $ # Many modern Python projects ship with a `requirements.txt` file that you can use with pip like this: $ ./myenv/bin/pip install -r requirements.txt
将你的WSGI模块拷贝到这个新环境中 (如果你不想要修改你的
PYTHONPATH
,那就是在lib/python2.x
之下)。
注解
对于许多部署而言,应用运行在virtualenv之外是常见的。如何配置它尚未有文档说明,但是它可能非常容易。
使用
home
/virtualenv
选项 (简称-H
)来运行uwsgi服务器:$ uwsgi -H myenv -s 127.0.0.1:3031 -M -w envapp
Python 3¶
WSGI规范随着 PEP3333 为Python 3进行了更新。
一个主要的改变时应用必须响应 bytes
实例,而非 (Unicode) 字符串到WSGI栈。
你应该对字符串进行编码,或者使用bytes literal:
def application(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/plain')])
yield 'Hello '.encode('utf-8')
yield b'World\n'
Paste支持¶
If you are a user or developer of 如果你是Paste兼容的框架(例如 Pyramid, Pylons 和 Turbogears 或者使用它们的应用)的用户或开发者,那么你可以使用uWSGI --paste
选项来方便地部署应用。
例如,如果你有一个位于 /opt/tg2env
的虚拟环境,包含一个名为 addressbook
的Turbogears应用,并把它配置在了 /opt/tg2env/addressbook/development.ini
:
uwsgi --paste config:/opt/tg2env/addressbook/development.ini --socket :3031 -H /opt/tg2env
就这样!无需编写额外的配置或者Python模块。
警告
如果你设置多个进程/worker (master mode) ,那么你将收到一个错误:
AssertionError: The EvalException middleware is not usable in a multi-process environment
在这种情况下,你将必须把你的paste配置文件中的 debug
选项设置为False —— 或者恢复到单进程环境。
Pecan支持¶
如果你是 Pecan WSGI框架的用户或开发者,那么你可以使用uWSGI的 --pecan
选项来方便地部署应用。
例如,如果你有一个位于 /opt/pecanenv
的虚拟环境,包含一个名为 addressbook
的Pecan应用,并把它配置在了 /opt/pecanenv/addressbook/development.py
:
uwsgi --pecan /opt/pecanenv/addressbook/development.py --socket :3031 -H /opt/pecanenv
警告
如果你设置多个进程/worker (master 模式),那么你将收到一个错误:
AssertionError: The DebugMiddleware middleware is not usable in a multi-process environment
在这种情况下,你将必须把你的Pecan配置文件中的 debug
选项设置为False —— 或者恢复到单进程环境。
使用Django应用django-uwsgi¶
首先,你需要从https://github.com/unbit/django-uwsgi获取``django_uwsgi`` app (一旦它在发行的``django``目录中)。
通过``pip install django-uwsgi``安装,并且将其添加到你的``INSTALLED_APPS``中。
INSTALLED_APPS = (
# ...
'django.contrib.admin',
'django_uwsgi',
# ...
)
然后相应地修改``urls.py``。例如:
# ...
url(r'^admin/uwsgi/', include('django_uwsgi.urls')),
url(r'^admin/', include(admin.site.urls)),
# ...
确保将django_uwsgid的URL模式放在admin site的模式*之前*,否则将永远匹配不上它。
然后,``/admin/uwsgi/``将提供uWSGI静态文件,并且有一个优雅重载服务器(当运行在Master之下时)的按钮。注意,只有在启用了``memory-report``选项的情况下,才会报告内存使用情况。
`阅读django-uwsgi位于rtfd.org <http://django-uwsgi.rtfd.org/>的文档`_
PyPy插件¶
要求uWSGI >= 2.0.9
介绍¶
想法/设计:Maciej Fijalkowski
贡献者:Alex Gaynor, Armin Rigo
自uWSGI 1.9.11起,就有了一个基于cffi的PyPy插件。老且慢的基于cpyext的已经从版本树中移除了。
目前,只在Linux系统上支持该插件。接下来的发布版本也会支持其他平台。
这个插件在启动的时候加载 libpypy-c.so
,设置PyPy安装目录,并且执行一个实现插件逻辑的特别的Python模块。所以是哒,大多数的插件都是用Python实现的,并且理论上讲,这个方法将允许除了C, C++和Objective-C之外,直接用Python编写uWSGI插件。
截至2014年12月,已经合并了到PyPy的所有所需插件,因此你可以获取一个官方最新版本 (或者一个于2014年12月后发布的稳定版本),然后与uWSGI一起使用它。
安装带PyPy支持的uWSGI¶
与往常一样,使用uWSGI,你有不同的方法基于你的需求安装uWSGI。
如果你已在PyPy家目录安装了pip,那么可以运行
pip install uwsgi
这个uwsgi setup.py文件将会识别PyPy环境,并且构建一个仅PyPy的uWSGI二进制文件。
使用相同的方式,你可以执行uWSGI源码中提供的setup.py:
pypy setup.py install
(这两种方法将会把pypy家目录硬编码到uWSGI二进制文件中,因此,你无需在你的选项中设置pypy-home)
或者你可以手工编译:
UWSGI_PROFILE=pypy make
或者可以使用网络安装程序:
curl https://uwsgi.it/install | bash -s pypy /tmp/uwsgi
这将会在 /tmp/uwsgi
中构建一个uWSGI + PyPy二进制文件
或者你可以把PyPy支持当成插件构建。
uwsgi --build-plugin plugins/pypy
或者(旧式)
python uwsgiconfig.py --plugin plugins/pypy
PyPy家目录¶
uWSGI Python插件 (更准确的说,是CPython插件) 通过链接 libpython
工作。这意味着,对于Python的每一个不同的版本,你都需要重新构建这个插件。而这个PyPy插件不同,因为libpypy-c是在启动时加载的,而它的符号在运行时解决。这允许你动态迁移到一个不同的PyPy版本。
这个方法的“缺点”是,你需要在运行时通知uWSGI你的PyPy安装在哪里 (除非你是通过pip或者使用setup.py脚本来安装uwsgi的,在这样的情况下,会自动找到家目录)
假设你的PyPy位于 /opt/pypy
,你可以这样启动uWSGI:
uwsgi --http-socket :9090 --pypy-home /opt/pypy
通过这个命令行,uWSGI将会搜索 /opt/pypy/bin/libpypy-c.so
,如果找到,它将会设置那个路径为PyPy家目录。
如果你的 libpypy-c.so
位于PyPy家目录之外 (并且位于一个动态链接器不可达的目录中),那么你可以使用 --pypy-lib
选项。
uwsgi --http-socket :9090 --pypy-home /opt/pypy --pypy-lib /opt/libs/libpypy-c.so
通过这个方法,你可以使用来自一个指定PyPy构建的库,以及来自其他的家目录。
注解
如果你想指向当前目录下的一个.so文件,那么记得给–pypy-lib加上前缀./!
PyPy设置文件¶
如前所述,大多数的uWSGI PyPy插件都是用Python写的。该代码于运行时加载,你也可以自定义它。
是哒,这就意味着你无需重新构建uWSGI就可以改变这个插件的工作方式。
pypy_setup.py
文件的默认版本被嵌入到uWSGI二进制文件中,而它自动在启动时加载。
如果你想改变它,那么仅需通过 --pypy-setup
选项传递另一个文件名。
uwsgi --http-socket :9090 --pypy-home /opt/pypy --pypy-lib /opt/libs/libpypy-c.so --pypy-setup /home/foobar/foo.py
这个Python模块实现了uWSGI钩子和用于访问来自你的应用的uWSGI API的虚拟 uwsgi
python模块。
如果你想要检索嵌入的pypy_setup.py文件的内容,那么你可以通过使用 print-sym
这个便捷选项来从二进制符号中读取它。
uwsgi --print-sym uwsgi_pypy_setup
WSGI支持¶
该插件实现了PEP 333和PEP 3333。你可以同时加载WSGI模块和 mod_wsgi
样式的 .wsgi
文件。
要加载一个WSGI模块 (它必须位于你的Python路径中):
uwsgi --http-socket :9090 --pypy-home /opt/pypy --pypy-wsgi myapp
要加载一个WSGI文件:
uwsgi --http-socket :9090 --pypy-home /opt/pypy --pypy-wsgi-file /var/www/myapp/myapp.wsgi
RPC支持¶
你可以使用 uwsgi.register_rpc()
API函数来注册RPC函数,就像你可以使用普通的Python插件一样。
import uwsgi
def hello():
return "Hello World"
uwsgi.register_rpc('hello', hello)
要调用RPC函数, uwsgi.rpc()
和 uwsgi.call()
都可以用。
import uwsgi
uwsgi.rpc('192.168.173.100:3031', 'myfunc', 'myarg')
uwsgi.call('myfunc', 'myarg')
uwsgi.call('myfunc@192.168.173.100:3031', 'myarg')
(与本地RPC) 集成已经在PyPy和PyPy, PyPy和JVM, 以及PyPy和Lua之前测试过了。所有这些都工作得十分完美……因此,那意味着你可以调用来自PyPy的Java函数。
IPython技巧¶
用一个用于测试的运行中的shell是非常非常棒的。你可以使用IPython来实现。
uwsgi --socket :3031 --pypy-home /opt/pypy --pypy-eval "import IPython; IPython.embed()" --honour-stdin
uWSGI API状态¶
自20130526起,支持以下API函数、钩子和属性。
uwsgi.opt
uwsgi.post_fork_hook
uwsgi.add_cron()
uwsgi.setprocname()
uwsgi.alarm()
uwsgi.signal_registered()
uwsgi.mule_id()
uwsgi.worker_id()
uwsgi.masterpid()
uwsgi.lock()
uwsgi.unlock()
uwsgi.add_file_monitor()
uwsgi.add_timer()
uwsgi.add_rb_timer()
uwsgi.cache_get()
uwsgi.cache_set()
uwsgi.cache_update()
uwsgi.cache_del()
uwsgi.signal()
uwsgi.call()
uwsgi.rpc()
uwsgi.register_rpc()
uwsgi.register_signal()
选项¶
pypy-lib
- 加载指定的libpypy-s.sopypy-setup
- 加载指定的pypy_setup脚本文件pypy-home
- 设置pypy家目录pypy-wsgi
- 加载一个WSGI模块pypy-wsgi-file
- 加载一个mod_wsgi兼容的.wsgi文件pypy-eval
- 在fork()
之前执行指定的字符串pypy-eval-post-fork
- 在每个fork()
之后执行指定的字符串pypy-exec
- 在fork()
之前执行指定的python脚本pypy-exec-post-fork
- 在每个fork()
之后执行指定的python脚本pypy-pp/pypy-python-path/pypy-pythonpath
- 添加指定项到pythonpathpypy-paste
- 加载一个paste.deploy .ini配置pypy-ini-paste
- 加载一个paste.deploy .ini配置,并使用它的[uwsgi]部分
注意事项¶
- 混合libpython和libpypy-c是明确禁止的。pypy插件中的检查阻止你做这种地狱般的事情。
- 从一个Python程序员的观点来看,PyPy插件一般较为“正统”,而CPython在许多领域也许有点亵渎。我们已经能够进行抉择了,因为我们无需向后兼容较老的uWSGI发行版。
- uWSGI API仍然未完成
- WSGI加载器并不更新uWSGI内部应用列表,因此,诸如
--need-app
这样的东东并不能用。即使应用已成功被加载,服务器也会在启动时报告”dynamic mode”。这将会很快被修复。
在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 'foobar@192.168.173.22:3030' cache for storing sessions
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
选项)来绕过这个问题:
[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()。
uWSGI Perl支持 (PSGI)¶
在Perl世界中, PSGI 等价于 WSGI 。
官方支持PSGI插件,并且官方分配给它一个uwsgi modifier, 5
。因此,照例,如果你要将请求派发给Perl应用,那么在你的web服务器配置中设置 modifier1
值为5。
编译PSGI插件¶
你可以使用提供的 buildconf/psgi.ini
文件来构建一个仅PSGI的uWSGI服务器。在构建PSGI插件之前,确保安装了 ExtUtils::Embed
模块和它的先决条件。
python uwsgiconfig.py --build psgi
# or compile it as a plugin...
python uwsgiconfig.py --plugin plugins/psgi
# and if you have not used the default configuration
# to build the uWSGI core, you have to pass
# the configuration name you used while doing that:
python uwsgiconfig.py --plugin plugins/psgi core
或者 (如常),你可以使用网络安装程序:
curl http://uwsgi.it/install | bash -s psgi /tmp/uwsgi
这样,在/tmp/uwsgi中,你就有了一个带有perl支持的单个uwsgi二进制文件。
使用¶
这个插件只导出了一个选项: psgi <app>
你可以这样简单加载应用
./uwsgi -s :3031 -M -p 4 --psgi myapp.psgi -m
# or when compiled as a plugin,
./uwsgi --plugins psgi -s :3031 -M -p 4 --psgi myapp.psgi -m
多应用支持¶
你可以使用 mount
选项或者使用 UWSGI_SCRIPT
/UWSGI_FILE
请求变量在同一个uWSGI进程中加载多个几乎相互隔离的应用。
[uwsgi]
mount = app1=foo1.pl
mount = app2=foo2.psgi
mount = app3=foo3.pl
server {
server_name example1.com;
location / {
include uwsgi_params;
uwsgi_pass 127.0.0.1:3031;
uwsgi_param UWSGI_APPID app1;
uwsgi_param UWSGI_SCRIPT foo1.pl;
uwsgi_modifier1 5;
}
}
server {
server_name example2.com;
location / {
include uwsgi_params;
uwsgi_pass 127.0.0.1:3031;
uwsgi_param UWSGI_APPID app2;
uwsgi_param UWSGI_SCRIPT foo2.psgi;
uwsgi_modifier1 5;
}
}
server {
server_name example3.com;
location / {
include uwsgi_params;
uwsgi_pass 127.0.0.1:3031;
uwsgi_param UWSGI_APPID app3;
uwsgi_param UWSGI_SCRIPT foo3.pl;
uwsgi_modifier1 5;
}
}
自动重载器 (自uWSGI 1.9.18起)¶
选项 –perl-auto-reload <n> 允许你指示uWSGI监控由perl vm导入的每一个单一的模块。
每当其中一个模块改变,整个实例将会被(优雅)加载。
监控器通过在处理一个请求并且经过指定的秒数(自最后一次运行起)(这个秒数是选项的值)之后,迭代%INC工作的。
这可能看起来是次优的 (你会获得从以下请求起的新内容),但对于perl的工作方式而言,它是一种更稳定(安全)的方式。
如果你想从监控跳过指定文件,只需使用–perl-auto-reload-ignore来添加它们。
记住,总会扫描%INC中的模块,如果你想要监控你的.psgi文件,那么你需要使用经典的–touch-reload选项来指定它们。
注意事项¶
- 应该默认支持异步。
- 在启用了ithreads的perl构建中,支持线程。对于每个应用,会为每个线程创建一个新的解释器。这应该不会与一个简单的多进程基于fork()的子系统有太大的不同。
- 存在目前未知的内存泄漏。
真实世界例子: HTML::Mason¶
安装来自CPAN的HTML::Mason PSGI处理器,并为你的站点创建一个目录。
cpan install HTML::Mason::PSGIHandler mkdir mason
创建
mason/index.html
:% my $noun = 'World'; % my $ua = $r->headers_in; % foreach my $hh (keys %{$ua}) { <% $hh %><br/> % } Hello <% $noun %>!<br/> How are ya?<br/> Request <% $r->method %> <% $r->uri %><br/>
创建PSGI文件 (
mason.psgi
):use HTML::Mason::PSGIHandler; my $h = HTML::Mason::PSGIHandler->new( comp_root => "/Users/serena/uwsgi/mason", # required ); my $handler = sub { my $env = shift; $h->handle_psgi($env); };
注意
comp_root
,它必须是一个绝对路径!现在运行uWSGI:
./uwsgi -s :3031 -M -p 8 --psgi mason.psgi -m
然后在你的浏览器中访问
/index.html
。
Ruby支持¶
Ruby API支持¶
状态¶
用于Ruby的uWSGI API仍然未完成 (QueueFramework, SharedArea, 自定义路由和SNMP是最缺乏的一员)。一旦各种API调用准备好了,将会立即扩展DSL。
当前可用的API函数和常量 (可用在UWSGI ruby模块中找到) 是
- UWSGI.suspend
- UWSGI.masterpid
- UWSGI.async_sleep
- UWSGI.wait_fd_read
- UWSGI.wait_fd_write
- UWSGI.async_connect
- UWSGI.signal
- UWSGI.register_signal
- UWSGI.register_rpc
- UWSGI.signal_registered
- UWSGI.signal_wait
- UWSGI.signal_received
- UWSGI.add_cron
- UWSGI.add_timer
- UWSGI.add_rb_timer
- UWSGI.add_file_monitor
- UWSGI.cache_get
- UWSGI.cache_get!
- UWSGI.cache_exists
- UWSGI.cache_exists?
- UWSGI.cache_del
- UWSGI.cache_set
- UWSGI.cache_set
- UWSGI.cache_set!
- UWSGI.cache_update
- UWSGI.cache_update!
- UWSGI.setprocname
- UWSGI.set_warning_message
- UWSGI.lock
- UWSGI.unlock
- UWSGI.mem
- UWSGI.mule_get_msg
- UWSGI.request_id
- UWSGI.mule_id
- UWSGI.mule_msg
- UWSGI.worker_id
- UWSGI.log
- UWSGI.logsize
- UWSGI.i_am_the_spooler
- UWSGI.send_to_spooler
- UWSGI.spool
- UWSGI::OPT
- UWSGI::VERSION
- UWSGI::HOSTNAME
- UWSGI::NUMPROC
- UWSGI::PIDFILE
- UWSGI::SPOOL_OK
- UWSGI::SPOOL_RETRY
- UWSGI::SPOLL_IGNORE
uWSGI DSL¶
平行于uWSGI API Python装饰器,有一个可用的用于Ruby的DSL,允许优雅访问 uWSGI API。
这个模块作为 uwsgidsl.rb
,可以在源代码发行版中找到。你可以将这个代码放到你的 config.ru
文件中,或者使用 rbrequire
选项来自动包含它。
cron(hours, mins, dom, mon, dow, block)¶
使用 CronInterface
周期性执行一个任务
cron 20,16,-1,-1,-1 do |signum|
puts "It's time for tea."
end
signal(signum, block)¶
为 SignalFramework
将代码注册为信号处理器。
signal 17 do |signum|
puts "Signal #{signum} was invoked."
end
rpc(name, block)¶
将代码作为 uWSGI RPC栈 函数注册。
rpc 'helloworld' do
return "Hello World"
end
rpc 'advancedhelloworld' do |x,y|
return "x = #{x}, y = #{y}"
end
mule(id?, block)¶
将代码作为 Mule brain注册。
mule 1 do # Run in mule 1
puts "I am the mule #{UWSGI.mule_id}"
end
mule do # Run in first available mule
puts "I am the mule #{UWSGI.mule_id}"
end
在函数返回之后,mule将会是无brain的。要避免这个问题,请将这个代码放到一个循环中,或者使用 muleloop
。
muleloop(id?, block)¶
在一个循环上下文中,在Mule中执行代码。
muleloop 3 do
puts "I am the mule #{UWSGI.mule_id}"
sleep(2)
end
SpoolProc¶
Proc
的子类,允许你定义一个在 Spooler 中执行的任务。
# define the function
my_long_running_task = SpoolProc.new {|args|
puts "I am a task"
UWSGI::SPOOL_OK
}
# spool it
my_long_running_task.call({'foo' => 'bar', 'one' => 'two'})
MuleFunc¶
从任意进程(例如一个worker)中调用一个函数,但是在mule中执行
i_am_a_long_running_function = MuleFunc.new do |pippo, pluto|
puts "i am mule #{UWSGI.mule_id} #{pippo}, #{pluto}"
end
i_am_a_long_running_function.call("serena", "alessandro")
这个worker调用 i_am_a_long_running_function()
,但是函数将会在第一个可用mule中异步执行。
如果你想在一个指定的mule中运行函数,那么添加一个ID参数。以下代码将会只使用mule #5。
i_am_a_long_running_function = MuleFunc.new 5 do |pippo,pluto|
puts "i am mule #{UWSGI.mule_id} #{pippo}, #{pluto}"
end
i_am_a_long_running_function.call("serena", "alessandro")
真实世界的使用¶
一个简单的Sinatra应用,每30秒打印消息:
# This is config.ru
require 'rubygems'
require 'sinatra'
require 'uwsgidsl'
timer 30 do |signum|
puts "30 seconds elapsed"
end
get '/hi' do
"Hello World!"
end
run Sinatra::Application
或者你可以将你的代码放到一个专有的文件中 (这里是 mytasks.rb
)
require 'uwsgidsl'
timer 30 do |signum|
puts "30 seconds elapsed"
end
timer 60 do |signum|
puts "60 seconds elapsed"
end
然后这样加载它
uwsgi --socket :3031 --rack config.ru --rbrequire mytasks.rb --master --processes 4
从版本0.9.7-dev起,一个Ruby (Rack/Rails) 插件正式发布。官方用于Ruby应用的modifier数字是7,因此,记得在你的web服务器配置中设置它。
插件可以被嵌入到uWSGI核心中,或者作为一个动态加载的插件构建。
插件仍然不支持一些uWSGI标准的特性,例如:
- UDP请求管理
- SharedArea —— uWSGI组件间共享内存页 (support on the way)
- uWSGI队列框架
见 Ruby API支持 页,以获取当前支持的特性列表。
为Ruby支持构建uWSGI¶
你可以在 buildconf
目录中找到 rack.ini
。这个配置将会构建一个嵌入了Ruby解释器的uWSGI。要用这个配置构建uWSGI,你会需要Ruby头文件/开发包。
python uwsgiconfig.py --build rack
所得uWSGI二进制文件可以运行Ruby应用。
也存在一个 rackp.ini
构建配置;这将会构建一个Ruby作为插件支持的uWSGI;在这个例子中,记得带 plugins=rack
选项调用uWSGI。
关于内存消费的一个注意事项¶
默认情况下,这个插件的内存管理是非常积极的 (因为Ruby可以轻松消耗掉内存,就像它正变得过时一样)。会默认在每个请求之后调用Ruby垃圾回收器。如果你的应用在每次请求都创建大量的对象的话,这或许会损害你的性能。你可以使用 OptionRubyGcFreq 选项来调整回收的频率。像往常一样,这并没有一本万利的值,因此,实验一下吧。
如果你的应用在不受控制的情况下内存泄漏,那么在使用 max-requests
选项重启之前考虑限制一个worker可以管理的请求数。使用 limit-as
也有用。
关于线程和fiber的一个注意事项¶
添加Ruby 1.8中的线程支持在讨论之外。这个版本中的线程支持实际上对一个像uWSGI这样的服务器而言是没用的。Ruby 1.9有一个非常类似于Python的线程模式,它的支持从uWSGI 1.9.14起,就可以使用”rbthreads”插件来实现。
fiber是Ruby 1.9的一个新特性。它们是协程/绿色线程/停止恢复/合作多线程,或者任何你喜欢的对这类有趣技术的称呼的实现。见 FiberLoop
。
在uWSGI上运行Rack应用¶
这个例子向你展示如何在uWSGI上运行一个Sinatra应用。
config.ru
require 'rubygems'
require 'sinatra'
get '/hi' do
"Hello World!"
end
run Sinatra::Application
然后调用uWSGI (如果你将Ruby支持作为插件构建,那么使用 --plugins
):
./uwsgi -s :3031 -M -p 4 -m --post-buffering 4096 --rack config.ru
./uwsgi --plugins rack -s :3031 -M -p 4 -m --post-buffering 4096 --rack config.ru
注解
根据Rack规范, post-buffering
是必须的。
注解
由于Sinatra有一个内建的日志记录系统,因此你或许希望用 disable-logging
选项来禁用uWSGI对请求的日志记录。
在uWSGI上运行Ruby on Rails应用¶
由于编写正式的文档并不十分有趣,这里是几个uWSGI上的Rails应用的例子。
运行typo¶
sudo gem install typo
typo install /tmp/mytypo
./uwsgi -s :3031 --lazy-apps --master --processes 4 --memory-report --rails /tmp/mytypo --post-buffering 4096 --env RAILS_ENV=production
–lazy-apps这里是至关重要的,因为typo (就像许多应用一样) 并非fork友好型的 (它不期望在master中加载,然后调用fork())。使用这个选项,应用会在每个worker中完全加载一次。
Nginx配置:
location / {
root "/tmp/mytypo/public";
include "uwsgi_params";
uwsgi_modifier1 7;
if (!-f $request_filename) {
uwsgi_pass 127.0.0.1:3031;
}
}
运行Radiant¶
sudo gem install radiant
radiant /tmp/myradiant
cd /tmp/myradiant
# (edit config/database.yml to fit)
rake production db:bootstrap
./uwsgi -s :3031 -M -p 2 -m --rails /tmp/myradiant --post-buffering 4096 --env RAILS_ENV=production
Apache配置 (直接映射静态路径):
DocumentRoot /tmp/myradiant/public
<Directory /tmp/myradiant/public>
Allow from all
</Directory>
<Location />
uWSGISocket 127.0.0.1:3032
SetHandler uwsgi-handler
uWSGIForceScriptName /
uWSGImodifier1 7
</Location>
<Location /images>
SetHandler default-handler
</Location>
<Location /stylesheets>
SetHandler default-handler
</Location>
<Location /javascripts>
SetHandler default-handler
</Location>
Rails和SSL¶
你或许希望使用 HTTPS
/ UWSGI_SCHEME https
uwsgi协议参数来告知应用它运行在HTTPS之下。
对于Nginx:
uwsgi_param HTTPS on; # Rails 2.x apps
uwsgi_param UWSGI_SCHEME https; # Rails 3.x apps
联合uWSGI使用Lua/WSAPI¶
为uWSGI 2.0进行了更新
构建插件¶
lua插件是官方的uWSGI发行版的一部分 (官方的modifier 6),你可以在plugins/lua目录下找到它。
该插件支持lua 5.1, lua 5.2和luajit。
默认情况下,假设使用lua 5.1
如往常一样,有多种方法来构建和安装Lua支持:
从源代码目录:
make lua
通过安装程序 (结果二进制文件将会出现在/tmp/uwsgi)
curl http://uwsgi.it/install | bash -s lua /tmp/uwsgi
或者你可以把它当成一个插件进行构建
python uwsgiconfig.py --plugin plugins/lua
或者 (如果你已经有了一个uwsgi二进制文件)
uwsgi --build-plugin plugins/lua
构建系统 (查看plugins/lua目录中的uwsgiplugin.py以获取更多信息) 使用pkg-config来查找头文件和库。
你可以指定pkg-config模块来使用UWSGICONFIG_LUAPC环境变量。
例如
UWSGICONFIG_LUAPC=lua5.2 make lua
将会为lua 5.2构建一个uwsgi二进制文件
以及
UWSGICONFIG_LUAPC=luajit make lua
将会为luajit构建一个uwsgi二进制文件
如果你不想依赖于pkg-config工具,那么你可以通过以下环境变量手动指定包含和库目录,以及lib名字:
UWSGICONFIG_LUAINC=<directory>
UWSGICONFIG_LUALIBPATH=<directory>
UWSGICONFIG_LUALIB=<name>
为什么是Lua ?¶
如果你是从其他面向对象的语言来的,那么你可能会发现对于web开发来说,lua是一个奇怪的选择。
那么,在探索Lua的时候,你必须考虑一件事:它很快,真的很快,并且消耗非常少的资源。
uWSGI插件允许你用lua编写web应用,但是另一个目的(如果不是主要目的)是使用Lua来扩展使用信号框架、rpc子系统或者简单的钩子引擎的uWSGI服务器 (和你的应用)。
如果你的代码中存在慢区域(独立于语言使用),那么考虑用Lua (在用C处理之前) 重写它们,并且使用uWSGI来安全调用它们。
你的第一个WSAPI应用¶
我们将使用官方的WSAPI例子,让我们称之为 pippo.lua
:
function hello(wsapi_env)
local headers = { ["Content-type"] = "text/html" }
local function hello_text()
coroutine.yield("<html><body>")
coroutine.yield("<p>Hello Wsapi!</p>")
coroutine.yield("<p>PATH_INFO: " .. wsapi_env.PATH_INFO .. "</p>")
coroutine.yield("<p>SCRIPT_NAME: " .. wsapi_env.SCRIPT_NAME .. "</p>")
coroutine.yield("</body></html>")
end
return 200, headers, coroutine.wrap(hello_text)
end
return hello
现在用 lua
选项来运行uWSGI (如果你将其当做插件使用,那么记得添加 --plugins lua
作为第一个命令行选项)
./uwsgi --http :8080 --http-modifier1 6 --lua pippo.lua
这个命令行启动了一个http路由器,它转发请求到单个加载了pippo.lua的worker。
正如你可以见到的那样,强制使用modifier 6。
显然,你可以直接附加uWSGI到你的前线web服务器(例如nginx)上,并将其绑定到一个uwsgi socket:
./uwsgi --socket 127.0.0.1:3031 --lua pippo.lua
(记得在你选择的web服务器中设置modifier1为6)
并发¶
基本上,在所有支持的uWSGI并发模型中,都能用Lua
你可以用多进程:
./uwsgi --socket 127.0.0.1:3031 --lua pippo.lua --processes 8 --master
或者多线程:
./uwsgi --socket 127.0.0.1:3031 --lua pippo.lua --threads 8 --master
或者同时使用
./uwsgi --socket 127.0.0.1:3031 --lua pippo.lua --processes 4 --threads 8 --master
你可以以协程模式 (见下) 运行它,使用 uGreen – uWSGI绿色线程(green thread) 作为挂起引擎
./uwsgi --socket 127.0.0.1:3031 --lua pippo.lua --async 1000 --ugreen
线程和异步模式每个都将初始化lua状态 (你可以将其视为一个完整的独立lua VM)
尽情使用协程¶
Lua最让人兴奋的特性之一是协程(协作多线程)支持。uWSGI使用它的异步引擎,可以从中受益。Lua插件将会为每个异步核心初始化一个 lua_State
。我们将使用我们的pippo.lua的CPU密集型版本来测试它:
function hello(wsapi_env)
local headers = { ["Content-type"] = "text/html" }
local function hello_text()
coroutine.yield("<html><body>")
coroutine.yield("<p>Hello Wsapi!</p>")
coroutine.yield("<p>PATH_INFO: " .. wsapi_env.PATH_INFO .. "</p>")
coroutine.yield("<p>SCRIPT_NAME: " .. wsapi_env.SCRIPT_NAME .. "</p>")
for i=0, 10000, 1 do
coroutine.yield(i .. "<br/>")
end
coroutine.yield("</body></html>")
end
return 200, headers, coroutine.wrap(hello_text)
end
return hello
并且运行带有8个异步核心的uWSGI……
./uwsgi --socket :3031 --lua pippo.lua --async 8
就这样,你可以在单个worker中管理8个并发请求!
Lua协程并非在C栈之上运行的 (意味着你不能用你的C代码来管理它们),但多亏了 uGreen – uWSGI绿色线程(green thread) (uWSGI官方协程/绿色线程引擎),你可以绕过这个限制。
多亏了uGreen,你可以在你的Lua应用中使用uWSGI异步API,获得一个非常高层次的并发。
uwsgi.async_connect
uwsgi.wait_fd_read
uwsgi.wait_fd_write
uwsgi.is_connected
uwsgi.send
uwsgi.recv
uwsgi.close
uwsgi.ready_fd
线程例子¶
Lua插件是“线程安全的”,因为uWSGI因为一个lua_State到每个内部pthread。例如,你可以非常容易地运行Sputnik_ wiki引擎。使用
LuaRocks 来安装Sputnik和 versium-sqlite3
。要求有一个数据库备份存储,因为默认的文件系统存储并不支持多个解释器并发访问。创建一个wsapi兼容文件:
require('sputnik')
return sputnik.wsapi_app.new{
VERSIUM_STORAGE_MODULE = "versium.sqlite3",
VERSIUM_PARAMS = {'/tmp/sputnik.db'},
SHOW_STACK_TRACE = true,
TOKEN_SALT = 'xxx',
BASE_URL = '/',
}
并运行你的线程化uWSGI服务器
./uwsgi --plugins lua --lua sputnik.ws --threads 20 --socket :3031
内存的注意事项¶
我们都知道,uWSGI对内存的吝啬。内存是一种珍贵的字眼。不要信任那些不关心你的内存的软件!在每个请求之后,(默认)会自动调用Lua垃圾收集器。
你可以使用 --lua-gc-freq <n>
选项来调整GC调用的频率,其中,n是将会调用GC之后的请求数目:
[uwsgi]
plugins = lua
socket = 127.0.0.1:3031
processes = 4
master = true
lua = foobar.lua
; run the gc every 10 requests
lua-gc-freq = 10
RPC和信号¶
Lua shell¶
把Lua当成一个“配置器”使用¶
uWSGI api状态¶
uWSGI服务器中的JVM (更新至1.9)¶
JWSGI接口¶
注解
JWSGI并不是一个标准,然而,如果你喜欢JWSGI,那么何不发送一个RFC给uWSGI邮件列表。我们对标准没有特别的兴趣,但是谁知道呢……
JWSGI是考虑Java的WSGI/PSGI/Rack方式的一个口子。
如果,由于某种模糊的原因,你想要用JVM语言开发应用,并且你不想要部署一个巨大的servlet栈,那么JWSGI应该与你同行。
它是一个非常简单的协议:调用一个公共方法,它接收一个 HashMap
作为它唯一的参数。这个HashMap包含了CGI风格的变量,而 jwsgi.input
包含了一个Java InputStream对象。
这个函数必须返回一个包含3个对象的数字:
status
(java.lang.Integer) (例如:200)headers
(HashMap) (例如:{“Content-type”: “text/html”, “Server”: “uWSGI”, “Foo”: [“one”,”two”]})body
(可能是一个String,一个String数组,一个File或者一个InputStream对象)
例子¶
一个简单的JWSGI应用看起来像这样:
import java.util.*;
public class MyApp {
public static Object[] application(HashMap env) {
int status = 200;
HashMap<String,Object> headers = new HashMap<String,Object>();
headers.put("Content-type", "text/html");
// a response header can have multiple values
String[] servers = {"uWSGI", "Unbit"};
headers.put("Server", servers);
String body = "<h1>Hello World</h1>" + env.get("REQUEST_URI");
Object[] response = { status, headers, body };
return response;
}
}
如何使用它?¶
你同时需要’jvm’插件和’jwsgi’插件。项目中有一个名为’jwsgi’的构建配置文件可以用,允许你实现带jvm+jwsgi的单片构建:
UWSGI_PROFILE=jwsgi make
用
javac
编译你的类。javac MyApp.java
运行uWSGI,并且指定要运行的方法 (使用格式class:method)
./uwsgi --socket /tmp/uwsgi.socket --plugins jvm,jwsgi --jwsgi MyApp:application --threads 40
这将会在UNIX socket /tmp/uwsgi.socket上运行一个JWSGI应用,使用40个线程。
读取请求体¶
jwsgi.input
项是一个 uwsgi.RequestBody
对象 (java/io/InputStream的子类). 通过它来访问请求体。
import java.util.*;
public class MyApp {
public static Object[] application(HashMap env) {
int status = 200;
HashMap<String,Object> headers = new HashMap<String,Object>();
headers.put("Content-type", "text/plain");
int body_len = Integer.parseInt((String) env.get("CONTENT_LENGTH"));
byte[] chunk = new byte[body_len];
uwsgi.RequestBody input = (uwsgi.RequestBody) env.get("jwsgi.input");
int len = input.read(chunk);
System.out.println("read " + len + " bytes");
String body = new String(chunk, 0, len);
Object[] response = { status, headers, body };
return response;
}
}
注意用 read(byte[])
来代替传统的 read()
。后者低效地每次读取一个字节,而前者一次读取一个更大的块。
JWSGI和Groovy¶
因为它是低级别的,因此,JWSGI标准可以原样用于JVM上运行的其他语言。例如,这是一个”Hello World” Groovy样例:
static def Object[] application(java.util.HashMap env) {
def headers = ["Content-Type":"text/html", "Server":"uWSGI"]
return [200, headers, "<h1>Hello World</h1"]
}
一个提供静态文件服务的例子:
static def Object[] application(java.util.HashMap env) {
def headers = ["Content-Type":"text/plain", "Server":"uWSGI"]
return [200, headers, new File("/etc/services")]
}
第二个方法非常有效,因为它将滥用uWSGI内部功能。例如,如果你启用了卸载,那么你的worker线程将会被突然释放。要加载Groovy代码,记得编译它:
groovyc Foobar.groovy
然后运行它:
./uwsgi --socket /tmp/uwsgi.socket --plugins jvm,jwsgi --jwsgi Foobar:application --threads 40
JWSGI和Scala¶
就像Groovy,你可以用Scala编写JWSGI应用。你只需要入口点函数来使用原生的Java对象:
object HelloWorld {
def application(env:java.util.HashMap[String, Object]): Array[Object] = {
var headers = new java.util.HashMap[String, Object]()
headers.put("Content-Type", "text/html")
headers.put("Server", "uWSGI")
return Array(200:java.lang.Integer, headers , "Hello World")
}
}
或者一种更Scala的方式:
object HelloWorld {
def application(env:java.util.HashMap[String, Object]): Array[Object] = {
val headers = new java.util.HashMap[String, Object]() {
put("Content-Type", "text/html")
put("Server", Array("uWSGI", "Unbit"))
}
return Array(200:java.lang.Integer, headers , "Hello World")
}
}
一旦用 scalac <filename>
编译好了,你可以像这样运行:
./uwsgi --socket /tmp/uwsgi.socket --plugins jvm,jwsgi --jwsgi HelloWorld:application --threads 40
Clojure/Ring JVM请求处理器¶
幸好自1.9起, uWSGI服务器中的JVM (更新至1.9) 插件就能用了,可以在uWSGI上运行Clojure web应用。
支持的网关标准是Ring, https://github.com/ring-clojure/ring。它完整的详细说明在这里: https://github.com/ring-clojure/ring/blob/master/SPEC
有一个名为”ring”的uWSGI构建配置文件可以用来生成同时带有JVM和Ring插件的单片构建。
从uWSGI源代码:
UWSGI_PROFILE=ring make
构建系统将会试着根据不同预设检测你的JDK安装 (例如,在CentOS上,你可以 yum install
java-1.6.0-openjdk.x86_64-devel
或者 java-1.7.0-openjdk-devel.x86_64
,或者在Debian/Ubuntu上 openjdk-6-jdk
等等……)。
也会搜索OSX/Xcode默认路径。
成功构建之后,你将会得到uwsgi二进制文件和一个uwsgi.jar文件,你应该拷贝到你的CLASSPATH中 (或者每次记住在uwsgi配置中设置它)。
参见
对于更多关于JVM插件的信息,看看 uWSGI服务器中的JVM (更新至1.9)
我们第一个Ring应用¶
一个基本的Clojure/Ring应用可能如下 (将其保持为myapp.clj):
(ns myapp)
(defn handler [req]
{:status 200
:headers { "Content-Type" "text/plain" , "Server" "uWSGI" }
:body (str "<h1>The requested uri is " (get req :uri) "</h1>")
}
)
该代码定义了一个名为’myapp’的新的名字空间,其中,’handler’函数是Ring的入口点 (每个web请求都会调用的函数)
现在,我们可以构建一个配置,在HTTP路由器上提供该应用,端口为9090 (称之为config.ini):
[uwsgi]
http = :9090
http-modifier1 = 8
http-modifier2 = 1
jvm-classpath = plugins/jvm/uwsgi.jar
jvm-classpath = ../.lein/self-installs/leiningen-2.0.0-standalone.jar
clojure-load = myapp.clj
ring-app = myapp:handler
运行uWSGI:
./uwsgi config.ini
现在,连接到端口9090,你应该看到该应用响应。
你可以注意到,我们手工添加了uwsgi.jar和Leiningen的standalone.jar (它包括整个Clojure发行版) 到我们的classpath中。
显然,如果你不想要使用Leiningen,那么只需添加Clojure jar到你的classpath。
clojure-load
选项在JVM中加载了一个Clojure对象 (非常类似于 jvm-class
和基本的jvm插件做的事)。
ring-app
指定了搜索ring函数入口点的类/名字空间。
在我们的例子中,该函数位于’myapp’名字空间,叫做’handler’ (你可以理解语法为namespace:function)
注意modifier配置。JVM插件将自己注册为8,而Ring则将自己注册为modifier 2 #1,产生有效配置”modifier1 8, modifier2 1”。
使用Leiningen¶
Leiningen是一个用来管理Clojure项目的很不错的工具。如果你使用Clojure,那么你非常有可能是一个Leiningen用户。
Leiningen的一个巨大的优势在于轻松生成单个JAR发行版。这意味着,你可以用单个文件部署整个应用。
让我们用 lein
命令创建一个新的”helloworld” Ring应用。
lein new helloworld
把它移到刚刚创建的’helloworld’目录,然后编辑project.clj文件
(defproject helloworld "0.1.0-SNAPSHOT"
:description "FIXME: write description"
:url "http://example.com/FIXME"
:license {:name "Eclipse Public License"
:url "http://www.eclipse.org/legal/epl-v10.html"}
:dependencies [[org.Clojure/Clojure "1.4.0"]])
我们想要添加 ring-core
包到我们的依赖中 (它包含了一组类/模块来简化ring应用的编写) ,而显然,我们需要修改描述和URL:
(defproject helloworld "0.1.0-SNAPSHOT"
:description "My second uWSGI ring app"
:url "https://uwsgi-docs.readthedocs.io/en/latest/Ring.html"
:license {:name "Eclipse Public License"
:url "http://www.eclipse.org/legal/epl-v10.html"}
:dependencies [[org.Clojure/Clojure "1.4.0"] [ring/ring-core "1.2.0-beta1"]])
现在,保存并运行……
lein repl
这将会安装所有我们需要的jar,并且带我们到Clojure控制台 (目前从中退出即可)。
现在,我们想要写我们的Ring应用,只需编辑文件src/helloworld/core.clj,将以下内容放到它里面:
(ns helloworld.core
(:use ring.util.response))
(defn handler [request]
(-> (response "Hello World")
(content-type "text/plain")))
然后重新编辑project.clj,来指示Leiningen在哪个名字空间之上构建:
(defproject helloworld "0.1.0-SNAPSHOT"
:description "FIXME: write description"
:url "http://example.com/FIXME"
:license {:name "Eclipse Public License"
:url "http://www.eclipse.org/legal/epl-v10.html"}
:aot [helloworld.core]
:dependencies [[org.Clojure/Clojure "1.4.0"] [ring/ring-core "1.2.0-beta1"]])
正如你所见,我们在 :aot
关键字中添加了helloworld.core。
现在,让我们编译我们的代码:
lein compile
然后构建完整的jar (uberjar):
lein uberjar
如果一切顺利,你应该会在这个过程的最后看到像这样的消息:
Created /home/unbit/helloworld/target/helloworld-0.1.0-SNAPSHOT-standalone.jar
记下这个地址,接着我们可以配置uWSGI来运行我们的应用。
[uwsgi]
http = :9090
http-modifier1 = 8
http-modifier2 = 1
jvm-classpath = plugins/jvm/uwsgi.jar
jvm-classpath = /home/unbit/helloworld/target/helloworld-0.1.0-SNAPSHOT-standalone.jar
jvm-class = helloworld/core__init
ring-app = helloworld.core:handler
这次,我们不加载Clojure代码,而是直接加载一个JVM类。
注意:当你指定过一个JVM类的时候,你必须使用’/’形式,而不是常见的点形式。
当编译你的应用的时候,Clojure系统会自动添加__init后缀。
ring-app
设置helloworld.core名字空间的入口点和函数’handler’。
当我们用 jvm-class
加载它的时候,我们可以访问那个名字空间。
并发¶
和所有JVM插件请求处理器一样,多线程是达到并发的最好的方式。
JVM中的线程时相当稳定的,不要怕使用它们 (即使你也可以生成多个进程)
[uwsgi]
http = :9090
http-modifier1 = 8
http-modifier2 = 1
jvm-classpath = plugins/jvm/uwsgi.jar
jvm-classpath = /home/unbit/helloworld/target/helloworld-0.1.0-SNAPSHOT-standalone.jar
jvm-class = helloworld/core__init
ring-app = helloworld.core:handler
master = true
processes = 4
threads = 8
这个设置会生成4个uWSGI进程 (worker) ,每个进程有8个线程 (总共32个线程)。
访问uWSGI api¶
Clojure也可以调用原生的Java类,因此它可以访问由JVM插件公开的uWSGI API。
下面的例子展示了如何通过Clojure调用一个函数 (用python写的):
(ns myapp
(import uwsgi)
)
(defn handler [req]
{:status 200
:headers { "Content-Type" "text/html" , "Server" "uWSGI" }
:body (str "<h1>The requested uri is " (get req :uri) "</h1>" "<h2>reverse is " (uwsgi/rpc (into-array ["" "reverse" (get req :uri)])) "</h2>" )
}
)
从一个Python模块注册了”reverse”函数:
from uwsgidecorators import *
@rpc('reverse')
def contrario(arg):
return arg[::-1]
这是使用的配置:
[uwsgi]
http = :9090
http-modifier1 = 8
http-modifier2 = 1
jvm-classpath = plugins/jvm/uwsgi.jar
jvm-classpath = /usr/share/java/Clojure-1.4.jar
Clojure-load = myapp.clj
plugin = python
import = pyrpc.py
ring-app = myapp:handler
master = true
另一个有用的特性是访问uwsgi换成。记住,缓存键是字符串,而值是字节。
uWSGI实现对于响应,除了字符串,还支持字节数组。这显然违反了标准,但是避免了每次都要重新编码字节 (但显然,如果你喜欢的话,你可以随意每次重新编码)。
介绍¶
自uWSGI 1.9起,你可以拥有一个嵌入在核心中的完整、线程安全的通用JVM。所有的插件可以通过 RPC subsystem 或者使用uWSGI uWSGI信号框架 来调用JVM函数 (用Java, JRuby, Jython, Clojure, 任何JVM可以运行的新式语言编写的)。JVM插件本身可以实现请求处理器来托管基于JVM的web应用。目前,支持 JWSGI接口 和 Clojure/Ring JVM请求处理器 (Clojure)应用。长期目标是支持servlets,但它将需要大量的赞助和资金 (请随意到info@unbit.it询问更多有关项目的信息)。
构建JVM支持¶
首先,确保安装了一个完整的JDK发行版。uWSGI构建系统将试着检测常见的JDK设置 (Debian, Ubuntu, Centos, OSX...),但是,如果它没法找到一个JDK安装,那么它会需要一些来自用户的信息(见下)。要构建JVM插件,只需运行:
python uwsgiconfig.py --plugin plugins/jvm default
如果需要的话,修改’default’为你的构建配置文件。例如,如果你有一个Perl/PSGI单片构建,只需运行
python uwsgiconfig.py --plugin plugins/jvm psgi
或者对于一个完整的模块化构建
python uwsgiconfig.py --plugin plugins/jvm core
如果一切顺利,将会构建jvm_plugin。如果构建系统不能找到一个JDK案子,那么你将需要指定头文件目录(包含jni.h文件的目录)和lib目录(包含libjvm.so的目录)的地址。例如,如果jni.h在/opt/java/includes中,而libjvm.so在/opt/java/lib/jvm/i386中,那么这样运行构建系统:
UWSGICONFIG_JVM_INCPATH=/opt/java/includes UWSGICONFIG_JVM_LIBPATH=/opt/java/lib/jvm/i386 python uwsgiconfig --plugin plugins/jvm
成功构建之后,你会获得uwsgi.jar文件的路径。那个jarball包含了访问uWSGI API的类,而你应该将它拷贝到你的CLASSPATH中,或者至少手工从uWSGI的配置加载它。
通过RPC子系统公开函数¶
在这个例子中,我们将公开一个”hello” Java函数 (返回一个字符串),并且将从一个Python WSGI应用中调用它。这是我们的基本配置 (假设使用模块化构建)。
[uwsgi]
plugins = python,jvm
http = :9090
wsgi-file = myapp.py
jvm-classpath = /opt/uwsgi/lib/uwsgi.jar
jvm-classpath
是一个由JVM插件公开的选项,它允许你添加目录或者jar文件到你的classpath中。你可以指定任意多的 jvm-classpath
选项。这里,我们手工添加 uwsgi.jar
,因为我们并没有将它拷贝到我们的CLASSPATH中。这是我们的WSGI样例脚本。
import uwsgi
def application(environ, start_response):
start_response('200 OK', [('Content-Type','text/html')])
yield "<h1>"
yield uwsgi.call('hello')
yield "</h1>"
这里,我们使用 uwsgi.call()
来取代 uwsgi.rpc()
,作为一种快捷方式 (选项解析时会有小的性能提升)。现在,我们创建我们的Foobar.java类。它的 static void main()
函数将会在启动的时候由uWSGI运行。
public class Foobar {
static void main() {
// create an anonymous function
uwsgi.RpcFunction rpc_func = new uwsgi.RpcFunction() {
public String function(String... args) {
return "Hello World";
}
};
// register it in the uWSGI RPC subsystem
uwsgi.register_rpc("hello", rpc_func);
}
}
uwsgi.RpcFunction
接口允许你轻松编写uWSGI兼容的RPC函数。现在,编译Foobar.java文件:
javac Foobar.java
(最终,修正classpath,或者用-cp选项传递uwsgi.jar路径) 你现在有了一个Foobar.class,它可以由uWSGI加载。让我们完成配置……
[uwsgi]
plugins = python,jvm
http = :9090
wsgi-file = myapp.py
jvm-classpath = /opt/uwsgi/lib/uwsgi.jar
jvm-main-class = Foobar
最后一个选项 (jvm-main-class
) 将会加载一个java类,并且执行它的 main()
方法。现在,我们可以访问localhost:9090,并且应该看到Hello World消息。
注册信号处理器¶
用与RPC子系统相同的方式,你可以注册信号处理器。你将能够在时间事件、文件修改、cron等上调用Java函数
我们的Sigbar.java:
public class Sigbar {
static void main() {
// create an anonymous function
uwsgi.SignalHandler sh = new uwsgi.SignalHandler() {
public void function(int signum) {
System.out.println("Hi, i am the signal " + signum);
}
};
// register it in the uWSGI signal subsystem
uwsgi.register_signal(17, "", sh);
}
}
uwsgi.SignalHandler
是用于信号处理器的接口。
每当触发了信号17,将会运行对应的JVM。记得编译这个文件,在uWSGI中加载它,并启用master进程 (没有它,信号子系统将不能用)。
fork()问题和多线程¶
JVM并不是 fork()
友好的。如果你在master中加载一个虚拟机,然后fork() (就像你通常在其他语言中做的那样) 子JVM将会失败 (这主要是因为JVM需要的线程并不会继承)。出于这样的原因,会为每个worker、mule和spooler生成一个JVM。幸运的是,与其他绝大部分平台不同,JVM带有真正强大的多线程支持。uWSGI支持它,因此,如果你想要运行请求处理器 (JWSGI, Clojure/Ring) 中的一个,那么只需记得使用 --threads
选项生成一批线程。
它是如何工作的呢?¶
uWSGI使用JNI接口嵌入JVM。不幸的是,我们不能依赖JVM的自动垃圾回收器,因此我们必须自动解引用所有分配的对象。这从性能和使用的角度来说并不是个问题,但是会让插件的开发与其他基于JVM的产品相比更难一点。幸运的是,当前的API简化了那个任务。
加载类 (不带main方法)¶
我们已经看到了如何加载类和在启动时运行它们的 main()
方法。通常来说,你会想要只在添加它们到JVM的时候加载类 (运行访问外部模块需要用到它们)。要加载一个类,你可以使用 --jvm-class
。
[uwsgi]
...
jvm-class = Foobar
jvm-class = org/unbit/Unbit
记住,类名必须使用’/’格式,而不是点!这个规则也应用到 --jvm-main-class
。
请求处理器¶
虽然Java(TM)的世界有它自己的J2EE环境来部署web应用,你可以响应使用一个不同的方法。uWSGI项目实现了J2EE没有的大量特性 (并且没有实现大量J2EE强大的特性),因此,你可能会发现它的方法更适合你的设置(或者口味,又或者技能)。
JVM插件公开了一个API,允许hook web请求。这个方法与uWSGI工作的“传统”方式有点托尼盖。JVM插件将自己注册为一个处理器,使用modifier1==8,但将会看看modifier2的值,以确定使用它的哪一个请求处理器来处理它。例如, Clojure/Ring JVM请求处理器 插件在JVM插件中注册,使用modifier2数为‘1’。因此,要传递请求给它,你需要类似于这样的:
[uwsgi]
http = :9090
http-modifier1 = 8
http-modifier2 = 1
或者使用nginx:
location / {
include uwsgi_params;
uwsgi_modifier1 8;
uwsgi_modifier2 1;
uwsgi_pass /tmp/uwsgi.socket;
}
目前,有2个JVM请求处理器可用:
- JWSGI接口
- Clojure/Ring JVM请求处理器 (for Clojure)
前面已经说了,开发一个servlet请求处理器的想法是有的,但是它将需要赞助(也就是说,银子),因为这会需要相当大的努力。
注意事项¶
- 要使用UNIX socket,无需特殊的jar文件 —— JVM插件可以访问所有的uWSGI特性。
- 你可能会沉迷于log4j模块。这没啥不对,但是看看uWSGI的日志记录功能 (需要更少的资源,更少的配置,以及更少的进取心)
- uWSGI API访问仍然未完成 (将会在1.9后更新)
- JVM在受限地址空间的环境中无法愉快使用。如果你在你的实例中加载JVM,那么避免使用
--limit-as
。
Mono ASP.NET插件¶
uWSGI 1.9添加了对Mono平台的支持,特别是对于ASP.NET基础设施。
The most common way to deploy 部署Mono ASP.NET应用的最常见方式是使用XSP项目,它是一个简单的web服务器网关,实现了HTTP和FastCGI协议。
通过Mono插件,你将能够给直接在uWSGI中托管ASP.net应用,在你的应用中免费获得它所有的特性。
正如所有其他uWSGI插件一样,你可以使用 uWSGI RPC栈 子系统调用所有从其他语言导出的函数。
构建uWSGI + Mono¶
你可以作为插件或者在一个单片构建中构建Mono支持。
有一个名为”mono”的构建配置文件可用,让这个任务相当简单。
确保你的系统中安装了mono。你需要Mono头部, mcs
编译器和System.Web组合。在标准的mono发行版中都可以找到它们。
在最近的Debian/Ubuntu系统中,你可以使用
apt-get install build-essential python mono-xsp4 asp.net-examples
mono-xsp4
是一次性安装所有我们需要的东东的窍门,而ASP.net样例将会被用来测试我们的安装。
我们可以构建一个嵌入了Mono的单片uWSGI发行版:
UWSGI_PROFILE=mono make
在这个过程的最后 (如果一切顺利的话),你将会得到到 uwsgi.dll
组合的路径。
你或许想要在你的GAC中安装它 (使用gacutil -i <path>),以避免每次都要指定其路径。这个库让你访问来自Mono应用的uWSGI api。
启动服务器¶
Mono插件有一个官方的 modifier1
, 15。
[uwsgi]
http = :9090
http-modifier1 = 15
mono-app = /usr/share/asp.net-demos
mono-index = index.asp
前一个步骤假设已在GAC中安装了uwsgi.dll,如果你并非如此,那么你可以这样强制它的路径使用:
[uwsgi]
http = :9090
http-modifier1 = 15
mono-app = /usr/share/asp.net-demos
mono-index = index.asp
mono-assembly = /usr/lib/uwsgi/uwsgi.dll
/usr/share/asp.net-demos
是包含Mono的样例ASP.net应用的目录。
如果启动uWSGI,那么你会得到一个关于不能找到uwsgi.dll的错误,你可以这样强制使用一个特定的路径
[uwsgi]
http = :9090
http-modifier1 = 15
mono-app = /usr/share/asp.net-demos
mono-index = index.asp
mono-assembly = /usr/lib/uwsgi/uwsgi.dll
env = MONO_PATH=/usr/lib/uwsgi/
或者你可以简单将uwsgi.dll拷贝到你的站点目录的 (在这个情况下,是 /usr/share/asp.net-demos
) /bin
目录下。
mono-index
选项是用来设置当请求一个目录时的搜索文件的。你可以多次指定它。
钩子之下:mono键¶
前一个例子应该会完美运行,但是在内部,做了大量的假设。
整个mono插件依赖于”键”概念。一个键是一个.net应用的唯一标识。在这个例子中,该应用的键是”/usr/share/asp.net-demos”。这是一种键1:1映射到虚拟机的情况。要将一个虚拟机路径映射到一个指定的键,你可以使用
[uwsgi]
http = :9090
http-modifier1 = 15
mono-app = /foobar=/usr/share/asp.net-demos
现在/foobar键映射到/usr/share/asp.net-demos .net应用。
默认情况下,请求键会被映射到DOCUMENT_ROOT变量。因此,在这个新的情况下,/foobar应该是DOCUMENT_ROOT的值。
但是uWSGI http路由器并没有DOCUMENT_ROOT的概念,因此前一个工作要怎么工作呢?这是因为第一个加载的应用通常是默认应用,因此mono插件,不能够查找到一个应用,则返回默认的。
把DOCUMENT_ROOT当成键使用会相当受限的。因此可用–mono-key选项。让我们使用uWSGI内部路由来构建一个海量虚拟主机栈
[uwsgi]
http = :9090
http-modifier1 = 15
mono-key = MONO_APP
route-run = addvar:MONO_APP=/var/www/asp/${HTTP_HOST}
MONO_APP并不是mono插件会用来搜索应用的变量 (取代DOCUMENT_ROOT)。
多亏了内部路径,我们(动态)设置它为指定主机应用的根目录,因此,到example.com的请求将会被映射到/var/www/asp/example.com
并发和fork()不友好¶
由于Mono VM并非 fork()
友好的,因此会为每个worker生成一个新的VM。这确保了你可以在多进程模式下运行你的应用。
Mono拥有相当稳定的多线程支持,并且它与uWSGI的线程支持工作良好。
[uwsgi]
http = :9090
http-modifier1 = 15
mono-app = /usr/share/asp.net-demos
mono-index = index.asp
mono-assembly = /usr/lib/uwsgi/uwsgi.dll
env = MONO_PATH=/usr/lib/uwsgi/
master = true
processes = 4
threads = 20
有了这个而设置,你会生成4个进程,每个进程有20个线程。试着不要依赖于单个进程。尽管在所谓的“企业环境”下,这是一个常见的设置,但是拥有多进程确保了更好的可用性 (多亏了master工作)。这个规则(例如)甚至应用到了 uWSGI服务器中的JVM (更新至1.9) 插件上。
API访问¶
这是一个在进行中的工作。目前,只导出了几个函数。 uWSGI RPC栈 和信号子系统,以及 uWSGI缓存框架 框架将是高优先级的。
技巧¶
如往常一样,uWSGI试着优化(可能的话)你的应用的“常见”操作。提供静态文件服务会被自动加速 (或者当卸载启用的时候进行卸载),并且会缓存所有的路径解析。
在uWSGI上运行CGI脚本¶
CGI插件提供了使用uWSGI服务器运行CGI脚本的能力。
Web服务器/客户端/负载均衡器使用modifier 9
发送请求给uWSGI服务器。然后,uWSGI使用从客户端传过来的变量作为CGI变量(有时会修复它们),调用对应的脚本/可执行文件,再转发其输出到客户端。
该插件会尝试模仿apache的行为,允许你即使在那种原生不支持CGI的服务器,例如nginx上,也能运行CGI脚本。
启用插件¶
默认情况下,不会将CGI插件构建到核心部分中。你需要构建一个嵌入了cgi的二进制文件,或者构建cgi插件。
要构建带CGI支持的单个二进制文件:
curl http://uwsgi.it/install | bash -s cgi /tmp/uwsgi
要将其作为插件编译,
python uwsgiconfig.py --plugin plugins/cgi
或者,在源代码目录:
make PROFILE=cgi
配置CGI模式¶
cgi <[mountpoint=]path>
选项是配置你的CGI环境的主要入口点。
path
可以是一个目录或者一个可执行文件。如果是一个目录,CGI插件将会使用该URI来查找脚本路径。如果传递了一个可执行文件,那么将会运行它,并且会在其环境中设置 SCRIPT_NAME
, SCRIPT_FILENAME
和 PATH_INFO
。
mountpoint
是可选的。你可以用它来映射不同的URI到不同的CGI目录/脚本。
注意¶
- 记得让你的CGI应用使用uWSGI的资源限制和jailing技术 (名字空间,chroot, capability, unshare....),来限制它们可能会造成的损害。
- 从uWSGI 2.0.2开始,你可以通过使用异步模式,拥有甚至更加便宜的并发。
- 如果没有映射到一个辅助函数,那么每个CGI脚本必须拥有读取和执行权限。
例子¶
例子1:启用CGI的哑目录¶
[uwsgi]
plugins = cgi
socket = uwsgi.sock
cgi = /var/www/cgi-bin
每个请求将会搜索 /var/www/cgi-bin
中的指定文件,并执行它。
到 http://example.com/foo.cgi
的请求会运行 /var/www/cgi-bin/foo.cgi
。
例子2:老式cgi-bin目录¶
[uwsgi]
plugins = cgi
socket = uwsgi.sock
cgi = /cgi-bin=/var/lib/cgi-bin
对 http://example.com/cgi-bin/foo
的调用将会运行 /var/lib/cgi-bin/foo
。
例子3:限制使用某些扩展¶
我们只想执行.cgi和.pl文件:
[uwsgi]
plugins = cgi
socket = uwsgi.sock
cgi = /cgi-bin=/var/lib/cgi-bin
cgi-allowed-ext = .cgi
cgi-allowed-ext = .pl
例子4:使用脚本扩展名,映射脚本到解释器¶
我们想要通过 php5-cgi
二进制文件,运行目录 /var/www
中的以 .php
结尾的文件:
[uwsgi]
plugins = cgi
socket = uwsgi.sock
cgi = /var/www
cgi-allowed-ext = .php
cgi-helper = .php=php5-cgi
如果用辅助函数运行一个文件,那么运行的文件将不需要执行权限。当然,辅助函数是需要的。
扩展名比较是不区分大小写的。
例子5:通过nginx,将PHP脚本作为CGI运行¶
配置Nginx来传递.php请求到uWSGI,使用 /var/www/foo
作为文档根目录。
location ~ .php$ {
include uwsgi_params;
uwsgi_param REDIRECT_STATUS 200; # required by php 5.3
uwsgi_modifier1 9;
uwsgi_pass 127.0.0.1:3031;
}
并且像这样配置uWSGI:
[uwsgi]
plugins = cgi
socket = 127.0.0.1:3031
cgi = /var/www/foo
cgi-allowed-ext = .php
cgi-helper = .php=php5-cgi
例子6:并发¶
默认情况下,每个uWSGI worker将能够运行单个CGI脚本。这意味着,使用一个进程将会阻塞传入的请求,直到结束了第一个请求。
添加更多的worker将会缓解这个问题,但是也会消耗大量内存。
线程是个更好的选择。让我们配置每个worker进程运行20个worker线程,这样,并发运行20个CGI脚本。
[uwsgi]
plugins = cgi
threads = 20
socket = 127.0.0.1:3031
cgi = /var/www/foo
cgi-allowed-ext = .php
cgi-helper = .php=php5-cgi
使用异步模式来得到甚至更廉价的并发:
[uwsgi]
plugins = cgi
async = 200
ugreen = true
socket = 127.0.0.1:3031
cgi = /var/www/foo
cgi-allowed-ext = .php
cgi-helper = .php=php5-cgi
这将会生成200个协程,每个能够管理一个CGI脚本(使用几k内存)
例子7:nginx之后到Mailman web接口¶
location /cgi-bin/mailman {
include uwsgi_params;
uwsgi_modifier1 9;
uwsgi_pass 127.0.0.1:3031;
}
[uwsgi]
plugins = cgi
threads = 20
socket = 127.0.0.1:3031
cgi = /cgi-bin/mailman=/usr/lib/cgi-bin/mailman
cgi-index = listinfo
cgi-index
指令指定了当请求一个以斜杠结尾的路径时,运行哪个脚本。这样, /cgi-bin/mailman/
将会被映射到 /cgi-bin/mailman/listinfo
脚本。
例子8:在一个子目录中作为CGI的Viewvc¶
使用挂载点选项。 .. code-block:: ini
[uwsgi] plugins = cgi threads = 20 socket = 127.0.0.1:3031 cgi = /viewvc=/usr/lib/cgi-bin/viewvc.cgi
例子9:使用uWSGI HTTP路由器和 check-static
选项¶
这是一个非常全栈的解决方案,只使用运行在8080端口上的uWSGI。
[uwsgi]
plugins = http, cgi
; bind on port 8080 and use the modifier 9
http = :8080
http-modifier1 = 9
; set the document_root as a placeholder
my_document_root = /var/www
; serve static files, skipping .pl and .cgi files
check-static = %(my_document_root)
static-skip-ext = .pl
static-skip-ext = .cgi
; run cgi (ending in .pl or .cgi) in the document_root
cgi = %(my_document_root)
cgi-index = index.pl
cgi-index = index.cgi
cgi-allowed-ext = .pl
cgi-allowed-ext = .cgi
例子10:优化CGI (高级)¶
你可以避免在每次请求上重新运行解释器的开销,启动时加载解释器(们),并且调用它们中的函数,而不是 execve()
解释器本身。
源代码发行版中的 contrib/cgi_python.c
文件是关于如何优化Python CGI脚本的一个小小的例子。
启动时加载Python解释器,并且在每次 fork()
后,调用 uwsgi_cgi_run_python
。
要编译该库,你可以使用像这样的命令:
gcc -shared -o cgi_python.so -fPIC -I /usr/include/python2.7/ cgi_python.c -lpython2.7
然后映射 .py
文件到 uwsgi_cgi_run_python
函数。
[uwsgi]
plugins = cgi
cgi = /var/www
cgi-loadlib = ./cgi_python.so:uwsgi_cgi_load_python
cgi-helper = .py=sym://uwsgi_cgi_run_python
}}}
记得在辅助函数中给符号加上 sym://
前缀,让uWSGI把它当成一个已加载符号进行搜索,而不是一个磁盘文件。
GCCGO插件¶
uWSGI 1.9.20官方使用一个基于GCCGO的新插件替换了老的 uWSGI Go支持 (只有1.4) 插件。
GCCGO的使用允许更多的特性,并且与uWSGI部署方式更好地集成起来。
GCC 套件 >= 4.8 is expected (强烈建议)。
它是如何工作的¶
当启用插件的时候,会在每次 fork()
之后初始化一个新的go运行时(runtime)。
如果一个 main
Go函数在进程地址空间内可用,那么它将会在Go运行时中执行,否则,控制权会回到uWSGI循环引擎。
为什么不使用纯Go?¶
不幸的是,标准的Go运行时当前是不可嵌入的,并且不支持作为共享库编译代码。
而这两个都是有意义的uWSGI集成所需的。
从GCC 4.8.2起,大大改进了它的 libgo
,并且构建共享库以及初始化Go运行时工作得异常成功 (即使它需要一点……不非常优雅的hack)。
第一个应用¶
你不需要改变你在Go中编写web应用的方式。可以无缝使用 net/http
包:
package main
import "uwsgi"
import "net/http"
import "fmt"
func viewHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "<h1>Hello World</h1>")
}
func main() {
http.HandleFunc("/view/", viewHandler)
uwsgi.Run()
}
唯一不同之处在于调用 uwsgi.Run()
,而不是初始化Go HTTP服务器。
要把代码作为共享库进行构建,只要简单运行:
gcc -fPIC -shared -o myapp.so myapp.go
如果你得到一个关于gcc不能够解析uWSGI符号的错误,那么只需添加 -I<path_to_uwsgi_binary>
到命令行 (见下):
gcc -fPIC -shared -I/usr/bin -o myapp.so myapp.go
现在,在uWSGI之下运行它:
uwsgi --http-socket :9090 --http-socket-modifier1 11 --go-load ./myapp.so
gccgo插件将其自身注册为 modifier1
11,所以,记得设置它来运行Go代码。
uwsgi.gox¶
默认情况下,当构建gccgo配置文件的时候,会创建一个uwsgi.gox文件。在使用uWSGI API构建go应用的时候,可以使用它,来解析符号。
记住,如果你添加包含uwsgi二进制(如前所示)的目录到gcc的包含 (-I path
) 路径,那么二进制文件本身将会被用于解析符号。
共享库 VS 单片二进制文件¶
对于许多开发者来说,Go的主要卖点在于“静态多合一”的二进制方法。
基本上,一个Go应用并没有依赖,因此,一半的常见部署文件就这样自动消失了。
托管Go应用的uWSGI友好方式是让一个uWSGI二进制文件以库的形式加载一个指定的Go应用。
如果这不可接受,那么你可以同时用uWSGI和该Go应用构建单个二进制文件:
CFLAGS=-DUWSGI_GCCGO_MONOLITHIC UWSGI_ADDITIONAL_SOURCES=myapp.go UWSGI_PROFILE=gccgo make
协程¶
多亏了新的GCC split stack特性,在gccgo中,goroutine被健全 (例如,不需要一个完整的pthread) 实现。
在插件自身中,有一个将每个uWSGI核心映射到一个goroutine上的循环引擎可用。
要以goroutine模式启动uWSGI,仅需添加 --goroutines <n>
,其中,<n>是要生成的并发goroutine的最大数。
就像 Gevent循环引擎 ,uWSGI信号处理器是在一个专用的goroutine中执行的。
除此之外,所有的阻塞调用利用 netpoll
Go api。这意味着,你可以在一个goroutine中运行内部路由动作,包括rpc。
选项¶
--go-load <path>
在进程地址空间中加载指定的go共享库--gccgo-load <path>
go-load的别名--go-args <arg1> <arg2> <argN>
设置传递给虚拟go命令行的参数--gccgo-args <arg1> <arg2> <argN>
go-args的别名--goroutines <n>
启用goroutine循环引擎,并指定异步核数目
uWSGI API¶
注解
这部分可能,或者可能不过时。谁知道呢!
不幸的是,只有一点点uWSGI API被移植到了gccgo插件中。对于uWSGI 2.0,会及时添加更多的特性。
当前公开的API函数:
uwsgi.CacheGet(key string, cache string) string
uwsgi.RegisterSignal(signum uint8, receiver string, handler func(uint8)) bool
注意事项¶
- 拜托拜托,不要启用多线程,它将不能用,并且可能永远不能用。
- 所有的uWSGI原生特性 (像内部路由) 是工作在goroutine模式下的。然而,别指望近期诸如Python或者Perl这样的语言会在其上工作。
Symcall插件¶
symcall插件 (modifier 18) 是一个便利的插件,允许你在不需要开发一个完整的uWSGI插件的情况下编写原生uWSGI请求处理器。
你告诉它在启动时加载那个时候符号,然后它就会在每个请求上运行它。
注解
在标准的构建配置文件中,默认内建”symcall”插件。
第一步:准备环境¶
uWSGI二进制自身允许你在无需外部开发包或者头文件的情况下开发插件和库。
第一步是获取 uwsgi.h
C/C++头文件:
uwsgi --dot-h > uwsgi.h
现在,在当前目录下,我们有了一个准备被包含的新鲜的uwsgi.h。
第二步:第一个请求处理器:¶
我们的C处理器将会打印REMOTE_ADDR值以及一对HTTP头。
(称其为mysym.c或者随便你)
#include "uwsgi.h"
int mysym_function(struct wsgi_request *wsgi_req) {
// read request variables
if (uwsgi_parse_vars(wsgi_req)) {
return -1;
}
// get REMOTE_ADDR
uint16_t vlen = 0;
char *v = uwsgi_get_var(wsgi_req, "REMOTE_ADDR", 11, &vlen);
// send status
if (uwsgi_response_prepare_headers(wsgi_req, "200 OK", 6)) return -1;
// send content_type
if (uwsgi_response_add_content_type(wsgi_req, "text/plain", 10)) return -1;
// send a custom header
if (uwsgi_response_add_header(wsgi_req, "Foo", 3, "Bar", 3)) return -1;
// send the body
if (uwsgi_response_write_body_do(wsgi_req, v, vlen)) return -1;
return UWSGI_OK;
}
第三步:将我们的代码作为共享库进行构建¶
uwsgi.h文件是一个ifdef地狱 (所以可能最好不要看得太深入)。
幸运的是,uwsgi二进制通过–cflags选项公开了所有所需的CFLAGS。
我们可以一次到位构建我们的库:
gcc -fPIC -shared -o mysym.so `uwsgi --cflags` mysym.c
现在,你准备好了在uWSGI中加载的mysym.so库了
最后一步:映射symcall插件到 mysym_function
符号¶
uwsgi --dlopen ./mysym.so --symcall mysym_function --http-socket :9090 --http-socket-modifier1 18
通过 --dlopen
,我们在uWSGI进程地址空间中加载了一个共享库。
--symcall
选项允许我们当有modifier1 18的时候,指定调用哪个符号
我们绑定实例到HTTP socket 9090,强制modifier1 18。
钩子和symcall释放:一个TCL处理器¶
我们想要编写一个每次运行以下TCL脚本 (foo.tcl) 的请求处理器:
# call it foo.tcl
proc request_handler { remote_addr path_info query_string } {
set upper_pathinfo [string toupper $path_info]
return "Hello $remote_addr $upper_pathinfo $query_string"
}
我们将定义一个初始化TCL解析器和传递脚本的函数。将会在启动时,于移除权限之后立即调用这个函数。
最后,我们定义引用这个TCL过程并传递参数的请求处理器
#include <tcl.h>
#include "uwsgi.h"
// global interpreter
static Tcl_Interp *tcl_interp;
// the init function
void ourtcl_init() {
// create the TCL interpreter
tcl_interp = Tcl_CreateInterp() ;
if (!tcl_interp) {
uwsgi_log("unable to initialize TCL interpreter\n");
exit(1);
}
// initialize the interpreter
if (Tcl_Init(tcl_interp) != TCL_OK) {
uwsgi_log("Tcl_Init error: %s\n", Tcl_GetStringResult(tcl_interp));
exit(1);
}
// parse foo.tcl
if (Tcl_EvalFile(tcl_interp, "foo.tcl") != TCL_OK) {
uwsgi_log("Tcl_EvalFile error: %s\n", Tcl_GetStringResult(tcl_interp));
exit(1);
}
uwsgi_log("TCL engine initialized");
}
// the request handler
int ourtcl_handler(struct wsgi_request *wsgi_req) {
// get request vars
if (uwsgi_parse_vars(wsgi_req)) return -1;
Tcl_Obj *objv[4];
// the proc name
objv[0] = Tcl_NewStringObj("request_handler", -1);
// REMOTE_ADDR
objv[1] = Tcl_NewStringObj(wsgi_req->remote_addr, wsgi_req->remote_addr_len);
// PATH_INFO
objv[2] = Tcl_NewStringObj(wsgi_req->path_info, wsgi_req->path_info_len);
// QUERY_STRING
objv[3] = Tcl_NewStringObj(wsgi_req->query_string, wsgi_req->query_string_len);
// call the proc
if (Tcl_EvalObjv(tcl_interp, 4, objv, TCL_EVAL_GLOBAL) != TCL_OK) {
// ERROR, report it to the browser
if (uwsgi_response_prepare_headers(wsgi_req, "500 Internal Server Error", 25)) return -1;
if (uwsgi_response_add_content_type(wsgi_req, "text/plain", 10)) return -1;
char *body = (char *) Tcl_GetStringResult(tcl_interp);
if (uwsgi_response_write_body_do(wsgi_req, body, strlen(body))) return -1;
return UWSGI_OK;
}
// all fine
if (uwsgi_response_prepare_headers(wsgi_req, "200 OK", 6)) return -1;
if (uwsgi_response_add_content_type(wsgi_req, "text/plain", 10)) return -1;
// write the result
char *body = (char *) Tcl_GetStringResult(tcl_interp);
if (uwsgi_response_write_body_do(wsgi_req, body, strlen(body))) return -1;
return UWSGI_OK;
}
你可以这样构建它:
gcc -fPIC -shared -o ourtcl.so `./uwsgi/uwsgi --cflags` -I/usr/include/tcl ourtcl.c -ltcl
与前一个例子唯一的差异在于,-I和-l用于添加TCL头文件和库。
所以,让我们允许它:
uwsgi --dlopen ./ourtcl.so --hook-as-user call:ourtcl_init --http-socket :9090 --symcall ourtcl_handler --http-socket-modifier1 18
这里,唯一新的用户是 --hook-as-user call:ourtcl_init
,它在移除特权后引用指定的函数。
注解
代码非线程安全的!如果你想改进这个tcl库来支持多线程,那么最好的方法将是对每个pthread而非对全局使用一个TCL解析器。
注意事项¶
自uWSGI 1.9.21起,多亏了 --build-plugin
选项,开发uWSGI插件变得相当简单。
symcall插件是用于小的代码库/片段,对于更大的需求,考虑开发一个完整的插件。
我们前面已经看到的tcl例子或许是“错误”使用的正确例子 ;)
XSLT插件¶
自uWSGI 1.9.1起,有了一个新的名为”xslt”的插件,它同时作为请求处理器和路由指令实现了XML样式表。
想成功应用转换,你需要一个“文档” (一个XML文档) 和一个样式表 (XSLT文件)。
此外,你可以应用全局参数,并设置一个指定的内容类型 (默认情况下,生成的输出被设置为text/html)。
请求处理器¶
Modifier1 23已被赋给XSLT请求处理器。
文档路径是通过将 PATH_INFO
附加到 DOCUMENT_ROOT
来创建的。
样式表路径根据以下步骤创建:
- 如果设置了一个指定的CGI变量 (通过
--xslt-var
),那么将把它当成样式表路径使用。 - 如果命名像文档加上指定的扩展名 (默认情况下,搜索
.xsl
和.xslt
) 的文件存在,那么将会把它当成样式表路径使用。 - 最后,尝试一系列静态XSLT文件 (由
--xslt-stylesheet
指定)。
例如:
uwsgi --http-socket :9090 --http-socket-modifier1 23 --xslt-ext .bar
如果请求/foo.xml (并且文件存在),那么 DOCUMENT_ROOT``+``foo.xml.bar
将会作为xslt文件被搜索。
uwsgi --http-socket :9090 --http-socket-modifier1 23 --xslt-stylesheet /var/www/myfile1.xslt --xslt-stylesheet /var/www/myfile2.xslt
如果请求/foo.xml (并且文件存在),那么将会试着用 /var/www/myfile1.xslt
。如果它不存在,那么将会用 /var/www/myfile2.xslt
来替代。
uwsgi --http-socket :9090 --http-socket-modifier1 23 --xslt-var UWSGI_XSLT
如果请求/foo.xml (并且文件存在),那么 UWSGI_XSLT
变量 (你可以从web服务器对其进行设置) 的内容就会被当成样式表路径使用。
如果 QUERY_STRING
可用,那么它的项将会作为全局参数传递给样式表。
例如,如果你请求 /foo.xml?foo=bar&test=uwsgi
,那么”foo” (值为”bar”)和 “test” (值为”uwsgi”)将会作为全局变量被传递:
<xsl:value-of select="$foo"/>
<xsl:value-of select="$test"/>
路由指令¶
该插件将自身注册为名为”xslt”的内部路由指令。它或许比请求插件更通用得多。
它的语法相当简单:
[uwsgi]
plugin = xslt
route = ^/foo xslt:doc=${DOCUMENT_ROOT}/${PATH_INFO}.xml,stylesheet=/var/www/myfunction.xslt,content_type=text/html,params=foo=bar&test=unbit
这将会应用 /var/www/myfunction.xslt
转换到 foo.xml
,并将其作为 text/html
返回。
该路由指令唯一需要的参数是 doc
和 stylesheet
。
SSI (服务器端包含,Server Side Includes) 插件¶
服务器端包含是一种编写动态web页面的“老式”方法。
通常把它当成一个模板系统,而不是一个全功能语言。
uWSGI SSI插件的主要目的是拥有一个可以访问uWSGI API的快速的模板系统。
在写这篇文章的时候,即2013年3月,这个插件还处于测试版本,实现了少于30%的SSI标准,关注点在于把uWSGI API当成SSI命令公开。
把它当成一个请求处理器使用¶
这个插件有一个官方的modifier1,数字19。
[uwsgi]
plugin = ssi
http = :9090
http-modifier1 = 19
http-var = DOCUMENT_ROOT=/var/www
该插件把文件名作为 DOCUMENT_ROOT``+``PATH_INFO
构建。然后,将这个文件作为服务器端包含文档解析。
DOCUMENT_ROOT
和PATH_INFO
都是必须的,否则将会返回一个500错误。
Nginx的一个样例配置如下:
location ~ \.shtml$ {
root /var/www;
include uwsgi_params;
uwsgi_pass 127.0.0.1:3031;
uwsgi_modifier1 19;
}
而对于uWSGI大概像这样……
[uwsgi]
plugin = ssi
socket = 127.0.0.1:3031
把SSI作为一个路由动作使用¶
更通用的方式是把SSI解析器作为一个路由动作使用。
[uwsgi]
plugin = ssi
http-socket = :9090
route = ^/(.*) ssi:/var/www/$1.shtml
警告
对于所有的路由动作,文件路径上并没有运行更高层次自定义的检查。如果你传递不信任路径给SSI动作,那么你应该清理它们 (你可以再次使用路由,检查..的存在,或者其他危险符号)。
并且考虑到上述告诫,当作为路由动作使用时, DOCUMENT_ROOT
或者 PATH_INFO
并不是必须的,因为传递的参数包含了完整的文件系统路径。
状态¶
- 这个插件是完全线程安全的,并且非常快。
- 非常少的命令可用,不久会添加更多命令。
uWSGI V8支持¶
Building¶
需要 libv8
头文件来构建该插件。官方用于V8的modifier1值为 ‘24’.
RPC¶
function part1(request_uri, remote_addr) {
return '<h1>i am part1 for ' + request_uri + ' ' + remote_addr + "</h1>" ;
}
function part2(request_uri, remote_addr) {
return '<h2>i am part2 for ' + request_uri + ' ' + remote_addr + "</h2>" ;
}
function part3(request_uri, remote_addr) {
return '<h3>i am part3 for ' + request_uri + ' ' + remote_addr + "</h3>" ;
}
uwsgi.register_rpc('part1', part1);
uwsgi.register_rpc('part2', part2);
uwsgi.register_rpc('part3', part3);
ciao = function(saluta) {
uwsgi.log("I have no idea what's going on.");
return "Ciao Ciao";
}
uwsgi.register_rpc('hello', ciao);
信号处理函数¶
function tempo(signum) {
uwsgi.log("e' passato 1 secondo");
}
uwsgi.register_signal(17, '', tempo);
多线程和多进程¶
Mule¶
uWSGI API¶
JSGI 3.0¶
exports.app = function (request) {
uwsgi.log("Hello! I am the app.\n");
uwsgi.log(request.scheme + ' ' + request.method + ' ' + request.scriptName + ' ' + request.pathInfo + ' ' + request.queryString + ' ' + request.host);
uwsgi.log(request.serverSoftware);
return {
status: 200,
headers: {"Content-Type": "text/plain", "Server": ["uWSGI", "v8/plugin"]},
body: ["Hello World!", "I am V8"]
};
}
uwsgi --plugin v8 --v8-jsgi myapp.js --http-socket :8080 --http-socket-modifier1 24
CommonJS¶
- Require: OK
- Binary/B: NO
- System/1.0: 进行中
- IO/A: NO
- Filesystem/A: NO
GridFS插件¶
从uWSGI 1.9.5开始,发布了一个”GridFS”插件。它同时公开了一个请求处理器和一个内部路由函数。它的官方modifier是‘25’。路由指令则是”gridfs”。该插件是用C++写的。
必要条件和安装¶
要构建这个插件,你需要 libmongoclient
头文件 (和一个正常运作的C ++编译器)。在一个类似于Debian的系统中,你可以这样做。
apt-get install mongodb-dev g++
有一个用于gridfs的构建配置文件可用:
UWSGI_PROFILE=gridfs make
或者你可以将它作为插件构建:
python uwsgiconfig.py --plugin plugins/gridfs
要快速安装单片构建,你可以使用网络安装程序:
curl http://uwsgi.it/install | bash -s gridfs /tmp/uwsgi
这将会安装一个启用了gridfs的uwsgi二进制文件。
独立快速入门¶
这是一个独立的配置,它盲目映射传入的 PATH_INFO
到名为”test”的GridFS db中的项:
[uwsgi]
; you can remove the plugin directive if you are using a uWSGI gridfs monolithic build
plugin = gridfs
; bind to http port 9090
http-socket = :9090
; force the modifier to be the 25th
http-socket-modifier1 = 25
; map gridfs requests to the "test" db
gridfs-mount = db=test
假设在你的GridFS中存储了myfile.txt文件,即”/myfile.txt”,那么运行以下命令:
curl -D /dev/stdout http://localhost:9090/myfile.txt
然后你应该可以获取到这个文件。
最初斜杠的问题¶
一般来说, PATH_INFO
都会带一个’/’前缀。如果你并不使用绝对路径名来存储项,那么这在GridFS路径解析中会引发问题。为了解决这个问题,你可以让 gridfs
插件跳过最初的斜杠:
[uwsgi]
; you can remove the plugin directive if you are using a uWSGI gridfs monolithic build
plugin = gridfs
; bind to http port 9090
http-socket = :9090
; force the modifier to be the 25th
http-socket-modifier1 = 25
; map gridfs requests to the "test" db
gridfs-mount = db=test,skip_slash=1
现在,它将会搜索”myfile.txt”,以取代搜索/myfile.txt。
多挂载点 (和服务器)¶
你可以在不同SCRIPT_NAME (或者UWSGI_APPID)下挂载不同的GridFS数据库。如果你的web服务器能够正确管理 SCRIPT_NAME
变量,那么你不需要任何额外的设置 (除了–gridfs-mount)。否则,不要忘了添加–manage-script-name选项
[uwsgi]
; you can remove the plugin directive if you are using a uWSGI gridfs monolithic build
plugin = gridfs
; bind to http port 9090
http-socket = :9090
; force the modifier to be the 25th
http-socket-modifier1 = 25
; map gridfs requests to the "test" db
gridfs-mount = db=test,skip_slash=1
; map /foo to db "wolverine" on server 192.168.173.17:4040
gridfs-mount = mountpoint=/foo,server=192.168.173.17:4040,db=wolverine
; map /bar to db "storm" on server 192.168.173.30:4040
gridfs-mount = mountpoint=/bar,server=192.168.173.30:4040,db=storm
; force management of the SCRIPT_NAME variable
manage-script-name = true
curl -D /dev/stdout http://localhost:9090/myfile.txt
curl -D /dev/stdout http://localhost:9090/foo/myfile.txt
curl -D /dev/stdout http://localhost:9090/bar/myfile.txt
这样,每个请求将会映射到一个不同的GridFS服务器。
副本集合¶
如果你正使用一个副本集合,那么你可以通过这个语法在你的uWSGI配置中使用它:<replica>server1,server2,serverN...
[uwsgi]
http-socket = :9090
http-socket-modifier1 = 25
gridfs-mount = server=rs0/ubuntu64.local\,raring64.local\,mrspurr-2.local,db=test
注意用来转义服务器列表的反斜杠。
前缀¶
和移除初始斜杠一样,你或许需要给每个项的名字加前缀:
[uwsgi]
http-socket = :9090
http-socket-modifier1 = 25
gridfs-mount = server=rs0/ubuntu64.local\,raring64.local\,mrspurr-2.local,db=test,prefix=/foobar___
对/test.txt的请求将会被映射到/foobar___/test.txt
而
[uwsgi]
http-socket = :9090
http-socket-modifier1 = 25
gridfs-mount = server=rs0/ubuntu64.local\,raring64.local\,mrspurr-2.local,db=test,prefix=/foobar___,skip_slash=1
将会映射到/foobar___test.txt
MIME类型和文件名¶
默认情况下,文件的MIME类型是由GridFS中存储的文件名而来的。这个文件名可能不会映射到有效请求的URI,或者你可能不想要为你的响应设置一个 content_type
。或者你也许想要允许一些其他系统设置它。如果你想要禁用MIME类型生成,那么只需添加 no_mime=1
到挂载选项。
[uwsgi]
http-socket = :9090
http-socket-modifier1 = 25
gridfs-mount = server=ubuntu64.local,db=test,skip_slash=1,no_mime=1
如果你想要你的响应使用原始值来设置文件名 (存储在GridFS中的那个),则添加 orig_filename=1
[uwsgi]
http-socket = :9090
http-socket-modifier1 = 25
gridfs-mount = server=ubuntu64.local,db=test,skip_slash=1,no_mime=1,orig_filename=1
超时¶
你可以通过添加 timeout=N
到选项中,来设置低层次MongoDB操作的超时时间:
[uwsgi]
http-socket = :9090
http-socket-modifier1 = 25
; set a 3 seconds timeout
gridfs-mount = server=ubuntu64.local,db=test,skip_slash=1,timeout=3
MD5和ETag头部¶
GridFS存储每个文件的MD5哈希值。你可以添加这个信息到你的响应头,作为ETag (十六进制格式的MD5)或者Content-MD5 (in Base64)的值。对于添加ETag头部,使用
etag=1
,而对于添加Content-MD5,则使用 md5=1
。没有什么可以阻止你同时添加这两个头到响应中。
[uwsgi]
http-socket = :9090
http-socket-modifier1 = 25
; set a 3 seconds timeout
gridfs-mount = server=ubuntu64.local,db=test,skip_slash=1,timeout=3,etag=1,md5=1
多线程¶
这个插件是完全线程安全的,因此考虑使用多线程来提高并发:
[uwsgi]
http-socket = :9090
http-socket-modifier1 = 25
; set a 3 seconds timeout
gridfs-mount = server=ubuntu64.local,db=test,skip_slash=1,timeout=3,etag=1,md5=1
master = true
processes = 2
threads = 8
这将会生成2个由master监控的进程,每个进程有8个线程,总共16个线程。
与Nginx组合¶
这与其他插件没有什么不同:
location / {
include uwsgi_params;
uwsgi_pass 127.0.0.1:3031;
uwsgi_modifier1 25;
}
这是确保设置 uwsgi_modifier1
值来保证所有的请求都被路由到GridFS。
[uwsgi]
socket = 127.0.0.1:3031
gridfs-mount = server=ubuntu64.local,db=test,skip_slash=1,timeout=3,etag=1,md5=1
master = true
processes = 2
threads = 8
‘gridfs’内部路由动作¶
这个插件公开了一个’gridfs’动作,它简单返回一个项:
[uwsgi]
socket = 127.0.0.1:3031
route = ^/foo/(.+).jpg gridfs:server=192.168.173.17,db=test,itemname=$1.jpg
选项和request插件的选项是一样的,只有”itemname”是例外。它指定了GridFS db中的对象名。
注意事项¶
- 如果你不指定一个服务器地址,就会假设使用127.0.0.1:27017。
- 在异步模式下使用这个插件并非官方支持的,但是也许能用。
- 如果你不明白为嘛请求并不提供你的GridFS项,那么考虑添加
--gridfs-debug
选项。它将会在uWSGI日志中打印被请求项。
GlusterFS插件¶
自uWSGI 1.9.15起可用
官方modifier1: 27
‘glusterfs’插件允许你使用自GlusterFS 3.4起可用的glusterfs api,直接提供存储在glusterfs文件系统中的文件
这个方法 (与通过fuse或者nfs提供相比) 在性能和易于部署方面有许多优势。
第1步:安装glusterfs¶
我们从官方渠道构建glusterfs,在3个节点上(192.168.173.1, 192.168.173.2, 192.168.173.3),把它安装在/opt/glusterfs。
./configure --prefix=/opt/glusterfs
make
make install
现在,这样启动配置/控制守护进程:
/opt/glusterfs/sbin/glusterd
从现在起,我们可以配置我们的集群
第2步:第一个集群¶
运行控制客户端来访问glusterfs shell:
/opt/glusterfs/sbin/gluster
第一步是“发现”其他节点:
# do not run on node1 !!!
peer probe 192.168.173.1
# do not run on node2 !!!
peer probe 192.168.173.2
# do not run on node3 !!!
peer probe 192.168.173.3
记住,你无需对运行着glusterfs控制台的机器的相同的地址运行”peer probe”。你必须在集群的每个节点上重复这个过程。
现在,我们可以创建一个副本卷 (每个节点上都必须存在/exports/brick001目录):
volume create unbit001 replica 3 192.168.173.1:/exports/brick001 192.168.173.2:/exports/brick001 192.168.173.3:/exports/brick001
并启动它:
volume start unbit001
现在,你应该可以挂载你的glusterfs文件系统,并开始在其中写文件了 (你可以使用nfs或者fuse)
第3步:uWSGI¶
一个名为’glusterfs’的构建配置文件已经可以用了,所以你可以简单这样做:
PKG_CONFIG_PATH=/opt/glusterfs/lib/pkgconfig/ UWSGI_PROFILE=glusterfs make
这个配置文件目前禁用了’matheval’支持,因为glusterfs库使用带相同函数前缀的bison/yacc (引发命名冲突)。
现在,你可以启动你的HTTP服务来快速提供glusterfs文件 (记住,不涉及任何nfs或者fuse):
[uwsgi]
; bind on port 9090
http-socket = :9090
; set the default modifier1 to the glusterfs one
http-socket-modifier1 = 27
; mount our glusterfs filesystem
glusterfs-mount = mountpoint=/,volume=unbit001,server=192.168.173.1:0
; spawn 30 threads
threads = 30
高可用性¶
主要的GlusterFS卖点是高可用性。使用前面的设置,我们引入了一个带控制守护进程的SPOF。
‘server’选项允许你指定多个控制守护进程 (尝试它们直到有一个响应)
[uwsgi]
; bind on port 9090
http-socket = :9090
; set the default modifier1 to the glusterfs one
http-socket-modifier1 = 27
; mount our glusterfs filesystem
glusterfs-mount = mountpoint=/,volume=unbit001,server=192.168.173.1:0;192.168.173.2:0;192.168.173.3:0
; spawn 30 threads
threads = 30
‘0’端口是glusterfs惯例,它意味着“默认端口” (一般是24007)。你可以指定任何你需要/想要的端口。
多个挂载点¶
如果你的web服务器 (像nginx或者uWSGI http路由器) 能够设置协议变量 (像SCRIPT_NAME或者UWSGI_APPID),那么你可以在同一个实例中挂载多个glusterfs文件系统:
[uwsgi]
; bind on port 9090
http-socket = :9090
; set the default modifier1 to the glusterfs one
http-socket-modifier1 = 27
; mount our glusterfs filesystem
glusterfs-mount = mountpoint=/,volume=unbit001,server=192.168.173.1:0;192.168.173.2:0;192.168.173.3:0
glusterfs-mount = mountpoint=/foo,volume=unbit002,server=192.168.173.1:0;192.168.173.2:0;192.168.173.3:0
glusterfs-mount = mountpoint=/bar,volume=unbit003,server=192.168.173.1:0;192.168.173.2:0;192.168.173.3:0
; spawn 30 threads
threads = 30
内部路由¶
uWSGI内部路由 让你可以重写请求来改变请求文件。目前,glusterfs插件只使用PATH_INFO,因此,你可以通过’setpathinfo’指令来改变它
也支持缓存。看看教程 (链接在主页上) ,获得一些很酷的点子吧
使用capabilities (在Linux上)¶
如果你的集群要求客户端绑定到特权端口 (<1024) ,而你不想要改动它 (而显然,你不想要作为root运行uWSGI),那么你或许想要给予你的uWSGI实例NET_BIND_SERVICE capability。只需确保你有一个启用了capabilities的uWSGI,然后添加
... --cap net_bind_service ...
到所有你想要连接到glusterfs的实例
RADOS插件¶
自uWSGI 1.9.16起可用,自uWSGI 2.0.6起稳定
官方modifier1: 28
作者:Javier Guerra, Marcin Deranek, Roberto De Ioris, Sokolov Yura 又名funny_falcon
‘rados’插件让你可以使用librados API,直接提供存储在一个Ceph集群中的对象。
注意,不是CephFS文件系统,也不是’radosgw’ S3/Swift兼容层;RADOS是裸对象存储层。
第1步:Ceph集群和内容¶
如果你想尝试最小Ceph安装,那么你可以遵循这个指导:http://ceph.com/docs/master/start/。注意,你只需要OSD和MON守护进程,MDS只在CephFS文件系统的时候是必须的。
一旦运行了它,你应该有一个配置文件 (默认在/etc/ceph/ceph.con上),并且应该能够使用 rados 功能。
rados lspools
默认情况下,你应该至少拥有’data’, ‘metadata’和’rbd’池。现在添加一些内容到’data’池。例如,如果你有一个’list.html’文件和子目录’imgs/’下的图片’first.jpeg’, ‘second.jpeg’:
rados -p data put list.html list.html
rados -p data put imgs/first.jpeg imgs/first.jpeg
rados -p data put imgs/second.jpeg imgs/second.jpeg
rados -p data ls -
注意,RADOS没有目录的概念,但是对象名可以包含斜线。
第2步:uWSGI¶
有一个名为’rados’的构建配置文件可以用,因此你可以简单这样做:
make PROFILE=rados
或者
python uwsgiconfig.py --build rados
或者使用安装程序
# this will create a binary called /tmp/radosuwsgi that you will use instead of 'uwsgi'
curl http://uwsgi.it/install | bash -s rados /tmp/radosuwsgi
显然,你可以将rados支持当成插件构建
uwsgi --build-plugin plugins/rados/
或者旧式的:
python uwsgiconfig.py --plugin plugins/rados/
现在,你可以启动一个HTTP服务器来提供RADOS对象服务了:
[uwsgi]
; bind on port 9090
http-socket = :9090
; set the default modifier1 to the rados one
http-socket-modifier1 = 28
; mount our rados pool
rados-mount = mountpoint=/rad/,pool=data,config=/etc/ceph/ceph.conf
; spawn 30 threads
threads = 30
‘rados-mount’参数接收多个子参数:
- mountpoint: 必需,RADOS对象将会出现的URL前缀。
- pool: 必需,提供服务的RADOS池。
- config: 可选,ceph配置文件的路径。
- timeout: 可选,设置操作的超时时间,以秒为单位
- allow_put: 允许调用
PUT
HTTP方法来存储新的对象- allow_delete: 允许调用
DELETE
HTTP方法来移除对象- allow_mkcol: 允许调用
MKCOL
HTTP方法来创建新的池- allow_propfind: (要求版本uWSGI 2.1) 允许调用WebDAV
PROPFIND
方法- buffer_size:
GET
请求的最大缓冲大小,以字节为单位 (最小8192,最大的16777216,默认是131072)- put_buffer_size:
PUT
请求的最大缓冲大小 (默认为buffer_size)
在这个例子中,你的内容地址将会是http://localhost:9090/rad/list.html, http://localhost:9090/rad/imgs/first.jpeg和http://localhost:9090/rad/imgs/second.jpeg。
高可用性¶
RADOS存储系统是全分布的,只要使用相同的’ceph.conf’,在多个机器上启动几个uWSGI worker,那么所有的worker都能看到同一个池。如果它们都在相同的挂载点上提供服务,那么你会得到一个抗故障的RADOS-HTTP网关。
多挂载点¶
你可以声明多个’rados-mount’项,每一个会定义一个新的挂载点。通过这种方式,你可以在不同的URL上公开不同的RADOS池。
HTTP方法¶
支持以下方法:
- GET -> 检索资源
- HEAD -> 和GET一样,但是没有请求体
- OPTIONS -> (要求版本uWSGI 2.1) 返回允许的HTTP方法和WebDAV支持的列表
- PUT -> 要求mountpoint选项中有allow_put,存储资源到ceph:curl -T /etc/services http://localhost:8080/services
- MKCOL -> 要求mountpoint选项中有allow_mkcol,创建一个新的池:curl -X MKCOL http://localhost:8080/anewpool (将会创建池’anewpool’)
- DELETE -> 要求mountpoint选项中有allow_delete,移除一个对象
- PROPFIND -> 要求mountpoint选项中有allow_propfind (uWSGI 2.1+),实现WebDAV PROPFIND方法
特性¶
- 支持多进程
- 异步支持功能齐全,ugreen挂起引擎是唯一支持的引擎:
[uwsgi]
; bind on port 9090
http-socket = :9090
; set the default modifier1 to the rados one
http-socket-modifier1 = 28
; mount our rados pool
rados-mount = mountpoint=/rad/,pool=data,config=/etc/ceph/ceph.conf
; spawn 1000 async cores
async = 1000
; required !!!
ugreen = true
缓存样例¶
强烈建议使用缓存来改进性能和减少Ceph集群上的负载。这是一个好例子:
[uwsgi]
; create a bitmap cache with max 1000 items storable in 10000 4k blocks
cache2 = name=radoscache,items=1000,blocks=10000,blocksize=4096,bitmap=1
; check every object ending with .html in the 'radoscache' cache
route = \.html$ cache:key=${PATH_INFO},name=radoscache,content_type=text/html
; if not found, store it at the end of the request for 3600 seconds (this will automatically enable Expires header)
route = \.html$ cachestore:key=${PATH_INFO},name=radoscache,expires=3600
; general options
; master is always a good idea
master = true
; bind on http port 9090 (better to use a uwsgi socket behind a proxy like nginx)
http-socket = :9090
; set the default modifier1 to the rados one
http-socket-modifier1 = 28
; mount our rados 'htmlpages' pool
rados-mount = mountpoint=/,pool=htmlpages
; spawn multiple processes and threads
processes = 4
threads = 8
要测试缓存行为,诸如uwsgicachetop (https://pypi.python.org/pypi/uwsgicachetop) 这样的工具将非常有用。
更多关于缓存的信息在这里: CachingCookbook
安全注意事项¶
启用MKCOL, PUT和DELETE可能会有很高的安全风险。
将它们与内部路由框架结合,以添加鉴权/认证策略:
[uwsgi]
master = true
; bind on http port 9090 (better to use a uwsgi socket behind a proxy like nginx)
http-socket = :9090
; set the default modifier1 to the rados one
http-socket-modifier1 = 28
; mount our rados 'htmlpages' pool
rados-mount = mountpoint=/,pool=htmlpages,allow_put=1,allow_mkcol=1
; spawn multiple processes and threads
processes = 4
threads = 8
; permit PUT only to authenticated 'foo' user
route-if = equal:${REQUEST_METHOD};PUT basicauth:my secret area,foo:bar
; allow MKCOL only from 127.0.0.1
route-if = equal:${REQUEST_METHOD};MKCOL goto:check_localhost
; end of the chain
route-run = last:
route-label = check_localhost
; if REMOTE_ADDR = 127.0.0.1 -> continue to rados plugin
route-remote-addr = ^127\.0\.0\.1$ continue:
; otherwise break with 403
route-run = break:403 Forbidden
注意事项¶
- 这个插件自动启用MIME type引擎。
- 无目录索引支持。在rados/ceph上下文中,它并无意义。
- 你应该在你的uWSGI实例中移除特权,因此,确保你把正确的权限给予了ceph keyring。
- 如果你用它来获取/存储大对象,那么考虑提高
buffer_size
。4194304是个非常高性能的值,如果你想节约内存,那么1048576也不错。 - 支持放入Erasure编码的池。会自动调整
put_buffer_size
来满足池对齐要求。
其他插件¶
Pty插件¶
-自uWSGI 1.9.15起可用,Linux, OpenBSD, FreeBSD和OSX皆支持。
这个插件允许你附加伪终端到你的应用上。
目前,只能在第一个worker上附加(并通过网络公开)伪终端服务器 (未来会移除该限制)。
该插件还公开了一个客户端模式 (避免你把它跟netcat, telnet或者screen设置混在一起)
构建它¶
默认构建配置文件中并不包含这个插件,因此,你必须手工构建它:
python uwsgiconfig.py --plugin plugins/pty [profile]
(如果你不使用默认的构建配置文件,那么记得指定它)
例子1:Rack应用共享调试¶
UWSGI_PROFILE=ruby2 UWSGI_EMBED_PLUGINS=pty make
./uwsgi --rbshell="require 'pry';binding.pry" --socket /tmp/foo.socket --master --pty-socket :5000
./uwsgi --pty-connect :5000
例子2:IPython控制线程¶
import IPython
from uwsgidecorators import *
# only worker 1 has the pty attached
@postfork(1)
@thread
def tshell():
while True:
IPython.embed()
SPNEGO认证¶
使用LDAP配置uWSGI¶
可以使用LDAP配置uWSGI。LDAP是一种集中uWSGI服务器的大型集群的配置的灵活方法。
注解
必须在 building
uWSGI时启用LDAP支持。要求
libldap 库。
导入uWSGIConfig模式¶
带 –ldap-schema 或者 –ldap-schema-ldif 参数运行uWSGI会让它输出一个标准的LDAP模式 (或者一个LDIF文件),你可以将其导入到你的服务器中。
LDIF dump的一个例子¶
This is an 这是带有 uWSGIConfig 项的一个OpenLDAP服务器的LDIF dump,运行着一个Trac实例。
dn: dc=projects,dc=unbit,dc=it
objectclass: uWSGIConfig
objectclass: domain
dc: projects
uWSGIsocket: /var/run/uwsgi/projects.unbit.it.sock
uWSGIhome: /accounts/unbit/tracvenv
uWSGImodule: trac.web.main:dispatch_request
uWSGImaster: TRUE
uWSGIprocesses: 4
uWSGIenv: TRAC_ENV=/accounts/unbit/trac/uwsgi
使用¶
你仅需传递一个有效的LDAP url给 –ldap 选项。只有返回的第一个项才会被当成配置使用。
uwsgi –ldap ldap://ldap.unbit.it/dc=projects,dc=unbit,dc=it
如果你想要一个带有子范围的过滤器 (这将会返回树 dc=projects,dc=unbit,dc=it 下 ou=Unbit 的第一条记录):
uwsgi –ldap ldap://ldap.unbit.it/dc=projects,dc=unbit,dc=it?sub?ou=Unbit
中断/弃用特性¶
集成uWSGI和Erlang¶
警告
自1.9.20起,Erlang支持已经不能用了。新方案正在开发中。
uWSGI服务器可以作为一个Erlang C-Node,并且和Erlang节点交换数据以及进行RPC。
构建¶
首先,你需要 ei
库和头文件。在官方的Erlang包中可以找到它们。如果你在Debian/Ubuntu之上,那么安装
erlang-dev
包。可以嵌入Erlang支持,或者将其当成一个插件进行构建。要嵌入,则添加 erlang
和 pyerl
插件到你的构建配置文件中。
embedded_plugins = python, ping, nagios, rpc, fastrouter, http, ugreen, erlang, pyerl
或者将其作为插件构建
python uwsgiconfig --plugin plugins/erlang
python uwsgiconfig --plugin plugins/pyerl
Erlang插件将允许uWSGI成为一个Erlang C-Node。 pyerl
插件将添加Erlang函数到Python插件中。
激活Erlang支持¶
你只需要设置两个选项来在你的启用Erlang的uWSGI构建中启用Erlang支持。 erlang
选项设置了你的uWSGI服务器的Erlang节点名。它可以以简单或扩展格式指定:
nodename@ip
nodename@address
nodename
erlang-cookie
选项为内部节点通信设置cookie。如果你不指定它,那么会从~/.erlang.cookie
文件获取这个值。
要运行带启用Erlang的uWSGI:
uwsgi --socket :3031 --erlang testnode@192.168.173.15 --erlang-cookie UUWSGIUWSGIU -p 2
一个简单的RPC hello world例子¶
定义一个新的Erlang模块,它只导出一个简单的函数。
-module(uwsgitest). -export([hello/0]). hello() -> 'hello world !'.
启动
erl
shell,指定节点名和(最终) (eventually) cookie:erl -name testnode@192.168.173.1
编译uwsgitest Erlang模块
c(uwsgitest). {ok,uwsgitest}
... 然后试着运行
hello
函数:uwsgitest:hello(). 'hello world !'
不错 - 现在,我们的Erlang模块就能用了,我们准备好RPC了!返回你的uWSGI服务器机器,然后定义一个新的WSGI模块 —— 让我们称之为
erhello.py
.
import uwsgi
def application(env, start_response):
testnode = uwsgi.erlang_connect("testnode@192.168.173.1")
start_response('200 OK', [('Content-Type', 'text/plain')])
yield uwsgi.erlang_rpc(testnode, "uwsgitest", "hello", [])
uwsgi.erlang_close(testnode)
或者快速方式
import uwsgi
def application(env, start_response):
start_response('200 OK', [('Content-Type', 'text/plain')])
yield uwsgi.erlang_rpc("testnode@192.168.173.1", "uwsgitest", "hello", [])
现在,使用这个新的模块启动uWSGI服务器:
uwsgi --socket :3031 --erlang testnode@192.168.173.15 --erlang-cookie UUWSGIUWSGIU -p 2 -w erhello
在你的浏览器中访问启用了uWSGI的web服务器,你应该会看到你的Erlang RPC调用的输出。
Python-Erlang映射¶
uWSGI服务器试着根据下面这张表将Erlang类型转换成Python对象。
Python | Erlang | 注意 |
---|---|---|
str | binary | |
unicode | atom | 受内部atom大小限制 |
int/long | int | |
list | list | |
tuple | tuple | |
3-tuple | pid |
发送消息给Erlang节点¶
Erlang的最强大的特性之一是节点间消息传递系统。uWSGI也可以与Erlang节点进行通信。让我们定义一个新的Erlang模块,它简单回显任何我们发送给它的东西。
-module(uwsgiecho).
-export([start/0, loop/0, echo/1]).
echo(Message) ->
{i_am_echo , Message}.
loop() ->
receive
Message1 ->
io:format("received a message~n"),
{ useless, 'testnode@192.168.173.15' } ! echo(Message1)
end,
loop().
start() ->
register(echoer, spawn(uwsgiecho, loop, [])).
记得用Erlang register
函数注册你的进程。使用pid来标志进程是有问题的。现在,你可以用
uwsgi.erlang_send_message()
来发送消息。
uwsgi.erlang_send_message(node, "echoer", "Hello echo server !!!" )
第二个参数是注册进程名。如果你不指定名字,那么传递一个Python的3元素元组会被解析为一个Pid。如果你的Erlang服务器返回消息给你的请求,那么你可以用
uwsgi.erlang_recv_message()
来接收它们。记住,即使Erlang需要一个进程名/pid来发送消息,但是它们将被uWSGI所忽略。
接收Erlang消息¶
有时,你想直接从一个Erlang节点发送消息给uWSGI服务器。要接收Erlang消息,你必须在你的Python代码中注册”Erlang进程”。
import uwsgi
def erman(arg):
print "received an erlang message:", arg
uwsgi.erlang_register_process("myprocess", erman)
现在,在Erlang中,你可发送消息给你注册的”myprocess”进程了:
{ myprocess, 'testnode@192.168.173.15' } ! "Hello".
RPC¶
你可以直接从Erlang调用uWSGI uWSGI RPC栈 函数。
rpc:call('testnode@192.168.173.15', useless, myfunction, []).
这将会调用一个配置为Erlang节点的uWSGI服务器上的”myfunction” uWSGI RPC函数。
连接持久化¶
在高负载站点上,为每一个Erlang交互打开和关闭连接是很过分的。在你的应用初始化过程中用
uwsgi.erlang_connect()
打开一个连接,并让文件描述符保持。
Mnesia又如何?¶
当你需要一个高可用性站点的时候,我们建议你使用 Mnesia 。构建一个Erlang模块来公开所有你需要的数据库交互,并且使用
uwsgi.erlang_rpc()
来与之交互。
我可以在uWSGI之上运行 EWGI 应用吗?¶
现在,是不行的。最好的方式是开发一个插件,并且为EWGI应用分配一个特殊的modifier。
那在此之前,你可以在Python代码中封装即将到来的请求为EWGI形式,并使用 uwsgi.erlang_rpc()
来调用你的Erlang应用。
管理标志¶
警告
这个特性当前可能已暂停或弃用。
使用管理标志(Management Flag)系统,你可以远程修改uWSGI栈的某些方面的行为,而无需下线服务器。
注解
一个更综合的重设置系统可能正在开发中。
所有的标志都是用一个无符号的32位值(因此块大小总是4),它包含为这个标志设置的值。如果你不指定该值,那么仅发送uWSGI头,服务型将会把它当成一种读请求。
标志 | 动作 | 描述 |
---|---|---|
0 | logging | 启用/禁用日志记录 |
1 | max_requests | 设置每个worker的最大请求数 |
2 | socket_timeout | 修改内部socket超时 |
3 | memory_debug | 启用/禁用内存调试/报告 |
4 | master_interval | 设置master进程检查间隔 |
5 | harakiri | 设置/取消设置harakiri超时 |
6 | cgi_mode | 启用/禁用cgi模式 |
7 | threads | 启用/禁用线程(当前未实现) |
8 | reaper | 启用/禁用进程reaper |
9 | log-zero | 启用/禁用记录响应大小为零的请求 |
10 | log-slow | 设置/取消设置记录缓慢请求 |
11 | log-4xx | 启用/禁用记录4xx响应状态的请求 |
12 | log-5xx | 启用/禁用记录5xx响应状态的请求 |
13 | log-big | 设置/取消设置记录具有大的响应大小的请求 |
14 | log-sendfile | 设置/取消设置记录sendfile请求 |
15 | backlog-status | 报告backlog队列的当前大小(Linux仅限tcp) |
16 | backlog-errors | 报告backlog队列中的错误数(Linux仅限tcp) |
myadmin工具¶
包含一个简单(丑陋)的脚本, myadmin
,用来远程更改管理标志:
# disable logging on the uWSGI server listening on 192.168.173.17 port 3031
./uwsgi --no-server -w myadmin --pyargv "192.168.173.17:3031 0 0"
# re-enable logging
./uwsgi --no-server -w myadmin --pyargv "192.168.173.17:3031 0 1"
# read a value:
./uwsgi --no-server -w myadmin --pyargv "192.168.173.17:3031 15"
uWSGI Go支持 (只有1.4)¶
警告
自1.9.20起,Go插件已经被 GCCGO插件 插件取代了。
从uWSGI 1.4-dev起,你可以在你的uWSGI栈中托管Go web应用了。你可以从http://golang.org/下载Go。目前,支持Linux i386/x86_64, FreeBSD i386/x86_64和OSX。对于OSX支持,你需要Go 1.0.3+,或者你将需要应用http://code.google.com/p/go/source/detail?r=62b7ebe62958上的可用补丁。goroutine目前只在Linux i386/x86_64上支持。
构建带Go支持的uWSGI¶
可以作为嵌入部件或者插件构建Go支持。与其他语言的设置主要的不同是,这次,我们会构建一个uwsgi库,而不是一个uwsgi二进制文件。这个库将会被名为uwsgi.go的一个Go包使用,你可以链接你的应用。不要怕,因为在uWSGI发行版中,已经有一个构建配置文件,你可以用它来构建一个内置Go支持的完整的(单片)发行版。在构建过程的最后,你会得到一个libuwsgi.so共享库和一个uwsgi.a Go包。
要构建uWSGI+go,仅需运行 (在uWSGI源代码目录)
UWSGI_PROFILE=go make
或者如果Python并不在你的系统目录中,或者你需要使用一个指定python版本:
/usr/local/bin/python uwsgiconfig.py --build go
(或者你自定义的Python的位置)
在构建过程的最后,你会得到一个libuwsgi.so文件 (拷贝或者链接它到一个库目录,例如/usr/local/lib或者/usr/lib,最后需要的话,运行ldconfig),和一个plugins/go/pkg子目录(基于你的架构/操作系统)中的uwsgi.a文件。
重要
构建过程的最后的消息报告了你在构建uWSGI Go应用的时候应该使用的 GOPATH
(在某个地方拷贝/记住/注释该值)。
如果你已经知道Go导入系统是如何工作的,那么随意拷贝你的系统范围的GOPATH中的uwsgi.a。
编写第一个Go应用¶
默认情况下,uWSGI Go插件支持 http.DefaultServeMux
处理器,所以,如果你的应用已经基于此了,那么在uWSGI中运行它应该非常简单。
package main
import (
"uwsgi"
"net/http"
"fmt"
)
func oneHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "<h1>One</h1>")
}
func twoHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "<h2>Two</h2>")
}
func main() {
http.HandleFunc("/one/", oneHandler)
http.HandleFunc("/two/", twoHandler)
uwsgi.Run()
}
可以看到,唯一与标准的基于 net/http
的应用不同的地方在于,需要 import "uwsgi"
,以及调用 uwsgi.Run()
函数,这将会运行整个uWSGI服务器。如果你想用你自己的请求处理器来取代 http.DefaultServeMux
,那么使用``uwsgi.Handler(http.Handler)`` 或者 uwsgi.RequestHandler(func(http.ResponseWriter, *http.Request))
来设置它。
func myHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "<h2>Two</h2>")
}
func main() {
uwsgi.RequestHandler(myHandler)
uwsgi.Run()
}
构建你的第一个应用¶
假设你的应用是helloworld.go,仅需运行以下。
GOPATH=/home/foobar/uwsgi/plugins/go go build helloworld.go
将GOPATH改为你从构建过程获得的值,或者你安装/拷贝uwsgi.a的目录。如果一切顺利,你会得到一个’helloworld’可执行文件。那个可执行文件是一个完整的uWSGI服务器 (是的,真的是)。
./helloworld --http :8080 --http-modifier1 11
仅需将你的浏览器指向端口8080,然后检查/one/和/two/。你可以开始添加进程和一个master:
./helloworld --http :8080 --http-modifier1 11 --master --processes 8
注意:官方分配了modifier1 11给Go。
上生产¶
在一个生产环境中,你可能会把一个web服务器/代理放在你的应用前面。因此,你的nginx配置看起来会是这样::
- location / {
- include uwsgi_params; uwsgi_pass 127.0.0.1:3031; uwsgi_modifier1 11;
}
而你的uWSGI配置将会差不多是这样……
[uwsgi]
socket = 127.0.0.1:3031
master = true
processes = 4
最后,简单运行你的应用:
./helloworld config.ini
goroutine (目前仅Linux/FreeBSD)¶
goroutine非常可能是Go平台最有趣的特性。当你构建带go插件的uWSGI时,一个用于goroutine的uWSGI循环引擎自动嵌入到uWSGI库中。要在每个uWSGI进程中生成goroutine,只需添加 goroutines = N
选项,其中,N是要生成的goroutine数目。
[uwsgi]
socket = 127.0.0.1:3031
master = true
processes = 4
goroutines = 100
使用这个配置,你将为每个uWSGI进程生成100个goroutine,总共生成400个goroutine (!) 就uWSGI看来,goroutine对应pthread,但你将也能够从你的应用生成基于coroutine的任务。
uWSGI api¶
从你的Go应用访问uWSGI API是相当简单的。要这样做,请引用uwsgi包导出的函数:
package main
import (
"fmt"
"uwsgi"
)
func hello2(signum int) {
fmt.Println("I am an rb_timer running on mule", uwsgi.MuleId())
}
func hello(signum int) {
fmt.Println("Ciao, 3 seconds elapsed")
}
func postinit() {
uwsgi.RegisterSignal(17, "", hello)
uwsgi.AddTimer(17, 3)
uwsgi.RegisterSignal(30, "mule1", hello2)
uwsgi.AddRbTimer(30, 5)
}
func foofork() {
fmt.Println("fork() has been called")
}
func main() {
uwsgi.PostInit(postinit)
uwsgi.PostFork(foofork)
uwsgi.Run()
}
PostInit()函数设置在完成Go初始化之后,调用的“钩子”。PostFork()设置在每次fork()之后调用的“钩子”。在postinit钩子中,我们注册了两个uwsgi信号,第二个运行在一个mule (mule1) 中。要运行这个代码,只需像上面那样构建你新的应用,然后执行它
[uwsgi]
socket = 127.0.0.1:3031
master = true
processes = 2
goroutines = 20
mules = 2
memory-report = true
这次,我们添加了memory-report,试一试,看看Go应用可以多省内存。
从Emperor运行¶
如果你运行在Emperor模式下,那么你可以通过 privileged-binary-patch
选项运行uWSGI-Go应用。你的vassal配置应该差不多像这样。
[uwsgi]
socket = 127.0.0.1:3031
master = true
processes = 2
goroutines = 20
mules = 2
memory-report = true
uid = foobar
gid = foobar
privileged-binary-patch = /tmp/bin/helloworld
(显然,修改``/tmp/bin/helloworld`` 为任何你应用所在的地方……)
注意事项¶
- 你可以在uWSGI源代码发行版的
t/go
目录中找到一系列有趣的go样例。 - 目前不可能在不修改go核心的情况下修改进程名
- 你不能和Go一起使用uWSGI原生线程 (只使用–goroutines)
- 目前只公开了一小部分uWSGI API。如果你想要hack下,或者需要更多,那么只需编辑plugins/go/src/uwsgi目录中的uwsgi.go文件
- goroutine需要异步模式 (如果你正自定义你的uWSGI二进制文件,那么记得总是包含它)
- 似乎可能甚至在goroutines模式下无问题加载Python, Lua和PSGI插件 (需要更多的测试)
发布说明¶
稳定版本¶
uWSGI 2.0.13.1¶
[20160513]
修正快速版本
改动¶
- 修复对python 2.5和python 2.6的支持
- 修复对较老的glibc的支持
- 恢复EPOLLEXCLUSIVE补丁,需要更多的调查
可用性¶
你可以从http://projects.unbit.it/downloads/uwsgi-2.0.13.1.tar.gz下载uWSGI 2.0.13.1
uWSGI 2.0.13¶
[20160510]
改动¶
- 修复使用GCC 6编译的问题
- 远程rpc修复 (Darvame)
- Musl支持! (Natanael Copa, Matt Dainty, Riccardo Magliocchetti)
- 在spooler目录不存在的时候创建它 (Alexandre Bonnetain)
- 修复大端linux上的编译 (Riccardo Magliocchetti)
- 大量的缓存修复 (Darvame)
- 使得在一个不同的目录中编译插件更简单(Jakub Jirutka)
- 添加wheel包机制 (Matt Robenolt)
- 将EPOLLEXCLUSIVE用于读取,有助于惊群问题 (在linux 4.5+上) (INADA Naoki)
- 修复与unix socket的apache 2.4集成 (Alexandre Rossi)
- 添加HTTP/2支持到apache 2 proxy (Michael Fladischer, OGAWA Hirofumi)
- 修复apache 2.4.20的apache mod proxy编译 (Mathieu Arnold)
- 默认使用clang作为MacOS X上的默认编译器 (Riccardo Magliocchetti)
- 添加–cgi-close-stdin-on-eof (Roberto De Ioris)
可用性¶
你可以从http://projects.unbit.it/downloads/uwsgi-2.0.13.tar.gz下载uWSGI 2.0.13
uWSGI 2.0.12¶
[20151230]
错误修复¶
- ‘rpcvar’路由动作在空响应时正确返回NEXT
- uwsgiconfig: 修复python3中的空键处理 (Simone Basso)
- plugins/alarm_speech: 修复AppKit拼写,以支持大小写敏感的文件系统 (Andrew Janke)
- 修复继承INET地址0.0.0.0 (INADA Naoki)
- core/xmlconf: 正确初始化libxml2 (Riccardo Magliocchetti)
- python插件中传递LIBDIR给linker (Borys Pierov)
- 对于pty, forkptyrouter和mono插件的平台相关的构建修复 (Jonas Smedegaard和Riccardo Magliocchetti)
新的特性和反向移植¶
自定义worker api¶
你终于可以覆盖uWSGI处理模型,来获得它的完全控制权。这与你可以在诸如gunicorn(以及它与tornado或者gevent的集成)这样的项目中可以做的事情非常类似。显然,原生的插件仍然是最佳方法 (它们允许与uWSGI api和states的集成),但在一些情况下,你也许想要使用uWSGI进程管理功能,让你的应用做剩下的工作。
目前,只有python插件支持对worker的“覆盖”,有一个aiohttp (asyncio)样例模块:
https://github.com/unbit/uwsgi-docs/blob/master/WorkerOverride.rst
–wsgi-disable-file-wrapper¶
这个选项禁用WSGI标准的wsgi.file_wrapper优化。在一些边缘情况中,这是避免错误的唯一技巧。
官方PHP 7支持¶
php插件中现在官方支持PHP 7。
uwsgi.spooler_get_task api (关于作者:Alexandre Bonnetain)¶
这个补丁允许你轻松解析spooler文件。
看看这里的例子/测试:
https://github.com/unbit/uwsgi/blob/master/t/spooler/read.py
–if-hostname-match (关于作者:Alexandre Bonnetain)¶
这个选项用于配置逻辑,允许你只有在关于主机名的正则表达式匹配上时才定义选项:
[uwsgi]
if-hostname-match = ^prod
threads = 20
endif =
可用性¶
你可以从http://projects.unbit.it/downloads/uwsgi-2.0.12.tar.gz下载uWSGI 2.0.12
uWSGI 2.0.11.2¶
[20151007]
错误修复¶
- OSX 10.11支持TCP_FASTOPEN
- 修复harakiri之后的http-socket解析器错误
- 修复线程化请求日志器
- 修复fastrouter订阅modifier
- 修复告警积压
uWSGI 2.0.11.1¶
[20150719]
错误修复¶
- 修复HTTPS路由器资源释放和文件描述符泄漏
- 当errno为0的时候,不吐出ssl错误
uWSGI 2.0.11¶
[20150701]
错误修复¶
- [pypy] 修复ffi.string的错误使用
- 修复gcc 5检测 (jimfunk)
- 修复用于网关的共享socket
- [psgi] 将abs更改为labs,因为offset被声明为long类型 (Peter H. Ezetta)
- 添加null结束符号到uwsgi_get_dot_h()和uwsgi_config_py() (Jay Oster)
- 修复停止/重启期间的线程等待 (Kaiwen Xu)
- 修复链式重载信息显示
- [python] 修复spooler任务引用计数 (Curtis Maloney)
- 多种静态分析改进 (Riccardo Magliocchetti)
- 修复对于非常大范围的共享区域支持
- 修复大小为0的响应的gzip转换 (Curtis Maloney)
- 修复https客户端证书认证管理 (Vladimir Didenko)
- 修复OpenBSD构建
- 修复TMPFILE权限
新特性¶
fixpathinfo路由动作¶
这是移除臭名昭著的uwsgi_modifier1 30遗留物需要的另一步。
这个路由动作假设PATH_INFO cgi变量包含了SCRIPT_NAME部分。
这个动作允许你在nginx中设置SCRIPT_NAME,而无需重写PATH_INFO (这是nginx无法承担的)
[uwsgi]
; blindly assumes PATH_INFO is clobbered with SCRIPT_NAME
route-run = fixpathinfo:
uwsgi[sor]和time[micros]路由变量¶
这两个新的变量公开了当前请求的开始时间 (以微秒为单位) 和当前时间 (再次,以微秒为单位)
[uwsgi]
route-run = log:request started at ${uwsgi[sor]}
route-run = log:current micros time is ${time[micros]}
wait-for-socket¶
这与wait-for-fs/iface/file/dir类似
挂起实例的生成,直到特定的tcp/unix socket准备好了。
你可以用它来同步vassal生成 (例如,停止一个vassal,直到已生成一个postgresql服务器)
uWSGI 2.0.10¶
[20150317]
错误修复¶
- 不因gcc 4.9的使用而降低安全标准 (Riccardo Magliocchetti)
- Perl/PSGI确保至少有两个参数传递给xs_input_seek (Ivan Kruglov)
- Per/PSGI修复多个解析器使用
- spooler: 修复scandir使用
- 修复异常处理器参数管理
- 修复’log-master’ + ‘daemonize2’禁用所有日志记录
- 修复http Range头管理
新特性¶
safeexec钩子¶
这类似’exec’,但不会在错误时退出,即使执行的命令返回一个非零值
移植–emperor-wrapper-fallback和–emperor-wrapper-override¶
–emperor-wrapper-fallback选项允许你在运行vassal并且找不到默认的binary_path(或者返回错误)时,指定一个替代二进制文件来执行。 (你可以多次指定它)
–emperor-wrapper-override类似,但是“覆盖”默认的封装器 (你可以多次指定它)
添加对UNIX socket的支持到rsyslog¶
此rsyslog记录器现在可以把一个unix socket当成地址 (以斜线开头的参数会被识别成一个unix路径)
forcecl转换¶
这个转换就像’fixcl’,但它生成Content-Length头,即使Content-Length已处于删除列表中。
uWSGI 2.0.9¶
[20141230]
错误修复¶
- 修复非阻塞模式下的mod_proxy_uwsgi (非常感谢Joe cuchac)
- 修复master-fifo + cheaper
- 修复bind_to_unix上的错误时泄漏 (Riccardo Magliocchetti)
- atexit钩子也可以工作在cheaped worker上
- atexit钩子在关闭期间也可以工作在gevent模式上
- 修复重载后的carbon命令行选项
- 第一次运行时不遵循Emperor节流
- 修复Mono插件
- 修复corerouter中的对端名
- 修复守护进程的停止信号
- https/spdy路由器中的多个ssl/tls修复
- 修复python3 –py-auto-reload-ignore
- 修复corerouter中的modifier
- 支持yajl (OSX)
- psgi: 确保我们在psgix.harakiri.commit上调用任何DESTROY钩子 (Ævar Arnfjörð Bjarmason)
- systemdlogger: 使用-Werror=format-security修复编译 (Riccardo Magliocchetti)
- 修复无掩码websockets
- perl修复潜在的refcounting问题 (Mattia Barbon)
新特性¶
改进Linux的PyPy支持¶
PyPy团队已开始在他们的官方版本中构建libpypy-c.so了。现在,通过uWSGI使用pypy应该更容易了:
Fastrouter post-buffering¶
fastrouter拥有post-buffering功能:
https://uwsgi-docs.readthedocs.io/en/latest/Fastrouter.html#post-buffering-mode-uwsgi-2-0-9
Perl uwsgi::opt¶
psgi/perl插件公开了uwsgi::opt哈希值,报告整个实例的键值配置
–pull-header¶
这像–collect-header,但并不返回已收集的头给客户端
active-workers信号目标¶
这就像’workers’目标,但是只将信号转发给非cheaper的worker
httpdumb路由动作¶
http内部路由器公开了一个名为’httpdumb’的新模式,此模式下,在转发请求之前不修改请求头
uWSGI 2.0.8¶
注意:这是第一个默认禁用SSL3的版本,如果你需要它,可以通过 --ssl-enable3
选项来重新启用
错误修复¶
- 修复当
--php-app
存在时的PHP SCRIPT_NAME 使用 - 允许不带第二个参数的”appendn”钩子
- 修复Carbon插件中的堆损坏 (关于作者:Nigel Heron)
- 修复 getifaddrs() 内存管理
- 修复 tcsetattr() 使用
- 修复返回值的kevent使用 (关于作者:Adriano Di Luzio)
- 确保PSGI响应头格式正确
- 修复附加守护进程的重载
- 修复SSL/TLS关闭
- 修复不以/结尾的路径的挂载逻辑 (关于作者: Adriano Di Luzio)
- 修复spooler装饰器的Python3支持 (关于作者:Adriano Di Luzio)
新特性¶
从2.1移植过来的RTSP和分块输入,用于HTTP路由器¶
--http-manage-rtsp
和 ``–http-chunked-input` 已从2.1移植过来,允许HTTP路由器自动检测RTSP和分块请求。这对于即将到来的https://github.com/unbit/uwsgi-realtime插件有用。
–hook-post-fork¶
这个自定义钩子允许你在每次 fork() 之后调用动作。
对asyncio插件,回退到trollius¶
如果你对python2构建asyncio插件,那么将会尝试回退到 trollius 模块。
这个特性基本零测试覆盖,因此欢迎每个报告 (错误或者成功都可以)。
添加sweep_on_full, clear_on_full 和 no_expire 到 --cache2
¶
已添加3个用于 --cache2
的新选项,用来改进缓存过期策略:
sweep_on_full
一旦缓存变满,就进行扫除 (删除所有过期项)clear_on_full
一旦缓存满了,就完全清理缓存no_expire
强制缓存不要生成缓存扫除线程,委托项移除给前两个选项
从2.1移植wait-for-fs/mountpoints¶
--wait-for-fs <path>
挂起uWSGI启动,直到文件/目录可用--wait-for-file <path>
挂起uWSGI启动,直到文件可用--wait-for-dir <path>
挂起uWSGI启动,直到目录可用--wait-for-mountpoint <path>
挂起uWSGI启动,直到挂载点可用
改进卸载api (backport from 2.1)¶
uWSGI 2.0.8与即将到来的https://github.com/unbit/uwsgi-realtime插件兼容,该插件使用uWSGI卸载引擎 + Redis发布/订阅(publish/subscribe),允许实时特性 (例如websockets或者音频/视频流)的使用。
允许将来自远程源的插件作为嵌入插件构建¶
已扩展UWSGI_EMBED_PLUGINS环境变量,支持远程插件。例如,你可以使用Avahi和实时插件,这样构建一个单片uwsgi二进制文件:
UWSGI_EMBED_PLUGINS="avahi=https://github.com/20tab/uwsgi-avahi,realtime=https://github.com/unbit/uwsgi-realtime" make
自动管理HTTP_X_FORWARDED_PROTO¶
虽然在HTTP世界里,对于转发会话有了一个新的标准 (http://tools.ietf.org/html/rfc7239),但是这个版本添加了对X-Forwarded-Proto头的支持,自动检测相应的请求模式(scheme)。
uWSGI 2.0.7¶
更新日志 [20140905]
错误修复¶
- 修复Statsd插件中的计数器 (Joshua C. Forest)
- 修复PHP插件中的缓存 (Andrew Bevitt)
- 修复对以数字开头的系统用户的管理
- 使用 memmove 来代替 memcpy 进行请求体readline(Andrew Wason)
- 忽略 setns 中的“user”名字空间 (仍然是问题源)
- 修复Python3 RPC 字节/字符串混乱 (结果:我们两种都支持)
- 当挂载钩子失败时不销毁Emperor
- 修复OS X上的Mono插件中的符号查找错误 (Ventero)
- 修复缓冲区溢出时的FastCGI和SCGI协议错误
- 修复Solaris/SmartOS I/O管理
- 修复RPC子系统中的两种内存泄漏 (Riccardo Magliocchetti)
- 修复Rados插件的PUT方法 (Martin Mlynář)
- 修复cow模式下使用多个线程的多个Python挂载点
- 统计信息UNIX socket现在由 vacuum 删除
- 修复缓存LRU模式中的离一(off-by-one)崩溃
- 强制Cygwin中的单CPU构建 (Guido Notari)
新特性和改进¶
允许从每个CPython上下文中调用spooler¶
在Europython 2014, Ultrabug (一个uWSGI贡献者和packager) 要求直接从greenlet spool任务的可能。
已完成。
文件日志器循环¶
作者:Riccardo Magliocchetti
file 日志器已扩展,允许使用循环 (非插件式 –logto 使用相同的系统)。
https://github.com/unbit/uwsgi/commit/0324e5965c360dccfb873ffe351dec88ddab59c5
Vassal插件钩子¶
这个插件钩子API已扩展了两个新的钩子: vassal 和 vassal_before_exec 。
它们允许在vassal进程已创建后立即对vassal自定义。
第一个使用它的第三方插件是’apparmor’: https://github.com/unbit/uwsgi-apparmor
这允许你应用Apparmor配置文件到vassal。
Broodlord改进¶
已改进Broodlord子系统,它有一个新选项: –vassal-sos ,当实例的所有worker都处于忙碌状态时,自动要求加固。
除此之外,系统管理员现在可以手动要求加固,发送’B’命令到一个实例的master FIFO。
uWSGI 2.0.6¶
更新日志 [20140701]
错误修复¶
- 修复订阅系统中的内存泄漏
- 修复ssl-socket快捷方式
- 修复Apache2 mod_proxy_uwsgi。现在,它对所有的Apache MPM引擎都是稳定的。
- 修复PHP插件中的SCRIPT_NAME和PATH_TRANSLATED生成 (感谢Matthijs Kooijman)
- 重建时将老的FIFO socket从事件队列移除 (感谢Marko Tiikkaja)
新特性¶
新的Rados插件¶
关于作者:Marcin Deranek
Rados插件已经被改进和加固,现在对生产来说,可以算是稳定和可用的了。
异步模式和多线程正常工作。
已添加对上传对象(通过PUT)和创建新的池 (MKCOL) 的支持。
期望在uWSGI 2.1支持WebDAV。
文档已更新:https://uwsgi-docs.readthedocs.io/en/latest/Rados.html
–if-hostname¶
这是配置逻辑,用来只有当系统的主机名匹配到一个给定的值的时候,才包含选项。
[uwsgi]
if-hostname = node1.local
socket = /tmp/socket1.socket
endif =
if-hostname = node2.local
socket = /var/run/foo.socket
endif =
Apache2 mod_proxy_uwsgi 固化¶
经过真真多年的问题报告和损坏的数据,以及其他一般糟糕的事, mod_proxy_uwsgi 终于稳定了。
在现代的Apache2版本中,它也支持UNIX socket。
已更新文档: https://uwsgi-docs.readthedocs.io/en/latest/Apache.html#mod-proxy-uwsgi
uwsgi[rsize]路由变量¶
新的 uwsgi[rsize] 路由变量 (只有在“最终”链中才有意义) 公开了请求的响应大小。
`callint`模式¶
该模式允许你从由你的uWSGI实例公开的函数生成blob:
[uwsgi]
uid = @(callint://get_my_uid)
gid = @(callint://get_my_gid)
通过–php-sapi-name进行PHP 5.5指令缓存¶
出于神秘的原因,并未在“嵌入的”SAPI中启用PHP 5.5+的指令缓存。这个选项允许你伪造SAPI名 – apache 是个不错的选项 – 来强制指令缓存引擎打开。
改进链式重载¶
多亏了Marko Tiikkaja,链式重载过程正确地在cheaper模式下工作,并且更加详细。
添加’chdir’键值到–attach-daemon2¶
现在,你可以设置附加的守护进程需要chdir()到哪里。
uWSGI 2.0.5¶
更新日志 [20140601]
错误修复¶
- 修复Lua插件中对重复header的支持 (关于作者:tizoc)
- 修复OpenBSD和NetBSD上对嵌入配置的支持
- 基于cURL的插件的多个修复 (关于作者:Yu Zhao)
- 修复基于毫秒的等待
- 修复共享区域的poller
- 修复统计信息服务器中的JSON编码器
- 修复FastCGI解析器并实现EOF管理 (关于作者: Jeff Trawick)
- 改进快速按需模式
- 排除对静态文件的avg_rt计算
- 修复uwsgi内部路由器中的变量支持
- 修复websockets + keepalive顺序
- 禁用基于协程的循环引擎中的SIGPIPE管理
- 修复32位系统中的64位共享区域管理
- 在fd0模式下遵循chmod/chown-socket
- hack以避免iOS上的Safari弄乱keepalive
- 当同时使用–logto和–log2时的日志设置 (关于作者:Łukasz Mierzwa)
- 修复mule_get_msg EAGAIN
- signal_pidfile返回正确的错误码code
- 修复OSX上的asyncio
新特性¶
mule进程的优雅重载 (关于作者:Paul Egan)¶
现在会发送 SIGHUP 给mule,而不是直接杀死它们。 你可以随意在代码中捕获该信号。 如果一个mule在允许的“宽恕期” (–mule-reload-mercy, 默认是60秒)内没有死掉,那么将发送SIGKILL。
return 路由动作 (关于作者:Yu Zhao)¶
这个新的动作将允许用户编写简化的”break”子句。
例如,”return:403”等价于”break:403 Forbidden”,带有响应体”Forbidden”。
这个响应体对于告诉终端用户出了什么事是非常有用的。
–emperor-no-blacklist¶
这个新的选项完全禁用Emperor的黑名单子系统。
Icecast2协议辅助器¶
即将到来的unbit.com项目之一是一个基于uWSGI的音频/视频流媒体服务器。
这个插件 (应在Europython 2014期间发布) 已支持Icecast2协议。
已添加了一堆的补丁到这个HTTP路由器,来支持Icecast2协议。
例如, --http-manage-source
选项运行HTTP路由器遵循 SOURCE 方法请求,自动将它们置于原始模式。
–metrics-no-cores, –stats-no-cores, –stats-no-metrics¶
当你拥有上百(或上千)个异步核时,公开它们的度量也许会非常地慢。
已添加了3个新的选项,允许你禁用核相关的度量的生成,以及最终导致禁用它们在统计数据服务器上的使用。
共享区域改进¶
共享区域API在持续改进中。最后的补丁包括直接从命令行进行内存映射文件(或设备) (mmap) 的支持。
一个测试该特性的有趣的方式是映射树莓派的BCM2835内存。这个小小的例子让你读取RPi系统的计时器。
uwsgi --sharedarea file=/dev/mem,offset=0x20003000,size=4096 ...
现在,你可以从第一个(基于0)共享区域读取一个64位的值:
# read 64bit from 0x20003004
timer = uwsgi.sharedarea_read64(0, 0x04)
(显然,在读取和写入树莓派内存时,要小心。一个错误很可能就把整个系统搞挂了!)
UWSGI_GO_CHEAP_CODE¶
这个退出码 (15) 可以由worker引发,来告诉master不要重新生成它。
对http路由器的PROXY1支持 (关于作者:bgglenn)¶
选项 --http-enable-proxy-protocol
允许HTTP路由器理解PROXY1协议请求,例如那些由Haproxy或者 or Amazon Elastic Load Balancer (ELB)发起的请求。
用于度量的reset_after_push (关于作者:Babacar Tall)¶
这个度量属性确保度量被推送到外部系统(例如Carbon或者StatsD)后,度量值被重置为0 (或者它硬编码的 initial_value)。
setremoteaddr¶
这个新的路由选项允许你完全覆盖由协议处理器检测到的 REMOTE_ADDR :
[uwsgi]
; treat all requests as local
route-run = setremoteaddr:127.0.0.1
resolve 选项¶
有些uWSGI选项 (或者插件) 并不会自动解析DNS名到IP地址。这个选项允许你映射一个占位符到一个字符串的DNS解析结果:
[uwsgi]
; place the dns resolution of 'example.com' in the 'myserver' placeholder
resolve = myserver=example.com
; %(myserver) would now be 93.184.216.119
subscribe2 = server=%(myserver),key=foobar
uWSGI 2.0.4¶
更新日志 [20140422]
错误修复¶
- 修复”mime”路由变量 (Steve Stagg)
- http解析器允许重复的http头
- 更快的即需Emperor管理
- 修复UWSGI_ADDITIONAL_SOURCES构建选项
- 当启用SPDY时,合并重复的头 (Łukasz Mierzwa)
- 为未命名记录器修复段错误
- lazy-apps模式下可用–need-app
- 修复致命钩子管理
新特性¶
实验性asyncio循环引擎 (CPython >= 3.4)¶
asyncio (也被称为’tulip’)是使用Python 3编写基于非阻塞/异步/回调代码的新的基础结构。
这个 (实验性的) 插件允许你将asyncio当成uWSGI循环引擎使用。
httprouter高级超时时间管理¶
HTTP路由器有2个新的特定超时时间:
- –http-headers-timeout <n>: 定义等待http头的超时时间
- –http-connect-timeout <n>: 定义连接到后端实例时的超时时间
这应该能帮助系统管理员提高安全性和可用性。
关于作者:Łukasz Mierzwa
清理LRU缓存特性,来自Yu Zhao (getcwd)¶
这个新模式允许你配置一个缓存,来自动终止最近最少使用 (LRU) 的元素,从而在空间耗尽的时候腾出空间。
仅需添加 purge_lru=1 到你的cache2指令中。
支持FreeBSD上的嵌入配置¶
现在,你也可以在FreeBSD系统上,把配置文件嵌入到二进制文件中:
https://uwsgi-docs.readthedocs.io/en/latest/Embed.html#step-2-embedding-the-config-file
用于静态路由器的 no_headers 选项¶
基于键值的静态路由动作现在可以避免重写响应头了 (对X-Sendfile有用),仅需添加no_headers=1到你的键值选项上。
uWSGI 2.0.3¶
更新日志 20140317
错误修复¶
- 修复spooler的’at’键使用
- 使用即需Emperor socket修复内存和fd泄漏
- 在__APPLE__上,为syslog插件使用LOG_NOTICE
- 修复mongrel2支持
- hack以避免libmongoclient在处理损坏的指针时崩溃
- 日志告警现在是一个uwsgi_log_verbose()封装器
- 修复tuntap路由器内存损坏
- 独立于DHE参数设置ECDHE曲线 (Hynek Schlawack)
- 在检查每一个waitpid之前,不等待整个Emperor循环
- 修复caller()的一个回退,不代表启动*.psgi程序 (Ævar Arnfjörð Bjarmason)
新特性¶
Emperor SIGWINCH和SIGURG¶
Emperor现在响应两种新的信号:
SIGWINCH: 强制emperor重新扫描vassal
SIGURG: 清理Emperor状态 (目前,它只清理它的黑名单)
从git仓库,实时构建插件¶
现在,你可以构建存储在git服务器上的插件了:
uwsgi --build-plugin https://github.com/unbit/uwsgi-bonjour
或者
UWSGI_EMBED_PLUGINS="bonjour=https://github.com/unbit/uwsgi-bonjour" pip install uwsgi
uwsgi.add_var(key, value)¶
现在,你可以直接从你的应用设置请求变量,以更好地与内部路由子系统集成
my $app = sub {
uwsgi::add_var("newvar","newvalue");
return [200, ['Content-Type' => 'text/html'], ["Hello"]];
}
uwsgi --http-socket :9090 --psgi hello.pl --response-route-run "log:\${newvar}"
add_var已在CPython和Perl插件中实现
‘disableheaders’路由动作¶
这个新的动作禁用响应头的发送,独立于当前的请求状态
糟糕的条件下更智能的Emperor¶
现在,Emperor在它不可能正确地杀死一个损坏的vassal时(无论是不一致的Emperor状态,还是由于内部系统问题),会完全销毁内部vassal相关的结构
可用性¶
你可以从这里下载uWSGI 2.0.3:http://projects.unbit.it/downloads/uwsgi-2.0.3.tar.gz
uWSGI 2.0.2¶
更新日志 20140226
错误修复¶
- 修复在较老的编译器/libc上的python3支持
- 允许以仅spooler模式启动
- 修复缓存bitmap支持,并添加测试套件 (关于作者:Danila Shtan)
- 修复ftime日志变量
- 添加异步远程信号管理
- 修复end-for和end-if
- 修复内部路由响应链中的循环
- 修复pypy execute_source使用
- logpipe: 不setsid()两次 (关于作者:INADA Naoki)
新特性和改进¶
CGI插件¶
已改进此插件以支持流式传输。
除此之外,long-awaited异步支持最终准备好了。现在,你可以拥有CGI并发,而无需生成许许多多昂贵的线程/进程
看看: 在uWSGI上运行CGI脚本
PSGI加载改进¶
PSGI加载器现在尝试使用Plack::Util::load_psgi()函数,而不是简单的eval。这解决了环境中各种不一致 (例如psgi脚本的双次解析/编译/执行)。
如果Plack模块不可用,那么会使用一个简单的基于do的代码 (非常类似于load_psgi)
非常感谢booking.com的Ævar Arnfjörð Bjarmason,他发现了这个问题
可用性¶
你可以从以下地址下载uWSGI 2.0.2: http://projects.unbit.it/downloads/uwsgi-2.0.2.tar.gz
uWSGI 2.0.1¶
更新日志 [20140209]
错误修复和改进¶
- 由于错误的原型声明,无SSL构建uWSGI会导致编译错误。此问题已得到修复。
- 已修复PyPy插件中防止大量线程使用的竞争条件
- 只有在心跳子系统已启用的情况下才检查心跳状态
- 改进心跳代码,以支持各种边缘情况
- 改进psgi.input以支持read()中的偏移
- 修复 (和简化) perl堆栈跟踪使用情况
- 修复sni安全订阅
- CGI插件不再需要了,Status头部是第一个 (Andjelko Horvat)
- 修复CPython mule_msg_get超时时间解析
- 允许通过绝对路径嵌入配置文件
- 修复symcall rpc
- 修复CPython spooler api中的内存泄漏 (xiaost)
- –no-orphans加固已经回来了 (目前仅Linux)
- 改进dotsplit路由器模式,以减少DOS风险
- 子Emperor现在默认是loyal的
- 修复非共享ruby 1.8.7支持
- 修复harakiri CPython对tracebacker
- 请求变量现在正确由统计信息服务器公开
- 对logfile-chown支持log-master
- 改进legion重载
- 修复tuntap网络掩码
- 修复busyness插件,无需度量子系统
新特性¶
uWSGI 2.0是一个LTS分支,因此,别期待有灰常多的新特性。2.0.1是第一个维护发布版本,因此,你仍然可以获得一堆新特性 (主要特性并未在2.0完成)
Perl原生Spooler支持¶
Perl最终获得了对Spooler子系统的支持。在2.0中,我们添加了服务器支持,而在2.0.1中,我们也完成了客户端支持。
use Data::Dumper;
uwsgi::spooler(sub {
my $env = shift;
print Dumper($env);
return uwsgi::SPOOL_OK;
});
uwsgi::spool({'foo' => 'bar', 'arg2' => 'test2'})
–alarm-backlog¶
当监听队列满的时候,引发指定告警
[uwsgi]
alarm = myalarm cmd:mail -s 'ALARM ON QUEUE' admin@example.com
alarm-backlog = myalarm
–close-on-exec2¶
关于作者:Kaarle Ritvanen
这个标志在所有服务器socket上应用CLOSE_ON_EXEC socket标志。如果你不想要你请求生成进程继承服务器描述符,那么使用它。
注意:–close-on-exec在所有的socket(客户端和服务器)上应用此标志
简单通知子系统¶
订阅的一个烦人问题是,客户端不知道它是否已正确被订阅至服务器了。
该通知子系统允许你添加订阅包到一个数据报地址 (udp或者unix),服务器将会发送回消息(例如成功的订阅)到该地址
[uwsgi]
; enable the notification socket
notify-socket = /tmp/notify.socket
; pass it in subscriptions
subscription-notify-socket = /tmp/notify.socket
...
该通知子系统是真正通用的。期待未来会有更多的子系统使用它。
用于守护进程的pid名字空间 (仅Linux)¶
这是一个仅Linux的实验性特性,允许你在一个新的pid名字空间内生成守护进程。这个特性要求master作为root运行。
看看: 管理外部守护进程/服务
重订阅¶
fastrouter和http/https/spdy路由器现在支持”重订阅”。
你可以指定一个数据报地址 (udp或unix),所有的订阅请求将会被转发到该地址 (明显改变节点地址到这个路由器地址)
这个系统在构建“联合”设置时会有用:
[uwsgi]
fastrouter = 192.168.0.1:3031
fastrouter-subscription-server = 127.0.0.1:5000
fastrouter-resubscribe = 192.168.0.2:5000
使用这个设置,192.168.0.2上的fastrouter会拥有目的被设置为192.168.0.1:3031的所有192.168.0.1的记录。
文件系统监控API¶
该实时文件通知API已被标准化,现在可以通过插件使用它了。注册一个监控器的原型是:
struct uwsgi_fsmon *uwsgi_register_fsmon(char *path, void (*func) (struct uwsgi_fsmon *), void *data) {
它将会在“path”上注册一个监控器,触发函数”func”,并将”data”作为参数传递给该函数。
记住,这与”touch” api不同,它是基于poll的,并且只能监控文件。(而fsmon还可以监控文件夹)
对yajl 1.0的支持¶
2.0添加了yajl JSON解析器 (version 2)的支持。2.0.1也添加了对1.0的支持。
–inject-before 和 –inject-after¶
这两个新的选项应该为每个人使配置模板系统完整。
它们基本上前置和附加’blobs’到一个配置文件。
是哒,这听起来有点扯淡。
看看下面的例子:
header.xml:
<uwsgi>
<socket>:3031</socket>
footer.xml:
<master/>
</uwsgi>
和body.xml:
<processes>8</processes>
你可以这样构建一个单一的配置树:
uwsgi --show-config --inject-before header.xml --inject-after footer.xml --xml body.xml
这个方法,虽然原始,但是让你以更高级的方式使用魔术变量(因为你可以使用它们控制文件的上下文)
注意:顺序很重要,–inject-before和–inject-after必须在相关配置选项之前指定。
–http-server-name-as-http-host¶
一些Ruby/Rack中间件在SERVER_NAME/HTTP_HOST检查的时候进行可疑检查。
这个标志允许http路由器自动映射SERVER_NAME到HTTP_HOST,而不是指示你的uWSGI实例来做这件事。
更好的Emperor的Ragnarok (关闭过程)¶
‘Ragnarok’是Emperor阶段,当你让它关闭时,会执行此阶段。
在2.0.1之前,这个过程简单发送KILL给vassal,来粗暴销毁它们。
而这个新的Ragnarok则是一种更加仁慈的方式,让vassal优雅关闭。
Emperor对vassal不关闭的容忍可以使用–reload-mercy来调整 (默认30秒)
PyPy粘贴支持¶
已添加两个用于PyPy插件的新选项,用于粘贴支持:
–pypy-paste <config>
–pypy-ini-paste <ini>
它们都1:1映射到CPython变量,但与其相反,它们自动修复日志记录
可用性¶
你可以从这里下载uWSGI 2.0.1: http://projects.unbit.it/downloads/uwsgi-2.0.1.tar.gz
uWSGI 2.0¶
更新日志 [20131230]
重要改动¶
已确定删除动态选项,以及broken_plugins目录
错误修复和改进¶
- 改进日志循环
- 不依赖unix信号在harakiri期间打印请求状态
- 为uid和gid添加魔术变量
- 各种Lua修复
- Riccardo Magliocchetti贡献的大量coverity管理(coverity-governed)的错误修复
新特性¶
Linux setns()支持¶
uWSGI 1.9-2.0中的最大的改进之一是对Linux名字空间的完全支持。
这最后的补丁添加了对setns()系统调用的支持。
该系统调用允许一个进程“附加”到一个正在运行中的名字空间。
uWSGI实例可以通过unix socket公开它们的名字空间文件描述符 (基本上,它们是/proc/self/ns中的文件)。
外部实例连接到那个unix socket,然后自动进入映射到名字空间。
要以“名字空间服务器模式”生成一个实例,则使用 --setns-socket <addr>
选项
uwsgi --setns-socket /var/run/ns.socket --unshare net,ipc,uts ...
要附加,则简单使用 --setns <addr>
uwsgi --setns /var/run/ns.socket ...
已更新文档: 使用Linux名字空间监禁(jailing)你的应用
“私有”钩子¶
当uWSGI运行你的钩子时,它冗长打印整个钩子动作信息。这在某些场景(例如,当你作为root用户运行初始阶段,并且允许非特权用户访问日志时)下会是一个安全问题。
在你的动作前面加上前缀’!’会抑制完全日志记录:
[uwsgi]
hook-asap = !exec:my_secret_command
yajl库 (JSON解析器) 支持¶
直到现在,uWSGI只支持把jansson作为managing .js配置文件所需的json解析器。
你现在可以使用yajl库 (centos中可用) 来作为替代的JSON解析器 (将会自动检测)
Perl spooler支持¶
perl/PSGI插件现在可以被用作一个spooler服务器:
uwsgi::spooler(sub {
my $args = shift;
print Dumper($args);
return -2;
});
客户端部分仍旧缺失,因为我们需要修复一些内部API问题。
预期在2.0.1完成 ;)
网关可以移除特权¶
网关 (例如http router, sslrouter, rawrouter, forkptyrouter ...)现在可以由master独立移除特权。
目前,只有http/https/spdy路由器公开了新的选项 (--http-uid/--http-gid
)
Subscriptions-governed SNI上下文¶
订阅系统现在支持3个额外的键 (你可以用–subscribe2选项来设置它们):
sni_key
sni_cert
sni_ca
它们所有都接收一个到相关ssl文件的路径。
uWSGI 1.9.21¶
2.0(定于2013年12月30日)之前的最后一个1.9版本
从现在起,所有的版本都将-rc (将不会增加新功能)
一个描述从 (非常过时) 1.2 和 1.4版本起升级的注意事项的文档正在进行中。
这个版本包括一个直接嵌入到uWSGI二进制文件中的新的简化插件构建器子系统。
一个报告第三方插件的页面在这里: uWSGI第三方插件 (请随意添加你的插件)
然后现在……
更新日志 [20131211]
错误修复¶
- 如果psgi streamer失败,则中止
- 允许在raspberrypi上构建coroae
- 不等待可写,除非严格要求
- 在非异步模式下调用异步模式API时,避免段错误
- 修复纯 (无挂起引擎) 异步模式
- 在非x86 timerfd_create上,不显示错误
- 支持 __arm__ 上的timerfd_create/timerfd_settime
优化¶
用于第一个块的writev()¶
在内部发送第一个响应体时,uWSGI检查是否也发送了响应头,并最终使用额外的write()调用来发送它们。
这个新的优化允许uWSGI使用单个writev()系统调用来发送第一个响应体块。
如果writev()返回了第二个向量上不完整的写入,那么该系统将会回退到简单的write()。
新特性¶
已移除zeromq api¶
已移除zeromq api (实际上是一个单一的函数)。每个需要zeromq的插件可以简单调用zmq_init(),而不是uwsgi_zeromq_init()。
mongrel2支持已被移到’mongrel2’插件上。
uWSGI与mongrel2配对,可以使用跟之前一样的选项,只是记得要加载(和构建)mongrel2插件
tmsecs 和 tmicros, werr, rerr, ioerr, var.XXX¶
有6个新的请求日志变量可以用了:
tmsecs: 报告当前unix实现,以毫秒为单位
tmicros: 报告当前unix实现,以微秒为单位
werr: 报告当前请求的写错误数
rerr: 报告当前请求的读错误数
ioerr: werr和rerr的总数
var.XXX: 报告请求变量XXX (例如var.PATH_INFO) 的上下文
symcall的挂载点和mule支持¶
已改进symcall插件,支持mule和挂载点。
要在一个mule中运行一个c函数,仅需将其指定为 --mule=foobar()
。当该mule发现有个参数以()结尾,它将会把它当成一个函数符号。
read2和wait_milliseconds异步钩子¶
这两个非阻塞钩子添加新的功能到非阻塞系统上。
第一个允许用同一个调用等待两个文件描述符 (目前只在纯异步模式下实现了它)
第二个用于进行毫秒级别的休眠。 (这个目前只被共享区域等待系统使用)
websockets二进制消息¶
你现在可以发送websockets二进制消息了。仅需使用 uwsgi.websocket_send_binary()
来代替 uwsgi.websocket_send()
‘S’ master fifo命令¶
发送’S’到master fifo,启用/禁用订阅包的发送
接收钩子和改进的链式重载¶
已改进链式重载子系统,以考虑worker何时真正准备好accept()请求。
此特定的状态也会被宣告到Emperor。
看看这篇文章,以获得更多信息: https://uwsgi-docs.readthedocs.io/en/latest/articles/TheArtOfGracefulReloading.html
–after-request-call¶
这个选项允许你在每个请求后调用指定的C函数 (链式)。虽然你应该为这种任务使用框架/接口特性,但是有时在日志记录阶段之后不可能执行代码。在这种情况下,随意使用该选项。
错误页面¶
3个新的选项允许你自定义错误页面 (仅限html):
--error-page-403 <file>
为受管理的403响应添加一个错误页面(html)
--error-page-404 <file>
为受管理的404响应添加一个错误页面(html)
--error-page-500 <file>
为受管理的500响应添加一个错误页面(html)
简化插件构建器¶
构建uWSGI插件现在超级简单:
uwsgi --build-plugin <directory>
这个选项将会基于当前二进制创建一个健全的环境(无需使用构建配置文件和#ifdef),并且将会构建插件。
无需任何外部文件 (包括uwsgi.h),因为uWSGI二进制文件内置了它们。
2.0任务清单¶
- 实现Lua中的websockets和sharedarea支持
- 用于CPython, Perl, Ruby和PyPy的完整的sharedarea api
- 在所有可用的循环引擎中实现read2和wait_milliseconds钩子
uWSGI 1.9.20¶
更新日志 [20131117]
为2.0进行的第一轮弃用和清除¶
- Go茶具现在被认为是“损坏的”,并且已移出
plugins
目录。在uWSGI中运行Go应用的推荐方法是使用 GCCGO插件 插件。 - 已移出
--auto-snapshot
选项,现在可以通过 Master FIFO 来进行实例的高级管理。 - 已移除matheval支持,而推出了一个通用的”matheval”插件 (用于内部路由) (但默认不编译)。看看下面在配置文件中做“算术”的新方法。
- “erlang”和”pyerl”插件不能用了,并且已移出
plugins
目录。将会在2.0发布之后完全重写Erlang支持。
下一次计划的弃用和清除¶
ZeroMQ API (实际上是一个简单的函数) 将会被移除。每个使用ZeroMQ的插件江湖创建其自己的 zmq
上下文 (无需共享)。这意味着在uWSGI核心二进制文件中不再链接libzmq。
Mongrel2协议支持将会移到一个”mongrel2”插件上,而不是内嵌到核心中。
错误修复¶
- 修复在lazy模式下进行优雅重载时的master中止。
- 修复
default_app
使用。 - 另一轮coverity修复,由Riccardo Magliocchetti完成。
- 修复读取请求体时的
EAGAIN
管理。
新特性¶
RPC子系统的64位返回值¶
在此版本之前,每个RPC响应都被限制到64K大小 (16位)。
现在,RPC协议自动检测是否需要更多的空间,并且可以扩展到64位。
该方法的另一个优势在于,只会分配每个响应需要的内存大小,而不是每次盲目创建一个64k大小的块。
配置文件中简单计算¶
如前所见,我们移除了matheval支持,取而代之的是一个简化的接口:
例如,现在,你可以自动设置线程数为:
[uwsgi]
; %k is a magic var translated to the number of cpu cores
threads = %(%k * 3)
...
(%k * 3
是 number_of_cpu_cores * 3
).
新的魔术变量¶
%t
- Unix时间 (以秒为单位,实例启动时收集)。
%T
- Unix时间 (以微秒为单位,实例启动时收集)。
%k
- 检查CPU核数。
Perl/PSGI改进¶
- 块输入API.
psgix.io
是一个映射到连接文件描述符的Socket::IO
对象 (你需要用--psgi-enable-psgix-io
来启用它)。- 来自API的
uwsgi::rpc
和uwsgi::connection_fd
。 --plshell
江湖调用一个交互式shell (基于Devel::REPL
)。
新的原生协议: --https-socket
和 --ssl-socket
¶
当带SSL支持构建时,uWSGI公开了两个原生socket协议:HTTPS和通过SSL的uwsgi。
这两个选项都以以下值为参数: <addr>,<cert>,<key>[,ciphers,ca]
[uwsgi]
https-socket = :8443,foobar.crt,foobar.key
...
目前,没有主流的web服务器支持SSL上的uwsgi,在未来的几小时内,将会把一个nginx补丁送交审核。
PROXY (version1)协议支持¶
最近,Amazon ELB增加了对HAProxy PROXY (version 1)协议的支持。这个简单的协议允许前端传递真正的客户端IP到后端。
增加 --enable-proxy-protocol
将会强制 --http-socket
检查用于设置 REMOTE_ADDR
和 REMOTE_PORT
字段的PROXY协议请求。
新的度量收集器¶
avg
- 计算孩子的算术平均:
--metric name=foobar,collector=avg,children=metric1;metric2
. accumulator
- 总是添加指定孩子的值到最终值中。
multiplier
- n用
arg1n
中指定的值乘以指定孩子的总和。
看看 度量(Metrics)子系统.
uWSGI 1.9.19¶
更改日志 [20131109]
此版本为uWSGI 2.0 (定于2013年12月底)启动“固化”周期。
度量子系统是缺失的最后一块,而这个版本 (经过1年的分析之后) 最终包含了它。
在接下来的2个月内,我们将开始弃用那些不感冒的、已知不能用的或者就被更现代/高级的所取代的特性或插件。
目前,计划删除下列插件和功能:
- Go插件,由gccgo取代。 (如果fork()支持发生了某些改变,那么最终将会恢复这个Go插件)
- 自动快照,从未被公开过,它具有大量的极端情况错误,并且非常复杂。
MasterFifo
添加的特性允许对自动快照更好的实现。
待定:
- erlang插件超级老,设计糟糕,应该被完全重写。如果你是其用户之一,那么请求联系工作人员。非常有可能我们会在没有赞助的情况下不能对其进行维护。
- matheval支持很快将会被移除 (除非我们发现了某些需要它的特定使用),用选项解析器中直接实现的某种形式的简单运算来取代它
- admin插件应被某些更高级的所取代。一个用于定义动态选项的API正在进行中
错误修复¶
- 在非root情况下,完全跳过cgroups初始化
- 由Riccardo Magliocchetti负责的大量post静态分析修复
- 修复greenlet插件引用计数
- 避免统计信息推送器线程的kevent风暴
- 修复rbtimers计算
- ‘cache’和’file’路由器都有一个’no_content_length’关键选项来避免设置Content-Length头
- PyPy插件自动启用线程/GIL
- 管理HTTP解析器中的dot_segments
- 改进srand()使用
新特性¶
Tornado循环引擎¶
在进行nodejs集成的时候,我们意识到,与我们过去相信的相反,Tornado (Python中一个异步、基于回调的模块) 在uWSGI中是可用的。
注意:该插件默认不内置
官方文档: Tornado循环引擎
‘puwsgi’协议¶
添加了’uwsgi’解析器的一个”持久化” (keep-alive)版本,名字为’puwsgi’ (持久化uwsgi)。
这个协议只对没有请求体的请求有用,并且需要来自前端的支持。它目前用于自定义客户端/应用上,没有web服务器处理程序支持它。
--puwsgi-socket <addr>
将绑定一个puwsgi socket到指定的地址上
–vassal-set¶
你可以使用–set,告诉Emperor传递指定选项给每个vassal:
[uwsgi]
emperor = /etc/uwsgi/vassals
vassal-set = processes=8
vassal-set = enable-metrics=1
这会添加 --set processes=8
和 --set enable-metrics=1
到每个vassal
‘template’转换¶
这是一个允许你应用所有的内部路由模式到你的响应上的转换。
以下面的文件为例 (foo.html)
<html>
<head>
<title>Running on ${SERVER_NAME}</title>
</head>
<body>
Your ip address is: ${REMOTE_ADDR}<br/>
Served requests: ${metric[worker.0.requests]}<br/>
Pid: ${uwsgi[pid]}<br/>
A random UUID: ${uwsgi[uuid]}
</body>
</html>
我们将在它上面应用’template’转换:
[uwsgi]
http-socket = :9090
; enable the metrics subsystem
enable-metrics = true
; inject the route transformation
route-run = template:
; return a file (transformation will be applied to it)
route-run = file:filename=foo.html,no_content_length=1
内部路由子系统中任何可用的东西都能用在’template’转换上。
性能是不错的,因此,你或许想要试着使用它,来代替老式的服务器端包含(Server Side Includes)。
不够?将其与缓存结合在一起:
[uwsgi]
http-socket = :9090
; enable the metrics subsystem
enable-metrics = true
; load foo.html in the cache
cache2 = name=mycache,items=10
load-file-in-cache = foo.html
; inject the route transformation
route-run = template:
; return the cache item (transformation will be applied to it)
route-run = cache:key=foo.html,no_content_length=1
再来?
加上分块编码?
[uwsgi]
http-socket = :9090
; enable the metrics subsystem
enable-metrics = true
; load foo.html in the cache
cache2 = name=mycache,items=10
load-file-in-cache = foo.html
; inject the route transformation
route-run = template:
; inject chunked encoding
route-run = chunked:
; return the cache item (transformation will be applied to it)
route-run = cache:key=foo.html,no_content_length=1
或者gzip ?
[uwsgi]
http-socket = :9090
; enable the metrics subsystem
enable-metrics = true
; load foo.html in the cache
cache2 = name=mycache,items=10
load-file-in-cache = foo.html
; inject the route transformation
route-run = template:
; inject gzip
route-run = gzip:
; return the cache item (transformation will be applied to it)
route-run = cache:key=foo.html,no_content_length=1
uWSGI 1.9.18¶
更新日志 [20131011]
License变更¶
uWSGI的这个版本是1.9版本树第一个使用GPL2 + linking exception而不是简单的GPL2的版本。
这个新的license应该会避免在把uWSGI作为共享库使用(或者使用非GPL2兼容的库链接它)的时候的任何问题
记住:如果你需要对uWSGI进行闭源修改,那么你可以购买一个商业license。
错误修复¶
- 修复大端机器上的uwsgi原生协议支持
- 为arm修复jvm构建系统 (Jorge Gallegos)
- 修复zlib管理中由cppcheck发现的内存泄漏
- 在每一个emperor glob迭代chdir()
- 正确遵循–force-cwd
- 修复ia64/Linux编译 (Jonas Smedegaard/Riccardo Magliocchetti)
- 修复ruby rvm路径解析顺序
- 在守护进程的SIGTERM后添加waitpid() (Łukasz Mierzwa)
- 修复–idle后的Pid编号 (Łukasz Mierzwa)
- 修复/改进cheaper内存限制 (Łukasz Mierzwa)
- 正确关闭网关中的继承socket
- 修复mmap()中对MAP_FAILED的检查 (而不是NULL)
- 修复FastCGI非阻塞请求体read() (由Arkaitz Jimenez提供补丁)
- 修复attach.py脚本
- 避免因不符合标准的PSGI响应头引发的崩溃
- 在非lazy时,(即使是在非apps模式下)运行python自动重载器
新特性¶
最小构建配置文件¶
尽管uWSGI核心的内存使用量一般在1.8和2.5兆之间,但在一些用例下,你会想要一个最低限度的核心以及一组嵌入式插件。
例子诸如用户不利用uWSGI的指定特性,或者是uWSGI使用的库与其他库(例如openssl或者zeromq)的名字冲突这种情况。
添加了一堆“最小化”构建配置文件:
- pyonly (构建一个最小化的CPython WSGI服务器)
- pypyonly (构建一个最小化的PyPy WSGI服务器)
- plonly (构建一个最小化的PSGI服务器)
- rbonly (构建一个最小化的Rack服务器)
唯一支持的配置格式是.ini,而内部路由和legion子系统并未内置。
例如,如果你想要通过pip安装最小uWSGI二进制文件:
UWSGI_PROFILE=pyonly pip install uwsgi
重要:最小构建配置文件并不会提高性能,由于设计uWSGI的方式,未使用的特性并不会消耗CPU。最小构建配置文件只影响最终的二进制文件大小
Auto-fix modifier1¶
为非python插件设置modifier1是及其烦人的 (你往往会忘掉它)。
现在,如果请求的modifier1是0,但未加载python插件 (或者没有已加载的python应用),那么第一个配置的应用将会被替代设置 (除非你使用–no-default-app来禁用这个特性)。
这意味着,现在,你可以运行:
uwsgi --http-socket :9090 --psgi myapp.pl
而不是
uwsgi --http-socket :9090 --http-socket-modifier1 5 --psgi myapp.pl
显然,试着总是设置modifier1,这只是一个便捷方式
Perl自动重载器¶
–perl-auto-reload选项允许psgi插件在每次请求之后检查已修改的模块。它以扫描的频率(以秒为单位)作为参数。
该扫描在服务了一个请求之后进行。它是次优的,但是它也是最安全的选择。
“raw”模式 (预览技术,只用于CPython)¶
当在Unbit着手于一个新的服务器端项目时,我们有需要公开使用一个非常特别的协议的web应用 (uWSGI不支持它们中任何一个)。
我们的第一个方式是将新的协议作为插件添加,但很快我们就意识到这太具体了。因此,我们决定引入RAW模式。
Raw模式允许你直接在应用可调用对象中解析请求。与在可调用对象中获得CGI变量/头列表相反,accept()之后你只获得文件描述符。
然后你可以自由地对那个文件描述符进行read()/write()。
import os
def application(fd):
os.write(fd, "Hello World")
uwsgi --raw-socket :7070 --python-raw yourapp.py
Raw mode禁用请求日志记录。目前,我们只对CPython支持,如果我们得知其他语言支持(或者感兴趣),那么我们肯定会添加支持。
重要:raw模式并非标准,因此别期待会应用任何中间件或者常见使用模式。将其当成一个低层次的socket封装器使用。
对于WSGI响应,CPython缓存协议的可选的非标准支持¶
作者:yihuang,在INADA Naoki (methane)的帮助下
WSGI (PEP333/3333)对于响应的有效对象类型是非常清晰的:str用于python2,bytes用于python3
uWSGI (大量使用mod_wsgi作为参考) 总是强制这样的行为,因此,“怪异的”模式,例如返回不支持的字节数组。这样的使用在纯python应用服务器会有点无意识支持,只是因为它们在其之上简单调用write(),或者因为在返回之前将它们转换成字符串 (非常低效)
yihuang提出的补丁建议使用 CPython C api公开的低层次的缓存协议。字符串 (python2中) 和字节 (python3中) 支持该缓存协议,因此,它的使用是透明的,并且也保证向后兼容。 (给CPython C api专家:是哒,我们同时支持老的和新的缓存协议)
这是一个非标行为,你必须通过–wsgi-accept-buffer来自愿启用它。
小心使用,因为它可能会掩盖错误以及/或者错误行为。
注意:如果你尝试使用1.9.18-dev,那么你可能会注意到这个选项是默认被启用的。它是一个错误。多亏了Graham Dumpleton (mod_wsgi作者)指出。
Emperor和配置改进¶
关于作者:Matthijs Kooijman
已改进配置系统,相对于strict模式,它甚至更加一致 (提醒:使用–strict,你基本上为未知选项检查配置文件,避免了由于错别字引发的头疼问题)。
新增了魔术变量,公开原始配置文件的名字 (这在Emperor模式下简化了模板),看一看:https://github.com/unbit/uwsgi-docs/blob/master/Configuration.rst#magic-variables
使用–emperor-cap选项,Emperor获得了Linux功能的支持。该选项以vassal作为root启动的时候,你想要保持的功能列表为参数:
[uwsgi]
emperor = /etc/uwsgi/vassals
emperor-cap = setuid,net_bind_service
通过这个设置,你的vassal将只能够移除特权,并且绑定到<1024的端口
它的好基友是linux名字空间的CLONE_NEWUSER标志,现在uWSGI完全支持它:
[uwsgi]
emperor = /etc/uwsgi/vassals
emperor-use-clone = user
emperor-cap = setuid,net_bind_service
这将会为vassal创建一个新的带有更少特权的root用户(CLONE_NEWUSER是非常难以理解的,但理解它的最好的方式是将其视为带有专有功能的新的root用户)
构建系统改进¶
已改进构建系统,它现在可以运行时链接自定义资源了。对于低层次的钩子,它运行良好:
// embed_me.c
#include <stdio.h>
void hello_i_am_foobar() {
printf("I Am foobar");
}
现在,我们可以一次性链接到这个文件到主uWSGI二进制文件上:
UWSGI_ADDITIONAL_SOURCES=embed_me.c make
然后,你可以自动访问你的钩子:
uwsgi --http-socket :9090 --call-asap hello_i_am_foobar
最后,Riccardo Magliocchetti重写了构建脚本,使用optparse,而不是原始/老式的sys.argv解析
插件化’模式(scheme)’管理¶
模式(scheme)是uWSGI uri的前缀部分。当你这样
uwsgi --ini http://foobar.local:9090/test.ini
http://就是模式,发信号给uWSGI,告诉它必须通过http下载配置文件。
直到现在,那些’模式(scheme)’都是硬编码的。现在,它们作为茶具被公开出来,因此你可以添加更多的模式(scheme)了 (或者覆盖默认的)。
新的系统也已被应用到PSGI插件了 (抱歉啦,我们确保只有perl开发者才会明白那种诗意 :P),因此,你可以做这样的事:
uwsgi --http-socket :1717 --psgi http://yourapps.local/dancer.pl
或者
./uwsgi --binary-append-data yourapp.pl > blob001
cat blob001 >> ./uwsgi
./uwsgi --http-socket :1717 --psgi data://0
挂载点检查¶
可能很难理解为什么一个应用服务器应该检查挂载点。
正如几年前,理解如何在用户空间内写入文件系统是件傻事。
因此,看看这篇关于用uWSGI管理Fuse文件系统的文章: https://uwsgi-docs.readthedocs.io/en/latest/tutorials/ReliableFuse.html
初步libffi插件¶
随着为公开的钩子嵌入c库变得越来越普遍,我们开始致力于libffi集成,允许传给钩子安全(而理智)的参数。很快就会有相关内容了。
uWSGI 1.9.17¶
更新日志[20130917]
错误修复¶
- ‘pty’客户端现在是阻塞的 (更安全的方法)
- 移除strtok()使用 (由strtok_r()之上的一个新的uwsgi api函数取代)
- 修复–pty-exec (关于作者:C Anthony Risinger)
- listen_queue/somaxconn linux检查现已完成,甚至是对于UNIX socket也是可用的
新特性¶
Master FIFO¶
这是除了UNIX信号之外的一种新的管理方式。由于我们没有更多信号可以使用了 (并且一般处理信号和pid文件并不是非常有趣),因此uWSGI的所有新的管理特性都将基于master fifo。
文档已经有了: Master FIFO
TCC (libtcc)插件¶
TCC是一个嵌入式C编译器。它包含了一个共享库 (libtcc),你可以用来在运行时编译C代码字符串。
libtcc uWSGI插件允许编译c字符串来处理符号。目前,已实现了”tcc”钩子引擎:
[uwsgi]
hook-asap = tcc:mkdir("/var/run/sockets");printf("directory created\n");
hook-as-user = tcc:printf("i am process with pid %d\n", getpid());
hook-post-app = tcc:if (getenv("DESTROY_THE_WORLD")) exit(1);
http-socket = /var/run/sockets/foobar.sock
forkptyrouter网关¶
由于关于Linux容器/名字空间的工作正持续进行中,因此我们添加了这个特殊的路由器/网关,允许在uWSGI实例中动态分配伪终端。要访问由forkptyrouter创建的socket,你可以使用由”pty”插件公开的–pty-connect选项。
相关文档正在编写中。
添加了一个新的魔术变量用于ANSI转义¶
已添加%[魔术变量,它允许你在日志中定义ANSI序列。
如果你喜欢彩色日志:
log-encoder = format %[[33m${msgnl}%[[0m
可路由日志编码器¶
现在,你可以附加日志编码器到指定的日志路由上了:
[uwsgi]
logger = stderr file:/dev/tty
log-route = stderr ubuntu
log-route = stderr clock
print = %[[34mHELLO%[[0m
; add an encoder to the 'stderr' logger
log-encoder = format:stderr %[[33m${msgnl}%[[0m
http-socket = :9090
Emperor心跳系统现在是严苛的……¶
Emperor子系统对于心跳的老方法是向坏掉的vassal请求“优雅”重载。
现在,没有发送心跳(在注册到心跳子系统后)的vassal会被kill -9
这个补丁的结果是对于坏掉的vassal的管理更加健壮
uWSGI 1.9.16¶
更新日志[20130914]
gevent插件的关闭/重载过程的重要改动!!!¶
gevent模式下的关闭/重载过程已经被修改以更好地与多线程(和multigreenlet)环境(最明显的是newrelic代理)进行集成。
与”加入”gevent hub相反,会生成和“加入”一个新的“虚拟”greenlet。
在关闭期间,只有由uWSGI生成的greenlet才会被考虑到,而在它们所有都被销毁之后,进程将会退出。这与老方法不同,后者会等待所有当前可用的greenlet(和猴子不定线程)。
如果你更喜欢旧的行为,那么仅需指定选项–gevent-wait-for-hub
错误修复/改进¶
- 修复CPython在rpc和信号处理器中的引用计数错误
- 对QUERY_STRING,SCRIPT_NAME,SERVER_NAME和SERVER_PORT遵守Rack规格
- 报告缺少内部路由支持 (当libpcre缺失的时候,只是警告)
- 关闭和zerg模式期间更好的ipcsem支持 (添加–persistent-ipcsem作为特例)
- 修复apache mod_fastcgi公开的fastcgi错误
- 重载时不调用pre-jail钩子
- 强制在solaris上使用-lrt进行链接
- 报告thunder锁状态
- 允许在rsyslog插件中自定义优先级
新特性¶
FreeBSD jails原生支持¶
uWSGI有原生FreeBSD jails支持。官方文档在这里: FreeBSD Jails
TunTap路由器¶
这个新的网关时尝试使用Linux名字空间构建更好(也可以说是:稳定)的基础设施的过程中,大量让人头疼的问题的产物。
在处理uts, ipc, pid和文件系统的时候,名字空间是相当方便的,而管理网络真心痛苦。
我们在uWSGI 1.9.15引入了大量的措施 (特别是简化veth管理),但是最后,我们意识到那些系统无法在管理方面扩展。
这个TunTap路由器试图解决在用户空间中移动jailed vassal的网络部分的问题。
基本上,每个vassal创建一个或多个tuntap设备。这些设备被连接(通过unix socket)到”tuntap路由器”,允许从vassal到外部网络的访问。
这意味着,在主名字空间中,有单个网络接口,而对于每个vassal,有一个网络接口。
性能已经是非常棒的了 (涉及到内核层次的路由,我们只损失了大约10%),但还是可以优化。
除此之外,tuntap路由器有一个简单的用户空间防火墙,你可以用来管理复杂的路由规则。
文档还在编写中,但是你可以按照这个文件顶部的大大的注释来配置一个tuntap路由器:
https://github.com/unbit/uwsgi/blob/master/plugins/tuntap/tuntap.c
你可以用 --tuntap-device <dev> <socket>
来连接到它,其中,<dev>要在vassal/客户端中创建的tuntap设备,而<socket>是tuntap路由器的unix地址
一个样例Emperor
[uwsgi]
tuntap-router = emperor0 /tmp/tuntap.socket
exec-as-root = ifconfig emperor0 192.168.0.1 netmask 255.255.255.0 up
exec-as-root = iptables -t nat -F
exec-as-root = iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
exec-as-root = echo 1 >/proc/sys/net/ipv4/ip_forward
emperor-use-clone = ipc,uts,fs,pid,net
emperor = /etc/vassals
及其vassal之一:
[uwsgi]
tuntap-device = uwsgi0 /tmp/tuntap.socket
exec-as-root = ifconfig lo up
exec-as-root = ifconfig uwsgi0 192.168.0.2 netmask 255.255.255.0 up
exec-as-root = route add default gw 192.168.0.1
exec-as-root = hostname foobar
socket = /var/www/foobar.socket
psgi-file = foobar.pl
Linux O_TMPFILE¶
最新的Linux内核一种新的操作模式来打开文件:O_TMPFILE
这个标志在没有任何形式的竞争条件下打开一个临时文件 (也就是说:未链接)。
这个模式在可用的时候会自动使用 (无需任何选项)
Linux pivot-root¶
在处理Linux名字空间时,修改根文件系统时其中一个主要任务。
chroot()一般来讲太简单了,而pivot-root允许你进行更多高级设置
语法是 --pivot-root <new_root> <old_root>
Cheaper memlimit¶
作者:Łukasz Mierzwa
这项新的检查允许控制基于RSS使用的动态进程生成:
https://uwsgi-docs.readthedocs.io/en/latest/Cheaper.html#setting-memory-limits
日志编码器¶
时下,有几十个日志引擎和存储系统。原始的uWSGI方法是为每个引擎开发一个插件。
在使用logstash和fluentd的时候,我们意识到,大多数的日志插件是一次又一次地队相同概念的重新实现。
我们遵循一个甚至更加模块化的方法,引入日志编码器:
它们基本上是你可以应用到每个日志行上到模式。
uWSGI 1.9.15¶
更新日志[20130829]
错误修复¶
- 修复jvm选项hashmap (#364)
- 修复python3 wsgi.file_wrapper
- 修复python3 –catch-exceptions
- 修复pypy wsgi.input.read中的类型
- 对pypy更好的符号检测
- 改进heroku上的ruby库管理
- 修复http-keepalive memleak
- 修复CPython下的spooler体管理
- 修复’fs’的unshare()使用
- 修复当使用–plugin构建插件时的UWSGI_PROFILE使用
- 改进SmartOS支持,以及添加OmniOS支持
新特性¶
PTY插件¶
这个新的插件允许你生成伪终端,并将其附加到你的worker上。
接着,通过网络 (UNIX或者TCP socket) 访问伪终端。
你可以将它们用于共享调试,或者让你的web应用拥有输入通道。
该插件出于开发初期 (特性非常少),并且不是默认构建的,但是你可以做一些像这样有趣的事:
[uwsgi]
plugin = pty,rack
; generate a new pseudoterminal on port 5000 and map it to the first worker
pty-socket = 127.0.0.1:5000
; classic options
master = true
processes = 2
rack = myapp.ru
socket = /tmp/uwsgi.socket
; run a ruby interactive console (will use the pseudoterminal)
; we use pry as it kick asses
rbshell = require 'pry';binding.pry
现在,你可以这样访问伪终端了
uwsgi --plugin pty --pty-connect 127.0.0.1:5000
你可以在多个窗口中运行该客户端,它将由所有客户端共享 (所有都将访问同一个伪终端)。
我们相信,关于它的新的有趣的使用很快就会出现
初步文档可以在 Pty插件 这里找到
strict模式¶
编写uWSGI配置文件时最常见的错误之一是,选项名的拼写错误。
由于你可以添加任何选项到uWSGI配置文件中,因此系统将会接受任何你写的东东,即使它并不是一个真正的uWSGI选项。
虽然这个方法是非常强大的,允许大量有趣的hack,但是,它也有可能让你很头疼。
如果你想一步检查所有的选项,那么现在,你可以添加–strict选项了。未知选项将会触发一个致命错误。
回退配置¶
uWSGI非常便宜 (在资源方面), 并且支持大量的操作系统和架构,它被大量用于嵌入式系统中。
这种设备的常见特性之一就是”恢复到出厂设置”。
有了–fallback-config选项,uWSGI现在原生支持这类操作。
如果一个uWSGI实例随着exit(1)而亡,并且指定了回退配置,那么将会使用新的配置,将其作为唯一的参数,re-exec()该二进制文件。
让我们看看一个使用了不可绑定地址的配置 (非特权用户试着绑定到一个特权端口)
[uwsgi]
uid = 1000
gid = 1000
socket = :80
以及一个回退的配置 (绑定到非特权端口8080)
[uwsgi]
uid = 1000
gid = 1000
socket = :8080
(以root用户,因为我们想删除特权) 运行它:
sudo uwsgi --ini wrong.ini --fallback-config right.ini
日志中将会是:
...
bind(): Permission denied [core/socket.c line 755]
Thu Aug 29 07:26:26 2013 - !!! /Users/roberta/uwsgi/uwsgi (pid: 12833) exited with status 1 !!!
Thu Aug 29 07:26:26 2013 - !!! Fallback config to right.ini !!!
[uWSGI] getting INI configuration from right.ini
*** Starting uWSGI 1.9.15-dev-4046f76 (64bit) on [Thu Aug 29 07:26:26 2013] ***
...
添加 %(ftime) 到日志格式¶
这就像’ltime’,但它遵守–log-date格式
当另一个实例绑定到UNIX socket时,保护其不受破坏¶
在启动时,uWSGI现在获得刚刚创建的unix socket的inode。
在vacuum下,如果inode发生了改变,那么会跳过socket的取消链接动作。
这应该有助于避免系统管理员破坏性竞争条件或错误配置
–worker-exec2¶
这个就像–worker-exec,但在post_fork钩子之后运行
允许普通插件上的post_fork钩子¶
普通插件 (没有.request钩子的那些) 现在可以公开.post_fork钩子了
–call钩子¶
与exec-*钩子有着相同的精神,call钩子工作方式相同,但是它会直接在当前进程的地址空间中调用函数 (必须将其作为有效符号公开)
以这个C源码为例 (称之为hello.c):
#include <stdio.h>
void i_am_hello_world_for_uwsgi() {
printf("Hello World!!!\n");
}
并把它作为共享库编译:
gcc -o libhello.so -shared -fPIC hello.c
现在,选择在uWSGI中何时 (以及何处) 调用它:
./uwsgi --help | grep -- --call-
--call-pre-jail call the specified function before jailing
--call-post-jail call the specified function after jailing
--call-in-jail call the specified function in jail after initialization
--call-as-root call the specified function before privileges drop
--call-as-user call the specified function after privileges drop
--call-as-user-atexit call the specified function before app exit and reload
--call-pre-app call the specified function before app loading
--call-post-app call the specified function after app loading
--call-as-vassal call the specified function() before exec()ing the vassal
--call-as-vassal1 call the specified function(char *) before exec()ing the vassal
--call-as-vassal3 call the specified function(char *, uid_t, gid_t) before exec()ing the vassal
--call-as-emperor call the specified function() in the emperor after the vassal has been started
--call-as-emperor1 call the specified function(char *) in the emperor after the vassal has been started
--call-as-emperor2 call the specified function(char *, pid_t) in the emperor after the vassal has been started
--call-as-emperor4 call the specified function(char *, pid_t, uid_t, gid_t) in the emperor after the vassal has been started
以数字结尾的选项是期望参数的变体 (其后缀是它们接收的参数的数目)
我们想要就在应用被加载之前调用函数:
[uwsgi]
; load the shared library
dlopen = ./libhello.so
; set the hook
call-pre-app = i_am_hello_world_for_uwsgi
...
将会就在应用加载之前调用你的自定义函数。
考虑到那些函数是在进程地址空间中调用的,因此你可以对它们施加所有类型的(黑)魔法。
注:dlopen是对dlopen()函数的封装,因此所有相同的规则都适用 (必读:适用绝对路径!!!)
插件的init_func支持,以及–need-plugin变体¶
在加载插件的时候,你可以在dlopen()之后立即调用插件中定义的符号:
uwsgi --plugin "foobar|myfunc" ...
uWSGI将会调用’foobar’插件公开的’myfunc’符号
–need-plugin就像–plugin,但是当插件加载失败的时候,会exit(1)进程
为pecan框架添加商品加载器(commodity loader)¶
作者:Ryan Petrello
为pecan WSGI框架添加了一个新的python加载器 (–pecan)
https://uwsgi-docs.readthedocs.io/en/latest/Python.html#pecan-support
UWSGI_REMOVE_INCLUDES¶
在构建阶段,你可以用UWSGI_REMOVE_INCLUDES环境变量移除include头文件。
这对交叉编译有用,其中,一些自动检测的include文件可能是错误的。
router_expires¶
我们在uWSGI核心中已经有多个设置Expires头的选项了。
添加这个路由器,以允许你对它们自定义:
[uwsgi]
route = /^foobar1(.*)/ expires:filename=foo$1poo,value=30
route = /^foobar2(.*)/ expires:unix=${time[unix]},value=30
这个路由器接收一个filename的mtime或者一个unix time,添加’value’到其上,然后将它作为一个http日期返回。
在重载/关机时宣布Legion的死亡¶
一旦触发了实例的重载(或者关闭),每一个legion成员现在都将会宣告它的死亡。
GlusterFS插件 (beta)¶
这个新的插件利用新的glusterfs c api,避免当提供存储在glusterfs服务器上的文件时的融合开销。
该插件支持多进程和多线程模式,而异步模式目前处于测试阶段。
文档在这里: GlusterFS插件
–force-gateway¶
所有的网关 (fastrouter, httprouter, rawrouter, sslrouter ...)都必须运行在master进程之下。
通过指定–force-gateway,你可以绕过这个限制
初步的python3 profiler (测试版)¶
–profiler pycall/pyline profiler已添加至python3中。它们处于测试阶段 (有内存泄漏问题),但应该可用。
对OpenBSD,NetBSD,DragonFlyBSD的文件监控支持¶
在这些操作系统上,–fs-reload和@fmon装饰器现在都能使用。
–add-gid¶
这个选项允许你添加额外的组id到当前的进程中。你可以多次指定它。
Emperor和Linux名字空间改进¶
多亏了与pythonanywhere.com那些小伙伴的合作,已对Emperor进行了改进,使其能够与Linux名字空间更好的集成。
–emperor-use-clone选项允许你使用clone()而不是fork()来进行vassal生成。通过这种方式,你可以在一个新的名字空间中直接创建vassal。这个函数接收的参数与–unshare相同
uwsgi --emperor /etc/vassals --emperor-use-clone pid,uts
将会在一个新的pid和uts名字空间中创建每个vassal
而
uwsgi --emperor /etc/vassals --emperor-use-clone pid,uts,net,ipc,fs
将会基本上使用当前所有可用的名字空间。
两个新的exec (和call) 钩子现在可以用了:
–exec-as-emperor将会在生成一个vassal后立即在emperor中运行命令 (设置4个环境变量UWSGI_VASSAL_CONFIG, UWSGI_VASSAL_PID, UWSGI_VASSAL_UID和UWSGI_VASSAL_GID)
–exec-as-vassal将就在调用exec()之前,在vassal中运行命令 (所以直接在新的名字空间中)
–wait-for-interface¶
由于处理Linux网络名字空间引入了大量的竞争条件 (特别是当使用虚拟以太网时),这个新的选项让你暂停一个实例,直到有一个网络接口可用。
这在当等待emperor把veth移动到vassal名字空间的时候有用,避免vassal在接口可用之前在其之上运行命令
[uwsgi]
emperor = /etc/uwsgi/vassals
emperor-use-clone = pid,net,fs,ipc,uts
; each vassal should have its veth pair, so the following commands should be improved
exec-as-emperor = ip link del veth0
exec-as-emperor = ip link add veth0 type veth peer name veth1
; do not use the $(UWSGI_VASSAL_PID) form, otherwise the config parser will expand it on startup !!!
exec-as-emperor = ip link set veth1 netns $UWSGI_VASSAL_PID
[uwsgi]
; suspend until the emperor attach veth1
wait-for-interface = veth1
; the following hook will be run only after veth1 is available
exec-as-root = hostname vassal001
exec-as-root = ifconfig lo up
exec-as-root = ifconfig veth1 192.168.0.2
uid = vassal001
gid = vassal001
socket = :3031
...
uWSGI 1.9.14¶
更新日志[20130721]
错误修复¶
- 修复python modifier1管理 (硬编码为0)
- 修复http和http-socket中的url解码 (它现在支持小写二进制,由Miles Shang发现)
- 为不可删除的unix socket添加更多用户友好型错误信息
- 修复http 1.1 keepalive模式中的–http-auto-chunked (André Cruz)
- 修复python wheel支持 (Fraser Nevett)
- 修复–safe-fd (没有被Emperor正确遵循)
- 修复ruby 2.x重载
- 改进对OSX Tiger的支持 (是的,OSX 10.4)
- 对监听队列负载的更好计算
- 修复OSX上的v8构建
- 修复pypy rpc
- 改进分块api性能
- 修复使用python3时的latin1编码
- 修复–spooler-ordered (Roberto Leandrini)
- 修复报告到请求日志中的状态行
新特性¶
Ruby 1.9.x/2.x原生线程支持¶
Ruby 1.9 (mri)引进了原生线程支持 (非常类似于CPython,由一个名为GVL的全局锁管理)。
由于各种原因 (看看在源插件上面的注释),在uWSGI中的ruby线程支持被当成“循环引擎插件”实现。
你需要构建”rbthreads”插件 (当使用’ruby2’构建配置文件时会自动构建它),并且用’–rbthreads’来启用它
已扩展了gem脚本,当检查到ruby >= 1.9的时候,会自动选择’ruby2’构建配置文件 (这对于Heroku用户而言,应该会更轻松)
Rails4是第一个支持和庇佑线程的Ruby on Rails版本 (在3.x中,你需要明确启用支持)。只有在“生产”模式下,你才能在Rails4中使用多线程,否则,在第一个请求之后,你的应用将会死锁。
一个样例配置:
[uwsgi]
plugins = rack,rbthreads
master = true
; spawn 2 processes
processes = 2
; spawn 8 threads
threads = 8
; enable ruby threads
rbthreads = true
; load the Rack app
rack = config.ru
; bind to an http port
http-socket = :9090
http-socket-modifier1 = 7
它将会生成总共16个线程
文件系统监控接口 (fsmon)¶
目前,uWSGI可以使用“简单的”–touch-*功能或者信号框架来监控文件系统修改 (使用各种操作系统API,例如inotify或者kqueue)。
已为插件编写者添加了一个名为”fsmon”的新接口,允许方便实现实时操作系统监控。
已添加三个新的选项:
–fs-reload <path>
–fs-brutal-reload <path>
–fs-signal <path> <signal>
与–touch-*选项相反,它们是实时的 (一旦发生改变,就会立即唤醒master),并且使用内核功能 (目前只支持inotify()和kqueue())。多亏了这个选择,现在,你可以监控一整个目录的改动了 (不需要像inotifywatch这样的外部进程/封装)
setscheme, setdocroot¶
这两个新的路由动作允许你动态覆盖DOCUMENT_ROOT和UWSGI_SCHEME
sendfile, fastfile¶
这两个动作 (已添加到router_static插件中) 允许你绕过DOCUMENT_ROOT检查,返回静态文件给客户端。
第一个强制使用sendfile()系统调用 (如果有的话),而第二个自动尝试选择最好的服务策略 (像卸载一样)
–reload-on-fd和–brutal-reload-on-fd¶
两个新的选项允许你在一个文件描述符准备好的时候重载一个实例。
目前,最好的使用场景是用于oom_control cgroup接口 (通过eventfd)。
假设你有一个进程封装器,分配一个eventfd(),报告OOM事件 (并且作为’OOM’环境变量公开),那么你可以在内存不足的时候这样强制uWSGI重载:
[uwsgi]
...
reload-on-fd = $(OOM):8 OUT OF MEMORY !!!
它表示:
监控$(OOM)文件描述符,当准备好的时候,从中读取8个字节 (这是eventfd()要求的),然后在日志中打印”OUT OF MEMORY !!!”,接着优雅重载该实例。
显然,这只是使用它的一种方式。UNIX世界是基于文件描述符的,因此,你有很多有趣的方式来使用它。
Spooler改进¶
作者:Roberto Leandrini
所有的工作都已经在uwsgidecorators.py中有效地完成了。
现在,你可以传递给所有可用的spooler相关的装饰器”pass_arguments=True”选项了,来自动序列化spooler函数参数。这是一个抽象,避免你需要序列化/反序列化参数。
除此之外,已扩展了装饰器,实现了 __call__ ,这样,你就可以直接把由spoller装饰的函数当成正常函数调用了。
–emperor-nofollow¶
启用这个选项将会让Emperor监控符号链接的mtime更新,而不是真正文件的mtime。
Alberto Scotto正在进行一个支持这两种的更新版本 (在下一个版本中应该就准备好了)
daemontools envdir支持¶
尽管daemontools看起来过时了,但是像envdirs (http://cr.yp.to/daemontools/envdir.html)这样的东东被大量用在各种环境中。
uWSGI有两个新的选项 (–envdir <path>和–early-envdir <path>),允许你支持这种特别的 (过时的?)配置方式。
xmldir改进¶
作者:Guido Berhoerster
改进了xmldir插件,支持基于iconv的utf8编码。各种小补丁已提交。
样例目录包含了两个新的文件,展示了xmldir+xslt使用
uWSGI 1.9.13¶
更新日志[20130622]
错误修复¶
- 修复了当启用卸载时,没有请求插件被加载的极端情况
- 修复当存在多条规则时的harakiri路由 (返回NEXT而不是CONTINUE)
- 修复在慢DNS响应上curl崩溃的master问题 (Łukasz Mierzwa)
- 移除uwsgi.h中的PTRACE检查 (自uWSGI 1.0起不再需要它了)
- 修复–print-sym
- 在–cflags中添加一个新行
- 改进python3检测和编译
- 修复Coro::AnyEvent循环引擎 (John Berthels)
- Rack api函数现在是静态的了
- 对大文件上传更好的fastcgi处理
- 为Python非apple构建改进在Darwin的GCC使用
- 修复rawrouter中的XCLIENT使用
- 当使用CC=clang的时候,用clang预处理器代替硬编码的’cpp’
- 当请求更高的值时,设置16位选项为65535
- 修复虚拟主机 (它现在与1.4配置兼容)
新特性¶
PyPy性能和功能改进¶
PyPy插件已经被大大改进。C代码的数量已减少了70%,英寸,现在,该插件的绝大多数代码是用Python写的。移除了c helper,允许python部分通过cffi直接调用原生uWSGI函数。
已添加对PyPy continulets (及其greenlet抽象)的支持 (同时等待一个用于pypy的稳定的gevent移植),并且已经有了一个聊天样例 (使用uwsgi异步api):
https://github.com/unbit/uwsgi/tree/master/t/pypy
https://github.com/unbit/uwsgi/blob/master/contrib/pypy/uwsgi_pypy_greenlets.py
改进了pypy uwsgi api,现在,你可以使用uwsgidecorators模块了 (即使spooler子系统仍然缺失)
分块输入api¶
在过去的日子里,已经有了一堆关于如何正确管理分块输入的讨论。由于基本上并未可用的标准以一种“明确的”方式来支持它,因此我们定义了一个低层次的api (所以,可以未来我们可以很容易的调整它)。
该api公开了两个函数:
uwsgi.chunked_read()
和
uwsgi.chunked_read_nb()
一个非阻塞的聊天例子:
import uwsgi
def application(e, sr):
while True:
uwsgi.wait_fd_read(uwsgi.connection_fd())
uwsgi.suspend()
msg = uwsgi.chunked_read_nb()
if msg: print "core %d" % e['uwsgi.core'], msg
变得更好的第三方插件管理:–dot-h选项¶
正如–cflags选项显示将CFLAGS用来构建服务器一样,–dot-h选项显示了uwsgi.h的内容
这意味着uwsgi.h的内容现在被嵌入到二进制文件中了 (如果有压缩的话)。
这可能看起来是个奇怪的选择,但是其目的是使得从uwsgi源代码中编译出插件变得容易 (在2.0中,肯定会有一些会能用的)
UWSGI_INCLUDES¶
你现在可以用这个环境变量来覆盖include搜索路径了 (在构建uWSGI的时候)。
改进的set_user_harakiri api函数¶
现在,uwsgi.set_user_harakiri自动识别mule和spooler。已将它添加到了ruby/rack, pypy和perl/psgi插件中
–add-cache-item [cache ]KEY=VALUE¶
这是一个商品选项(commodity option) (主要用于测试),允许你在启动的时候将一个项存储到uWSGI缓存中
router_xmldir插件¶
这是旨在强调转换api的概念插件的证明。
它基本上生成目录的一个xml表示。这对于实现apache式的目录索引可能有用:
看看这个使用xslt的例子:
https://github.com/unbit/uwsgi/issues/271#issuecomment-19820204
为@spool*装饰器实现 __call__¶
多亏了’anaconda’,你现在可以直接调用映射到spooler的函数了,因此,取代
myfunc.spool(args)
你可以直接这样:
myfunc(args)
显然还是支持旧的方式的
uwsgi[lq]路由变量¶
这个路由变量表示listen_queue的当前大小:
[uwsgi]
...
route-if = higher:${uwsgi[lq]};70 break:503 Server Overload
...
uWSGI 1.9.12¶
更新日志[20130605]
错误修复¶
- 卸载缓存写入将会反悔正确的状态码,而不是202
- 你现在可以设置TMPDIR环境变量,来控制临时文件的路径了 (这为那些没有/tmp路径控制权的用户修复了一个老问题)
- 修复amqp imperial监控器上的一个编译错误
- 当cron被报告到统计信息服务器的时候,会正确的被转义
- 修复上传大文件的时候fastcgi解析器的极端情况
- 修复对最新的cygwin的支持
新特性¶
卸载响应¶
看看下面的WSGI应用:
def application(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/plain')])
return ['u' * 100000000]
它将生成大约100兆数据。worker花费在该请求上的98%的时间都是数据传输。由于整个响应是紧随请求之后的,因此,我们可以卸载该数据写入到一个线程里,然后立即释放worker (这样,它就可以处理新的请求了)。
100兆很大,但甚至是1MB都可以引发十几个poll()/write()系统调用,这会阻塞你的worker不少毫秒
多亏了在1.9.11中添加的“内存卸载”功能,实现它变得非常简单。
通过 uWSGI转换 实现卸载
[uwsgi]
socket = :3031
wsgi-file = myapp.py
; offload all of the application writes
route-run = offload:
默认情况下,会将响应缓存至内存,直到它大小为1MB为止。之后,将会将其缓存至磁盘,接着卸载引擎会使用sendfile()。
你可以设置磁盘缓存后的限制 (以字节为单位),传递一个参数给offload:
[uwsgi]
socket = :3031
wsgi-file = myapp.py
; offload all of the application writes (buffer to disk after 1k)
route-run = offload:1024
“offload”必须是该恋中的最后一个转换
[uwsgi]
socket = :3031
wsgi-file = myapp.py
; gzip the response
route-run = gzip:
; offload all of the application writes (buffer to disk after 1k)
route-run = offload:1024
JWSGI和JVM改进¶
已扩展了JVM插件,来支持更多的扶助对象 (例如ArrayList),而JWSGI现在可以被当成一个低级别的层次使用,来天津队更多基于JVM语言的支持。
JRuby集成是这类应用的第一个尝试。我们刚刚发布了一个JWSGI到Rack适配器,允许你在JRUBY之上适配运行 Ruby/Rack应用:
https://github.com/unbit/jwsgi-rack
关于Jython的一个类似的方法正在开发中
–touch-signal¶
添加了一个新的touch选项,允许当touch一个文件的时候,引发一个uwsgi信号:
[uwsgi]
...
; raise signal 17 on /tmp/foobar modifications
touch-signal = /tmp/foobar 17
...
memcached路由改进¶
你现在可以把响应存储到memcached中了 (正如你已经可以用uWSGI缓存做到的那样)
[uwsgi]
...
route = ^/cacheme memcachedstore:addr=127.0.0.1:11211,key=${REQUEST_URI}
route = ^/cacheme2 memcachedstore:addr=192.168.0.1:11211,key=${REQUEST_URI}foobar
...
显然,你也可以获取它们
[uwsgi]
...
route-run = memcached:addr=127.0.0.1:11211,key=${REQUEST_URI}
...
当前,会在默认配置文件中内置memcached路由器
新的redis路由器¶
基于memcached路由器,添加了一个redis路由器。它的工作方式相同:
[uwsgi]
...
route = ^/cacheme redisstore:addr=127.0.0.1:6379,key=${REQUEST_URI}
route = ^/cacheme2 redisstore:addr=192.168.0.1:6379,key=${REQUEST_URI}foobar
...
... 以及获取值
[uwsgi]
...
route-run = redis:addr=127.0.0.1:6379,key=${REQUEST_URI}
...
默认内置redis路由器
“hash”路由器¶
这个特别的路由动作允许你哈希一个字符串,并从列表中返回一个值 (由哈希键索引)。
看看下面的列表:
127.0.0.1:11211
192.168.0.1:11222
192.168.0.2:22122
192.168.0.4:11321
以及一个字符串:
/foobar
我们使用djb33x算法来对字符串/foobar进行哈希,然后应用模4 (项列表的大小) 到结果中。
结果为”1”,所以,将会获得列表中的第二个项 (显然,索引是从零开始的)。
你认出这个模式了吗?
是的,它是一种标准的将项分布到多个服务器上的方式 (例如,memcached客户端用它已经有很长的一段时间了)。
这个哈希路由器公开了这个系统,允许你分布项到你的redis/memcached服务器上,或者做其他有趣的事。
这是redis的用法示例:
[uwsgi]
...
; hash the list of servers and return the value in the MYNODE var
route = ^/cacheme_as/(.*) hash:items=127.0.0.1:11211;192.168.0.1:11222;192.168.0.2:22122;192.168.0.4:11321,key=$1,var=MYNODE
; log the result
route = ^/cacheme_as/(.*) log:${MYNODE} is the choosen memcached server !!!
; use MYNODE as the server address
route = ^/cacheme_as/(.*) memcached:addr=${MYNODE},key=$1
...
你甚至可以从uWSGI支持的那些哈希算法中选择使用的算法
[uwsgi]
...
; hash the list of servers with murmur2 and return the value in the MYNODE var
route = ^/cacheme_as/(.*) hash:algo=murmur2,items=127.0.0.1:11211;192.168.0.1:11222;192.168.0.2:22122;192.168.0.4:11321,key=$1,var=MYNODE
; log the result
route = ^/cacheme_as/(.*) log:${MYNODE} is the choosen memcached server !!!
; use MYNODE as the server address
route = ^/cacheme_as/(.*) memcached:addr=${MYNODE},key=$1
...
默认会编译该router_hash插件
uWSGI 1.9.11¶
更新日志[20130526]
错误修复¶
- 修复Python 3 stdout/stderr缓冲
- 修复mule消息 (
@mulefunc
现在是可靠的了) - 修复在动态模式下的
SCRIPT_NAME
处理 - 修复gzip静态模式下的X-Sendfile
- 用自定义块大小修复缓存项最大大小
- 修复缓存路径处理
新特性¶
新的高性能PyPy插件¶
关于作者:Maciej Fijalkowski
我们很高兴地宣布,新的PyPy插件可以用了。
PyPy团队给予了我们巨大的帮助。我们希望此uWSGI集成 (这给PyPy项目带来了新的挑战)将会帮助PyPy变得越来越好。
官方文档 PyPy插件
Cron改进¶
关于作者:Łukasz Mierzwa
唯一cron¶
你现在可以避免重复cron了。uWSGI的master将会跟踪单个任务的结束,直到它结束了,才会触发相同的cron:
[uwsgi]
unique-cron = -1 -1 -1 -1 -1 my_script.sh
cron2语法¶
–cron选项的一个键值对变体现已可用:
[uwsgi]
cron2 = minute=39,hour=23,month=-1,week=-1,day=-1,unique=1,legion=foobar,harakiri=30
harakiri cron¶
在使用 cron2
选项的时候,允许你为一个cron任务设置一个harakiri超时时间。仅需添加 harakiri=n
到选项中。
GNU Hurd支持¶
Debian GNU/Hurd最近已经发布。可以在其上构建uWSGI 1.9.11,然而,已经挖槽的测试非常少。
内存卸载引擎¶
想法:Stefano Brentegani
当从缓存提供内容的时候,在从内存传输到socket期间可能会阻塞worker。
一个名为”memory”的新的卸载系统允许卸载内存传输。缓存路由器自动支持它。对更多领域的支持将会尽快添加。
要启用它,仅需添加 --offload-threads <n>
新的Websockets聊天例子¶
已添加一个使用Redis的Websockets聊天例子到这个repo中:
https://github.com/unbit/uwsgi/blob/master/tests/websockets_chat.py
错误路由¶
现在,你可以定义一个一旦在插件中设置HTTP状态码就会立即执行的路由表。
这让你可以完全修改响应。这对于自定义错误码很有用。
所有的路由标准选项都可用 (包括标签) ,加上一个优化的 error-route-status
,它匹配指定的HTTP状态码:
[uwsgi]
error-route-status = 502 redirect:http://unbit.it
支持wsgi.file_wrapper中的特殊情况使用¶
一般情况下, wsgi.file_wrapper
可调用需要一个类文件对象。PEP 333/3333报告了一种当该对象不是一个文件时的特殊模式 (调用 read()
,直到消费完该对象)。uWSGI现在支持这种模式 (即使是以一种Hack方式)。
The harakiri路由动作¶
现在,你可以使用内部路由,为每个请求设置一个harakiri计时器:
[uwsgi]
; set harakiri to 30 seconds for request starting with /slow
route = ^/slow harakiri:30
RPC封装¶
已经扩展了RPC插件,以允许与其他标准进行相互操作。
目前,公开了一个简单的HTTP封装器和一个XML-RPC封装器。
该HTTP简单封装器通过解析 PATH_INFO
工作。
/foo/bar/test
调用的结果将是
uwsgi.rpc(‘foo’, ‘bar’, ‘test’)
要启用此HTTP模式,仅需设置 modifier2
为 ‘2’:
[uwsgi]
http-socket = :9090
http-socket-modifier1 = 173
http-socket-modifier2 = 2
; load the rpc code
import = myrpcfuncs.py
或者 (拥有更多的控制权)
[uwsgi]
http-socket = :9090
route-run = uwsgi:,173,2
; load the rpc code
import = myrpcfuncs.py
XML-RPC封装器工作方式相同,但它使用的modifier2值为‘3’。它要求uWSGI的构建启用了libxml2。
[uwsgi]
http-socket = :9090
route-run = uwsgi:,173,3
; load the rpc code
import = myrpcfuncs.py
然后只需调用它:
proxy = xmlrpclib.ServerProxy("http://localhost:9090')
proxy.hello('foo','bar','test')
你可以使用路由将多个封装器组合在一起。
[uwsgi]
http-socket = :9090
; /xml force xmlrpc wrapper
route = ^/xml uwsgi:,173,3
; fallback to HTTP simple
route-if-not = startswith:${PATH_INFO};/xml uwsgi:,173,2
; load the rpc code
import = myrpcfuncs.py
uWSGI 1.9.10¶
更新日志[20130511]
新特性¶
欢迎来到gccgo¶
gcc 4.8中的Go支持是惊人的,多亏了堆栈分割特性,现在,你可以使用goroutine,而无需分配一整个pthread。
Go 1.1将不再与uWSGI兼容,而gccgo将会成为运行go应用的官方方式。
gccgo插件在开发的早期阶段,但是已经可以在preforking模式中运行了。
我们在努力地开发一个”goroutines”循环引擎。敬请关注。
最后的路由¶
现在,你可以在一个请求之后运行路由规则了。显然,请求之后并非所有公开的操作都有意义,但你应该可以编写甚至更复杂的设置。
看看这个基于HTTP响应状态的请求限制器 (一个你只能在请求之后获取的值):
https://github.com/unbit/uwsgi/blob/master/t/routing/errorlimiter.ini
uWSGI 1.9.9¶
更新日志[20130508]
特别警告!!!¶
router_basicauth插件已经更改了它的默认行为,当鉴权失败的时候,会返回”break”。
“basicauth-next”动作,使用老行为 (返回”next”)
这个新方法应该减少由错误配置引发的安全性问题
错误修复¶
- 不为“不负责的”插件增加”tx”统计数据计数
- 修复–backtrace-depth
- 修复cache-sync解析
- 修复mule farm初始化
- 修复当使用正则表达式条件路由时的多线程问题
- 修复psgi插件中的default-app使用
- 修复python动态模式 + 线程
- 修复重试出现时corerouter中的错误报告
- 为网关正确报告harakiri条件
新特性¶
WebDav插件¶
WebDav是该项目要求得挺多的特性之一。现在,我们有了一个测试版本的插件,已经支持诸如carddav这样的其他标准:
https://github.com/unbit/uwsgi/blob/master/t/webdav/carddav.ini
官方的modifier是35,并且要把一个简单的目录作为webdav共享(适用于windows, gnome....)进行挂载,你仅需指定–webdav-mount选项:
[uwsgi]
plugin = webdav
http-socket = :9090
http-socket-modifier1 = 35
webdav-mount = /home/foobar
记得保护共享:
[uwsgi]
plugin = webdav,router_basicauth
http-socket = :9090
http-socket-modifier1 = 35
route-run = basicauth:CardDav uWSGI server,unbit:unbit
webdav-mount = /home/foobar
WebDav属性是作为文件系统xattr进行存储的,因此,一定要使用支持它的文件系统 (ext4, xfs, hfs+...)
LOCK/UNLOCK支持仍然是不完整的
稍后会有官方文档。
支持Go 1.1 (或多或少,对于go用户而言,是个坏消息……)¶
虽然你可以成功地把go 1.1应用嵌入到uWSGI中,但是go 1.1将是完全fork()不安全的。
那意味着,你不能使用多进程,master,mule等等。
基本上,一半的uWSGI特性在go应用中都不怎么能用。
以后可能会有所改变,但目前,我们的目标是与gccgo项目更好的集成。
将会继续支持Go 1.0.x (除非gccgo显示了自己是一个更好的选择)
将会实现更多的特性。
改进的异步模式¶
已经更新Stackless, Greenlet和Fiber支持,以支持新的异步特性
radius插件¶
现在,你可以使用router_radius插件来鉴权radius服务器了:
[uwsgi]
plugin = webdav,router_radius
http-socket = :9090
http-socket-modifier1 = 35
route-run = radius:realm=CardDav uWSGI server,server=127.0.0.1:1812
webdav-mount = /home/foobar
SPNEGO插件¶
另一个鉴权后端,使用SPNEGO (kerberos)
[uwsgi]
plugin = webdav,router_spnego
http-socket = :9090
http-socket-modifier1 = 35
route-run = spnego:HTTP@localhost
webdav-mount = /home/foobar
这个插件还在测试阶段,因为它会内存泄漏 (看起来是MIT-kerberos中的一个问题),并Heimdal实现并没有用。
欢迎更多的报告
ldap认证¶
(作者:Łukasz Mierzwa)
目前,它缺乏SASL支持。很快会对其进行改善。
[uwsgi]
...
plugins = router_ldapauth
route = ^/a ldapauth:LDAP realm,url=ldap://ldap.domain,com;basedn=ou=users,dc=domain.com;binddn=uid=proxy,dc=domain,dc=com;bindpw=password
新的内部路由特性¶
我们移除了GOON动作,因为它乱糟糟的,并且基本上对新的鉴权方法没用
添加了”setscriptname”动作来覆盖内部计算的SCRIPT_NAME (不仅是变量)
“donotlog”动作强制uWSGI不要记录当前请求
改进了”regexp”路由条件,以允许分组。现在,你可以很容易地操作字符串,并把它们当成新的请求变量进行添加:
[uwsgi]
...
route-if = regexp:${REQUEST_URI};^/(.)oo addvar:PIPPO=$1
route-run = log:PIPPO IS ${PIPPO}
这将会获取foo的第一个字符,然后将其放到PIPPO请求变量中
Gevent atexit钩子¶
uwsgi.atexit钩子现在由gevent插件兑现 (作者:André Cruz)
xattr插件¶
xattr插件让你在内部路由子系统中应用文件扩展属性:
[uwsgi]
...
route-run = addvar:MYATTR=user.uwsgi.foo.bar
route-run = log:The attribute is ${xattr[/tmp/foo:MYATTR]}
或者 (带2个变量的变种)
[uwsgi]
...
route-run = addvar:MYFILE=/tmp/foo
route-run = addvar:MYATTR=user.uwsgi.foo.bar
route-run = log:The attribute is ${xattr2[MYFILE:MYATTR]}
Legion守护进程¶
(作者:Łukasz Mierzwa)
不,这不是一个黑金属乐队,它是 uWSGI Legion子系统 的一个新特性,允许你只在实例是lord的时候运行外部进程:
[uwsgi]
master = true
http = :8081
stats = :2101
wsgi-file = tests/staticfile.py
logdate = true
legion = legion1 225.1.1.1:19678 100 bf-cbc:abc
legion-node = legion1 225.1.1.1:19678
legion-attach-daemon = legion1 memcached -p 10001
legion-smart-attach-daemon = legion1 /tmp/memcached.pid memcached -p 10002 -d -P /tmp/memcached.pid
–touch-exec¶
有一个新的”touch”选项 (像–touch-reload)可以用了,触发一条命令的执行:
[uwsgi]
...
touch-exec = /tmp/foobar run_my_script.sh
touch-exec = /var/test/foo.txt run_my_second_script.sh arg1 arg2
用于缓存的数学运算¶
现在,你可以使用缓存子系统来存储64位有符号数,并在其上应用原子操作。
已使用5个新函数扩展了uwsgi api (目前仅有python插件公开):
*uwsgi.cache_num(key[,cache]) ->从指定项获取64位数
*uwsgi.cache_inc(key[,amount=1,expires,cache]) -> 增加指定键指定值
*uwsgi.cache_dec(key[,amount=1,expires,cache]) -> 减少指定键指定值
*uwsgi.cache_mul(key[,amount=2,expires,cache]) -> 乘以指定键指定值
*uwsgi.cache_div(key[,amount=2,expires,cache]) -> 除以指定键指定值
已将该新api暴露给路由子系统了,允许你事先高级模式,例如请求限制器:
https://github.com/unbit/uwsgi/blob/master/t/routing/limiter.ini
这个例子显示了热限制一个单一的IP的请求为每30秒10个
这个新功能的长期目标是作为即将到来的度量子系统的基础
uWSGI 1.9.8¶
更新日志[20130423]
注意:这是一个“紧急”版本,修复两个重载和使用async+uGreen时引发崩溃的回退
错误修复¶
- 修复重载master时的崩溃
- 修复异步模式+uGreen中的崩溃
- ‘mime’路由变量要求一个请求变量 (而不是一个原始字符串)
可用性¶
你可以从http://projects.unbit.it/downloads/uwsgi-1.9.8.tar.gz这里下载uWSGi 1.9.8
uWSGI 1.9.7¶
错误修复¶
- 修复teajs引擎构建
- 修复卸载状态码 (当卸载请求的时候,设置为202)
- 在60秒分辨率内执行cron任务,而不是61秒
- 修复websocket代理
- 检查python3 unicode编码 (而不是崩溃……)
- 修复重载时的ipcsem移除
- 修复OpenBSD, NetBSD和DragonFlyBSD上的kqueue定时器
- 修复/重新实现perl的uwsgi::register_rpc
- 修复sendfile()错误时的fd泄漏
- 修复在使用gzip文件变量时的Content-Length
- 允许非请求插件注册rpc函数
- 对于cgroups更健壮的错误检查
- 当挂载多个perl应用的时候,遵循PSGI插件中更多SCRIPT_NAME
新特性¶
Legion cron¶
当应用的多个实例在运行的时候,一个共同的需求是强制只有其中一个实例运行cron任务。新的–legion-cron使用 uWSGI Legion子系统 来做到这点:
[uwsgi]
; use the new legion-mcast shortcut (with a valor 90)
legion-mcast = mylegion 225.1.1.1:9191 90 bf-cbc:mysecret
; run the script only if the instance is the lord of the legion "mylegion"
legion-cron = mylegion -1 -1 -1 -1 -1 my_script.sh
Curl cron¶
添加了curl_cron插件,允许cron子系统调用url (通过libcurl) 而非unix命令:
[uwsgi]
; call http://uwsgi.it every minute
curl-cron = -1 -1 -1 -1 -1 http://uwsgi.it/
请求的输出会被记录在日志中
UWSGI_EMBED_PLUGINS构建变量¶
现在在构建阶段可以动态嵌入插件了。看看这个例子:
UWSGI_EMBED_PLUGINS=gridfs,rack UWSGI_PROFILE=psgi make
这将会构建一个单片二进制文件,对psgi使用默认的配置文件,以及带有gridfs和rack插件 (都嵌入到二进制文件中)
Gzip缓存¶
cachestore路由功能现在可以直接将项以gzip格式存储了。
看看CachingCookbook: https://uwsgi-docs.readthedocs.io/en/latest/tutorials/CachingCookbook.html
–skip-atexit¶
mongodb客户端库中的一个错误可能会导致uWSGI服务器在关机/重载期间崩溃。这个选项避免了调用atexit()钩子。如果你在构建一个 GridFS插件 基础设施,那么你也许想要使用这个选项,同时让MongoDB小伙伴解决问题。
proxyhttp和proxyuwsgi¶
http和uwsgi路由指令现在更智能了。你可以缓存它们的输出,并且在日志中获取正确的状态码。
这要求你不要使用卸载。如果卸载就在那里,并且对于这两个路由器不想使用卸载,那么使用以proxy为前缀的变量,这将会跳过卸载。
现在,你可以做些很酷的事情了,例如:
[uwsgi]
socket = 127.0.0.1:3031
; create a cache of 100 items
cache = 100
; check if a cached value is available
route-run = cache:key=${REQUEST_URI}
; proxy all request to http://unbit.it
route-run = http:81.174.68.52:80,unbit.it
; and cache them for 5 minutes
route-run = cachestore:key=${REQUEST_URI},expires=300
–alarm-fd¶
我们正在改进 uWSGI告警子系统 (自1.3起) ,让它更少依赖日志行。现在,当fd准备好读的时候,你可以触发告警了。
这对于与Linux的eventfd()功能集成是非常有用的。
例如,当你的cgroup正在运行OOM-Killer的时候,你可以监控(以及抛出一个异常)
[uwsgi]
; define an 'outofmemory' alarm that simply print the alarm in the logs
alarm = outofmemory log:
; raise the alarm (with the specified message) when fd is ready (this is an eventfd se we read 8 bytes from the fd)
alarm-fd = outofmemory $(CGROUP_OOM_FD):8 OUT OF MEMORY !!!
在这个例子中,CGROUP_OOM_FD是一个环境变量,映射到从某些启动脚本继承过来的一个eventfd()文件描述符数目。或许 (在不久的将来),我们会能够直接在uWSGI中定义这类型的监控。
更多关于eventfd() + cgroup集成在这里:https://www.kernel.org/doc/Documentation/cgroups/cgroups.txt
一个样例perl启动脚本:
use Linux::FD;
use POSIX;
my $foo = Linux::FD::Event->new(0);
open OOM,'/sys/fs/cgroup/uwsgi/memory.oom_control';
# we dup() the file as Linux::FD::Event set the CLOSE_ON_EXEC bit (why ???)
$ENV{'CGROUP_OOM_FD'} = dup(fileno($foo)).'';
open CONTROL,'>/sys/fs/cgroup/uwsgi/cgroup.event_control';
print CONTROL fileno($foo).' '.fileno(OOM)."\n";
close CONTROL;
exec 'uwsgi','mem.ini';
默认编译的spooler服务器插件和cheaper busyness算法¶
在极端高负载场景中,busyness cheaper算法 (来自Łukasz Mierzwa) 在过去就是一个银弹,它允许基于实时使用时间,考虑性能和响应时间,自适应生成进程。出于这个原因,现在默认内置了这个插件。
除此之外,还在默认构建配置文件中添加了远程spooler插件 (允许外部进程排队作业) 。
uWSGI 1.9.6¶
更新日志20130409
新特性¶
Sqlite和LDAP插件化¶
将配置存储在sqlite数据库或者LDAP树中是一个非常“罕见的”配置uWSGI实例的方式。出于这样的原因,已将它们转移到专用插件。
如果你将配置存储到一个sqlite数据库库中,只需添加–plugin sqlite3。对于LDAP,仅需条件–plugin ldap:
uwsgi --plugin sqlite --sqlite config.db
配置带内部路由的动态应用¶
直至现在,你需要配置你的web服务器动态加载应用。
添加了三条新指令来按需加载应用。
看看这个例子:
[uwsgi]
http-socket = :9090
route = ^/foo chdir:/tmp
route = ^/foo log:SCRIPT_NAME=${SCRIPT_NAME}
route = ^/foo log:URI=${REQUEST_URI}
route = ^/foo sethome:/var/uwsgi/venv001
route = ^/foo setfile:/var/uwsgi/app001.py
route = ^/foo break:
route = ^/bar chdir:/var
route = ^/bar addvar:SCRIPT_NAME=/bar
route = ^/bar sethome:/var/uwsgi/venv002
route = ^/bar setfile:/var/uwsgi/app002.py
route = ^/bar break:
正如你所看到的,现在重写SCRIPT_NAME非常容易。sethome指令目前仅适用于python应用 (它表示’virtualenv’)
Carbon平均值计算 (作者:Łukasz Mierzwa)¶
现在,你可以配置carbon插件在尚未管理任何请求的时候,如何发送响应平均数。
有三种方式:
–carbon-idle-avg none - 如果没有请求,则不要推送任何avg_rt值
—carbon-idle-avg last - 使用最后一次计算的avg_rt值(默认)
–carbon-idle-avg zero - 如果没有请求,则推送0
内部路由的数值检查¶
有了新的检查:
ishigher 或者 ‘>’
islower 或者 ‘<’
ishigherequal 或者 ‘>=’
islowerequal 或者 ‘<=’
例如:
[uwsgi]
route-if = ishigher:${CONTENT_LENGTH};1000 break:403 Forbidden
内部路由子系统中的math和time¶
如果你构建具有matheval(debian/ubuntu上是matheval-dev)支持的uWSGI,那么你将通过’math’路由变量,在你的路由系统中获得math支持。
目前,添加了’time’路由变量,它只导出’unix’字段,返回纪元。
看看这个疯狂的例子:
[uwsgi]
http-socket = :9090
route-run = addvar:TEMPO=${time[unix]}
route-run = log:inizio = ${TEMPO}
route-run = addvar:TEMPO=${math[TEMPO+1]}
route-run = log:tempo = ${TEMPO}
正如你所见,路由子系统可以存储值到请求变量中 (这里,我们创建了一个’TEMPO’变量,然后你将能够访问它,甚至是在你的应用请求变量中也可以访问它)
‘math’运算可以引用请求变量
查看matheval文档,看看支持的运算:http://matheval.sourceforge.net/docs/index.htm
新增非标准的seek()和tell()到wsgi.input (要求post-buffering)¶
在测试’Klaus’项目 (https://github.com/jonashaag/klaus)的’smart模式’时,我们注意到当处于smart模式时,它违反了WSGI的标准调用seek()和tell()。
当post-buffering启用的时候,我们添加了对这两种方法的支持。
记住:它们违反了WSGI标准,因此,试着避免使用它们 (如果可以的话)。有更好的方式可以来完成相同的事。
Pyshell改进,亦称Welcome IPython (想法:C Anthony Risinger)¶
在使用–pyshell的时候,你可以调用ipython shell而不是默认的:
uwsgi -s :3031 --pyshell="from IPython import embed; embed()"
显然,你可以传递任何代码给–pyshell
‘rpcraw’路由指令¶
另一个强大但是极度危险的动作。它会调用一个rpc函数,直接发送其返回值给客户端 (不进行进一步的处理)。
空的返回值表示“进入下一条路由规则”。
返回值必须是有效的HTTP:
uwsgi.register_rpc('myrules', function(uri) {
if (uri == '/foo') {
return "HTTP/1.0 200 OK\r\nContent-Type: text/plain\r\nServer: uWSGI\r\nFoo: bar\r\n\r\nCiao Ciao";
}
return "";
});
[uwsgi]
plugin = v8
v8-load = rules.js
route = ^/foo rpcraw:myrules ${REQUEST_URI}
HTTP Range头的初步支持¶
range请求头允许只请求资源的一部分 (例如,一个静态文件的有限字节集合)。
当提供静态文件服务的时候,可以使用该系统,但默认禁用它。仅需添加–honour-range来启用它。
未来,它将用于文件封装 (例如wsgi.file_wrapper) 和 GridFS插件 (这就是默认不启用它的原因,因为你很有可能已经在你的应用中实现了range管理)
‘lord’路由条件¶
我们在努力地实现一个使用 uWSGI Legion子系统 的真正令人惊奇的集群子系统。
现在,当一个实例是lord的时候,你可以执行内部路由规则了:
[uwsgi]
...
route-if = lord:mylegion log:I AM THE LORD !!!
这个”I AM THE LORD !!!”日志行只有在实例是legion ‘mylegion’的lord时才会打印
–for-times配置逻辑¶
你可以使用–for-times来运行多次(指定数目)uWSGI选项:
[uwsgi]
for-times = 8
mule = true
endfor =
这将会生成8个mule
‘uwsgi’路由变量¶
在定义路由规则的时候访问uWSGI内部参数将会很方便。’uwsgi’路由变量就是这样的变量的容器。
目前,它导出’wid’ (运行该规则的worker的id) 和’pid’ (运行该规则的worker的pid)
[uwsgi]
master = true
processes = 4
; stupid rule... break connections to the worker 4
route-if = ishigher:${uwsgi[wid]};3 break:403 Forbidden
‘alarm’路由动作¶
现在,你可以从路由子系统触发告警:
[uwsgi]
alarm = pippo cmd:cat
route = ^/help alarm:pippo ${uwsgi[wid]} ${uwsgi[pid]}
http-socket = :9090
当请求/help的时候,会触发’pippo’告警,传递wid和pid作为消息
欢迎来到ruby shell¶
和–pyshell一样,现在,我们有ruby shell了:
uwsgi --rbshell -s :3031
或者
uwsgi --rbshell="require 'pry';binding.pry" -s :3031
关于使用pry shell: http://pryrepl.org/
... 以及欢迎来到Lua shell¶
跟python和ruby一样,甚至Lua都有其shell了。仅需添加–lua-shell
Legion子系统的改进 (作者:Łukasz Mierzwa)¶
添加了两个新的钩子:–legion-node-joined和–legion-node-left
更多的微调¶
添加了–socket-sndbuf和–socket-rcvbuf,从而允许调整发送uWSGI socket的接收缓存 (小心使用)
V8改进和TeaJS集成¶
uWSGI V8支持 插件正继续改进中。主要的目标仍然是 uWSGI内部路由 ,但是JSGI支持基本完成了,我们正在致力于TeaJS (老的v8cgi)集成:http://code.google.com/p/teajs/
更多内容很快就会实现……
uWSGI 1.9.5¶
更新日志20130404
错误修复¶
- 修复cachestore路由指令的内存泄漏 (Riccardo Magliocchetti)
- 修复carbon插件中的内存泄漏 (Riccardo Magliocchetti)
- 修复cgi插件中的内存泄漏 (Riccardo Magliocchetti)
- 修复旧式python动态应用
- 强制emperor对vassal遵循–max-fd
- 改进的使用post-buffering的PSGI查找
- 修复kvlist转义
新特性¶
V8改进¶
V8插件继续改进。JSGI 3.0初步支持,以及多线程已经可用。
已实现’require’ commonjs标准。
编写commonjs规格说明将是一个非常漫长的工作,因此,也许与像teajs (老的v8cgi)这样的项目合作将是一个更好的选择。
与此同时,我们正在输出文档: uWSGI V8支持
uWSGI 1.9.4¶
更新日志20130330
错误修复¶
修复统计信息子系统导出的缓存统计信息 (Łukasz Mierzwa)
修复after_request中的CoroEV错误 (Tom Molesworth和John Berthels)
更新从永久化存储中恢复之后的缓存项 (Łukasz Mierzwa)
修复非worker进程中的信号处理
修复多个Mule设置中的惊群效应(thundering herd)
移植C++骨架插件到新API
修复当uWSGI作为共享库构建时的重载
新特性¶
SmartOS官方支持¶
从现在起,官方支持的操作系统包含了SmartOS
V8初始支持¶
之前关于使用Lua编写uWSGI路由规则和配置的建议,提醒了大量的javascript用户,他们指出,javascript自身可能是一个有效的替代品。V8插件现已推出,支持RPC,信号处理器和配置。要构建它,你需要libv8头文件:
python uwsgiconfig.py --plugin plugins/v8
var config = {};
config['socket'] = [':3031', ':3032', ':3033'];
config['master'] = true;
config['processes'] = 3+1;
config['module'] = 'werkzeug.testapp:test_app';
config;
uwsgi --plugin v8 --config foo.js
前一个例子将让你用javascript编写动态配置,而你可以通过RPC子系统来导出javascript函数:
function part1(request_uri, remote_addr) {
return '<h1>i am part1 for ' + request_uri + ' ' + remote_addr + "</h1>" ;
}
function part2(request_uri, remote_addr) {
return '<h2>i am part2 for ' + request_uri + ' ' + remote_addr + "</h2>" ;
}
function part3(request_uri, remote_addr) {
return '<h3>i am part3 for ' + request_uri + ' ' + remote_addr + "</h3>" ;
}
uwsgi_register_rpc('part1', part1);
uwsgi_register_rpc('part2', part2);
uwsgi_register_rpc('part3', part3);
[uwsgi]
plugin = v8
v8-load = func.js
cache2 = name=foobar,items=10
http-socket = :9090
route-run = addheader:Content-Type: text/html
route-run = cache:key=pippo,name=foobar
route-run = cachestore:key=pippo,name=foobar
route-run = rpcnext:part1 ${REQUEST_URI} ${REMOTE_ADDR}
route-run = rpcnext:part2 ${REQUEST_URI} ${REMOTE_ADDR}
route-run = rpcnext:part3 ${REQUEST_URI} ${REMOTE_ADDR}
route-run = break:
前一个例子从3个javascript函数中生成一个HTTP响应,让后将其存储在uWSGI缓存中。
对rpcnext感到好奇?
rpcnext路由动作¶
我们已经可以调用路由子系统中的rpc函数来生成响应了。通过此rpcnext动作 (别名又为rpcblob),你可以调用多个rpc函数,并且在一个单一的响应中组装返回值。
Legion改进¶
我们基本没对 uWSGI Legion子系统 进行加固。其目标是对于uWSGI 2.0,可以拥有一个坚如磐石的集群实现,让你甚至可以从你的应用使用它。
1.9.4中的代码已经被Łukasz Mierzwa做了一点重构,使得与外部插件的集成更容易些。
添加了一个新的”join”钩子,当一个节点变成legion的一个活跃的部分时会立即调用它 (也就是说,它是quorum的一部分)。
uWSGI 1.9.3¶
更新日志 20130328
错误修复¶
修复当使用虚拟环境的时候,在JVM构建系统中的导入问题 (Ryan Kaskel)
修复apache 2.4的mod_proxy_uwsgi问题
修复当Status是由php应用自身创建的时候的php头部问题
新特性¶
可插拔配置系统 (带Lua支持)¶
从此版本起,你将能够将配置器 (例如已经可用的xml, ini, yaml, json, ldap, sqlite...) 作为插件实现。
第一个可用的配置器是Lua (由lua插件提供)。
这是一个用Lua写的样例配置:
config = {}
config['immediate-uid'] = 'roberto'
config['immediate-gid'] = 'roberto'
config['http-socket'] = ':9090'
config['env'] = { 'FOO=bar', 'TEST=topogigio' }
config['module'] = 'werkzeug.testapp:test_app'
return config
你可以这样加载它:
uwsgi --plugin lua --config config.lua
–config选项是加载可插拔配置器的方式。你甚至可以使用自己的版本来覆盖已经可用的内嵌的配置器。
Emperor已被扩展来支持可插拔配置器:
[uwsgi]
emperor = /etc/uwsgi/vassals
emperor-extra-extension = .lua
emperor-extra-extension = .foo
增加emperor-extra-extension将会允许emperor搜索指定的扩展名,使用–config选项传递配置文件给vassal。
即用setuid和setgid¶
在最近的uWSGI邮件列表支线中,对于tyrant模式,不依赖于文件系统权限的需求应运而生。
尽管它是最安全的方法,但是还是添加了两个新的选项–immediate-uid和–immediate-gid。
在你的vassal文件顶部设置它们将会强制实例尽快setuid()/setgid(),并且(理论上来讲)不可能覆盖掉它们。
这里,“理论上来讲”这个词是关键,你需要记住,uWSGI中的一个安全性问题可能允许恶意用户修改权限,因此,如果你真的在意安全性 (或者不信任uWSGI开发者;) ,那么就要在生成vassal/实例之前删除权限 (就像在标准的tyrant模式中)
tyrant模式中的遵循符号链接(Honouring symlinks)¶
已经添加了选项–emperor-tyrant-nofollow,用来在tyrant模式中搜索uid/gid的时候,迫使emperor遵循符号链接。
该选项允许系统管理员简单的符号连接配置,并且只是修改符号连接自身的uid/gid (记得把-h选项传递给chown!!!)
“rpcret”路由动作 (或者使用Lua编写高级规则)¶
uWSGI内部路由 在持续改善中。
你已经可以为路由系统调用rpc函数了 (生成绕过WSGI/PSGI/Rack/...引擎的响应):
[uwsgi]
lua-load = myrpcfunctions.lua
route = ^/foo/(.+)/call rpc:hello_world ${REMOTE_ADDR} $1
hello_world rpc函数是在myrpcfunctions.lua中定义(以及注册)的,它接收两个参数。
当路由正则表达式匹配上的时候,会调用该函数,而它的输出会被发送给客户端。
“rpcret”工作方式相似,但不是生成响应,而是生成一个路由返回码:
function choose(request_uri, remote_addr)
print( 'REQUEST_URI is ' ..request_uri.. ' (from Lua)')
if request_uri == '/topogigio' then
return "goto topogigio"
end
return "break 500 Internal server Error !!!"
end
print('Hello Hello')
uwsgi.register_rpc('choose', choose)
以及uWSGI配置:
[uwsgi]
route-run = rpcret:choose ${REQUEST_URI} ${REMOTE_ADDR}
route-run = break
route-label = topogigio
route-run = log:i am topogigio !!!
这个’choose’ rpc函数将会在每个请求中被调用,传递REQUEST_URI和REMOTE_ADDR作为它的参数。
函数的返回字符串将会被用来指示下一步做什么 (从内部路由的角度)。
目前支持的返回字符串是:
next
移到下一条规则
continue
将请求传递给请求处理器
goon
移动到下一个有着不同动作的规则
break
关闭连接,有一个可选的状态码
goto <label>
跳到指定的标签
显然,可以在任何uWSGI支持的语言/平台中编写用于rpcret的rpc函数,但是我们强烈建议出于性能原因,使用Lua (与纯C相比,影响是非常不相关的)。如果你幸运,可以使用LuaJit,你会体验到甚至更好的性能,因为对于这种任务,JIT编译器是最好的方法。
uWSGI 1.9.2¶
更新日志 20130326
新特性¶
route-run以及cachestore路由操作¶
现在,你可以在uWSGI缓存中自动存储响应:
[uwsgi]
http-socket = :9090
; ensure the sweeper thread will run
master = true
cache2 = name=pippo2,items=10
module = werkzeug.testapp:test_app
route-run = cache:key=${REQUEST_URI},name=pippo2
route-run = cachestore:key=${REQUEST_URI},expires=30,name=pippo2
这个例子检查每一个请求在缓存’pippo2’中是否可用。如果不可用,那么请求插件 (werkzeug test app)将会正常运行,然后其输出将会存储在缓存中 (只有在它返回HTTP 200状态的时候才会存储)
--route-run
是一个新的选项,允许你直接调用路由动作,而无需检查特定的条件 (是哒,这是一个优化)
SSI插件 (测试版)¶
查看官方文档 docs SSI (服务器端包含,Server Side Includes) 插件
uWSGI 1.9.1¶
1.9版本树的第一个小版本。
Legion scrolls api¶
scrolls是附加到 Legion 集群中的每个成员上的文本。我们正在慢慢地定义一个API,以允许开发者直接在他们的应用和配置中使用legion子系统。1.9.1中添加的是uwsgi.scrolls(legion)函数,它返回由整个集群定义的当前scrolls的一个列表/数组。这还不是完全可用(有用)的,后续还会有更多的函数……
按需vassals¶
对于大规模托管的更好的资源使用的另一步。你现在可以告诉 Emperor 只在对指定的socket进行的第一个请求之后才启动vassal。与 –idle/–die-on-idle 选项相结合,就可以拥有完全按需的应用了。
要为每个vassal定义等待的socket,你有3个选项:
–emperor-on-demand-extension <ext>¶
这将会指示Emperor去检查一个名为<vassal>+<ext>的文件,如果该文件可用,那么将会读取,并把其内容作为socket等待的内容:
uwsgi --emperor /etc/uwsgi/vassals --emperor-on-demand-extension .socket
假设有一个在/etc/uwsgi/vassals中的myapp.ini文件,那么将会搜索/etc/uwsgi/vassals/myapp.ini.socket (而它的内容会用作这个socket的名字)
在第一次连接的时候,会生成vassal,并且会将socket作为文件描述符0进行传递。文件描述符0总是会被uWSGI检查,因此你并不需要在vassal文件中指定一个–socket选项。这自动对uwsgi socket有效,如果你使用其他协议 (例如http或者fastcgi),那么你必须使用–protocol选项来指定它
–emperor-on-demand-directory <dir>¶
这是较不通用的方法,只支持UNIX socket。基本上,vassal的名字 (不带扩展名和路径)会被附加到指定的目录,并带上.socket扩展名,然后作为按需 socket使用:
uwsgi --emperor /etc/uwsgi/vassals --emperor-on-demand-directory /var/tmp
使用前一个例子,socket /var/tmp/myapp.socket将会被自动绑定
–emperor-on-demand-exec <cmd>¶
只是你(非常有可能)用在很大的部署中。每次添加了一个新的vassal,就会运行提供的命令,并把vassal名当做第一个参数传递。命令的STDOUT作为socket名使用。
pyring构建配置文件¶
这是一个非常特别的构建配置文件,允许你自动构建一个带完整python支持和模块化jvm + ring执行的虚拟环境(注:原文是jvm + ring honouring virtualenvs)的uWSGI栈。
crypto记录器¶
如果你在云服务上托管你的应用,并且没有永久存储,那么你可能想要发送日志到外部系统。忧伤的是,日志常常包含敏感信息,你不应该明文传输它们。新的crypto记录器试着解决这个问题,它允许你加密每个日志包,然后通过udp将它发送给能够解密它的服务器。
在下面的例子中
uwsgi --plugin logcrypto --logger crypto:addr=192.168.173.22:1717,algo=bf-cbc,secret=ciaociao -M -p 4 -s :3031
将会发送每个日志包给192.168.173.22:1717指定的udp服务器,根据’ciaociao’密钥,使用blowfish cbc算法来加密文本。
这里是一个可用的样例服务器:
https://github.com/unbit/uwsgi/blob/master/contrib/cryptologger.rb
rpc内部路由指令¶
添加了”rpc”路由指令,允许你直接从路由子系统调用rpc函数,并将它们的输出转发到客户端。
看看下面的例子:
[uwsgi]
http-socket = :9090
route = ^/foo addheader:Content-Type: text/html
route = ^/foo rpc:hello ${REQUEST_URI} ${HTTP_USER_AGENT}
route = ^/bar/(.+)$ rpc:test $1 ${REMOTE_ADDR} uWSGI %V
route = ^/pippo/(.+)$ rpc:test@127.0.0.1:4141 $1 ${REMOTE_ADDR} uWSGI %V
import = funcs.py
carbon插件对名字解析的初步支持¶
你可以使用主机名来指定carbon服务器。当前的代码是非常简单的。以后的更新将会支持轮询。
新的路由条件¶
添加了新的路由条件 (equal,startswith,endswith,regexp),看看更新的文档:
https://uwsgi-docs.readthedocs.io/en/latest/InternalRouting.html#the-internal-routing-table
‘V’魔术变量¶
你可以在配置中使用%V魔术变量来引用uWSGI版本字符串
‘mongodb’一般插件¶
这是一个用于不能访问共享libmongoclient的packager的商品插件。这基本上在一个可以被其他mongodb插件使用的新的共享对象中链接它。
通过网络构建profile¶
现在,你可以使用url(支持http, https和ftp)来引用构建配置文件了:
UWSGI_PROFILE=http://uwsgi.it/psgi.ini make
uWSGI 1.9¶
这是一个将会指向LTS 2.0点版本。它引入了许多内部改动,并且移除了大量基本未使用、不能用或者太丑的功能。
1.0.x弃用的选项已经被永久删除。
一切皆非阻塞¶
从现在开始,所有的请求差距,都需要是非阻塞的。已经添加了一个新的 C/C++/Obj-C api集合,来帮助用户/开发者安全编写非阻塞代码。诸如RPC这样的插件已经使用新api进行重写了,这让你能够通过像Gevent或者Coro::Anyevent这样的引擎来使用它。重写了异步模式,从而更好地配合新规则。你可以在 uWSGI异步/非堵塞模式 (已更新至uWSGI 1.9) 找到更多信息。新的异步模式要求某些形式的协程/绿色线程/挂起引擎正确工作。再次,看看 uWSGI异步/非堵塞模式 (已更新至uWSGI 1.9)
Coro::AnyEvent¶
这个Perl/PSGI插件是uWSGI项目最老的插件之一,但过去它并不支持高级方式下的异步模式。
多亏了新的 uWSGI异步/非堵塞模式 (已更新至uWSGI 1.9) 模式,才添加了一个Coro::Anyevent (coroae)循环引擎。
要构建它,你需要Coro::Anyevent包 (可以使用cpanm来获取它),然后添加–coroae <n> 到你的选项中,其中,<n>是要生成的异步核心数。
JVM插件¶
在uWSGI 1.9中,我们终于有了一个真正可用的JVM基础设施了。看看 uWSGI服务器中的JVM (更新至1.9) 中的文档吧。改进的 JWSGI接口 支持,以及新的Clojure Clojure/Ring JVM请求处理器 插件都可以使用了。
语言独立的HTTP体管理¶
写uWSGI请求插件最恼人的任务之一就是每次都要重新实现http体读取器的管理。
新的非阻塞api添加了3个简单通用的C/C++/Obj-C函数来以一种语言独立的方式处理它:
char *uwsgi_request_body_read(struct wsgi_request *wsgi_req, ssize_t hint, ssize_t *rlen);
char *uwsgi_request_body_readline(struct wsgi_request *wsgi_req, ssize_t hint, ssize_t *rlen);
void uwsgi_request_body_seek(struct wsgi_request *wsgi_req, off_t pos);
它们自动管理post-buffering,非阻塞和上传进展。
所有的请求插件已更新到新的API。
更快的uwsgi/HTTP/FastCGI/SCGI原生socket¶
所有的–socket协议解析器已经全部被重写,从而更快 (更少的使用系统调用),并且使用更少的内存。现在,它们更复杂了,但是你应该注意到 (在已加载站点上) 每个请求的系统调用数的减少。
添加了对SCGI协议的支持,并且已实现了NPH fastcgi模式 (这里,输出是HTTP,而不是cgi)。
FastCGI协议现在支持真正的sendfile()使用
为HTTP和FastCGI将请求体存储在一个临时文件中的这种老做法已经被移除 (除非你使用post-buffering)。这意味着,除了uwsgi之外,你现在可以使用其他协议进行上传了。
请求日志记录 VS 错误日志记录¶
较老的uWSGI发布版本的最恼人的问题之一是缺乏方便地把请求日志从错误日志中分割出来的能力。现在,你可以创建一个记录器,然后让它只记录请求:
[uwsgi]
req-logger = syslog
...
举个例子,你也许想要发送请求日志到syslog和redis,发送错误日志到mongodb (放到foo.bar集合中):
[uwsgi]
req-logger = syslog
req-logger = redislog:127.0.0.1:6269
logger = mongodblog:127.0.0.1:9090,foo.bar
...
或者只是使用 (无聊到) 文件
[uwsgi]
req-logger = file:/tmp/reqlog
logger = file:/tmp/errlog
...
链式重载¶
当位于lazy/lazy_apps模式时,你可以简单地销毁一个worker,以迫使它重载应用代码。
一个新的名为“链式重载”的重载系统,允许你有时重载一个worker (与批量销毁所有worker这种标准方式相对)
只能通过”touch”来触发链式重载: –touch-chain-reload <file>
卸载改进¶
卸载是在uWSGI 1.4出现的,并且是最受喜爱的特性之一。在1.9种,我们添加了一个新的引擎: “write”,它允许你卸载磁盘上文件的写。有一个通用函数api uwsgi.offload(),它允许应用访问卸载引擎。所有发送静态文件的uWSGI部分 (包括语言特定的实现,像WSGI wsgi.file_wrapper) 都得到了扩展,以在可用的情况下自动使用卸载。这意味着,你可以使用你的框架的方式来提供静态文件服务,而不会损失太多的性能,并且(更重要的是)不会阻塞你的worker。
更好的静态文件管理/服务¶
uWSGI 1.9在提供静态文件服务方面有了许多改进。
你或许想要看看: 使用uWSGI提供静态文件 (更新至1.9)
对于系统管理员来说,最有趣的新特性之一是能够使用 uWSGI新一代的缓存 (见下) 来存储请求 -> 绝对路径映射
新一代的缓存子系统 (cache2)¶
uWSGI缓存子系统已经被完全重写,变成一个更通用的内存键/值存储。旧的缓存子系统已在它之上进行了重建,现在更多是一个通用的“web缓存”系统。新的缓存子系统允许你控制内存存储的所有方面,从哈希算法到块的数量。
现在,对于每个实例,你可以有多个缓存 (由名称标识) 选项
[uwsgi]
cache2 = name=mycache,items=100
cache2 = name=faster,items=200,hash=murmur2,keysize=100,blocksize=4096
cache2 = name=fslike,items=1000,keysize=256,bitmap=1,blocks=2000,blocksize=8192
...
在这个例子中,我们创建了3个缓存:mycache, faster和fslike.
第一个是标准的旧式缓存,能够存储100个项,最大是64k,键的大小限制为2048字节,使用djb33x哈希算法。第二个使用murmur2哈希算法,每个键最大可以是100字节,可以存储200个项,最大是4k。最后一个就像文件系统一样,其中,每个项可以跨多个块。这意味着,fslike缓存可以为不同大小的对象节省大量的内存 (但它会比基于非bitmap的缓存更慢)
你在cache2中可以指定的选项如下:
name
缓存名 (必须唯一) 必要
items/max_items/maxitems
设置缓存剋存储的最大项数。必要
blocksize
设置单个块的大小
blocks
设置块数 (只在bitmap模式使用)
hash
设置哈希算法,目前支持:djbx33盒murmur2
hashsize/hash_size
设置哈希表大小 (默认为65536个项)
keysize/key_size
设置键大小
store
设置永久化存储缓存的文件名
store_sync/storesync
设置频率 (以秒为单位),其中,调用msync()来把缓存刷新至磁盘 (当在永久化模式时)
node/nodes
新的缓存子系统可以通过udp包发送缓存更新。使用这个选项,设置一或多个 (使用 ; 分隔) 发送更新的udp地址
sync
将其设置为缓存服务器的地址。它的全部内容将会被拷贝到新的缓存 (将其用于初始化同步)
udp/udp_servers/udp_server/udpservers/udpserver
绑定到特定的udp地址 (使用 ; 分隔) ,监听缓存更新
bitmap
启用bitmap模式 (设置其为1)
如果你问,为啥会存在这样低层次的调整,那么你必须考虑到,新的缓存子系统被用在大量的地方,因此,对于不同的需求,你或许想要不同的调整。以 扩展SSL连接 (uWSGI 1.9) 为例
旧的–cache-server选项已被移除。0.9.8添加的线程缓存服务器已经完全被新的非阻塞基础设施所代替。如果你加载”缓存(cache)”插件 (单件构建时默认启用),那么将提供一个缓存服务器,它由worker管理。
更新文档可以看看这里 uWSGI缓存框架
Legion子系统¶
Legion子系统是uWSGI项目全新增加的一个系统。它取代了旧的集群子系统 (已在1.9删除)。它实现了一个quorum系统来管理集群环境中的共享资源。文档已经有了: uWSGI Legion子系统
Cygwin (windows) 支持¶
通过cygwin POSIX仿真系统,可以在windows机器上变异uWSGI。事件系统使用简单的poll() (映射到cygwin上的select()),而锁引擎使用windows的mutex。尽管从我们的测试看来,它相当的稳固,但是我们认为该移植仍然是“实验性的”
高级异常子系统¶
与语言独立的请求体管理一样,添加了一个异常管理系统。目前仅Python和 Ruby插件支持,允许对异常情况(例如对指定的异常进行重载)的语言无关处理。已对–catch-exception选项进行改进,以显示大量的有用信息。试试看 (尚在开发中!!!)。未来的开发将允许发送异常到诸如 Sentry或者Airbrake这样的系统。
HTTP路由器保持连接,自动分块,自动gzip和透明的websockets¶
许多用户已经开始在生产上使用HTTP/HTTPS/SPDY路由器了,因此,我们开始对其添加特性。记住,这只是一个路由器/代理,不允许任何I/O,因此你可能不能够丢掉你老的,但是工作良好的web服务器。
新选项:
--http-keepalive
启用HTTP/1.1保持连接(keepalive)连接
--http-auto-chunked
对于没有内容长度的后端响应 (或者已经启用块编码),在块模式下转换输出,以维持keepalive连接
--http-auto-gzip
如果uWSGI-Encoding头被设置为gzip,则自动gzip压缩内容,但是内容大小 (Content-Length/Transfer-Encoding) 和Content-Encoding不会被指定
--http-websockets
自动检测websockets连接,以把请求处理器置于原始(raw)模式
SSL路由器 (sslrouter)¶
添加了一个新的corerouter,它与rawrouter的工作方式相同,但是它会终止ssl连接。该sslrouter可以使用sni来实现虚拟主机 (使用–sslrouter-sni选项)
Websockets api¶
20Tab S.r.l. (一个开发HTML5浏览器游戏的公司) 为uWSGI赞助了一个快速的语言无关的websockets api的开发。该api目前处于一个非常良好的状态(也许比其他实现更快)。仍然需要完成文档,但是你可以看看下面的例子 (一个简单的echo):
https://github.com/unbit/uwsgi/blob/master/tests/websockets_echo.pl (perl)
https://github.com/unbit/uwsgi/blob/master/tests/websockets_echo.py (python)
https://github.com/unbit/uwsgi/blob/master/tests/websockets_echo.ru (ruby)
新的内部路由 (图灵完备?)¶
内部路由子系统已经被重写,现在它是“可编程的”。你可以把它看成使用steroids (和goto ;)的apache mod_rewrite。仍然需要移植文档,但是这个新的系统允许你在线修改/过滤CGI变量和HTTP头,以及管理HTTP鉴权和缓存。
已更新的文档在这里 (仍在进行中) uWSGI内部路由
Emperor ZMQ插件¶
添加了一个新的imperial监控器,允许通过zeromq消息管理vassal:
https://uwsgi-docs.readthedocs.io/en/latest/ImperialMonitors.html#zmq-zeromq
通过统计信息服务器的完全检查¶
现在,统计信息服务器为每个核导出了当前运行中的请求的所有请求变量,因此,它也工作在多线程模式下。这是检查你的实例在做什么,以及如何做的一种棒棒哒的方式。在未来,会扩展uwsgitop,从而实时显示当前运行的请求。
Nagios插件¶
使用nagios插件发送Ping请求将不再算到应用请求统计数据中。这意味着,如果应用使用–idle选项,那么启用的nagios ping将不再阻止应用变成idle状态,因此,从1.9起,当使用nagios插件的时候,应该禁用 –idle。否则,应用可能刚好在nagios ping请求之前置于idle状态,当ping抵达的时候,它需要从idle中醒来,而这可能花费的时间会比ping超时时间更长,从而引发nagios告警。
移除和弃用特性¶
- 已移除–app选项。要在指定的挂载点加载应用,请使用–mount选项
- 已移除–static-offload-to-thread选项。使用更灵活的–offload-threads
- 已移除grunt模式。要实现相同的行为,只需使用线程,或者直接调用fork()和uwsgi.disconnect()
- 已移除send_message/recv_message api (使用语言提供的函数)
进行中的,问题和回归¶
对于大量预期特性,我们错过了时间:
- SPNEGO支持,这是一个内部路由指令,用来实现SPNEGO认证支持
- Ruby 1.9 fibers支持已经被重写了,但是需要测试
- Erlang支持并为获得所需关注,非常有可能会延迟到2.0
- 异步休眠API未完成
- 仍然未实现SPDY推送
- RADIUS和LDAP内部路由指令未实现
- channel子系统 (为方便的websockets通信所需) 仍然未实现
除此之外,我们还有一些将会在接下来的小版本中解决的问题:
- –lazy模式没用了,现在它像–lazy-apps,但是在SIGHUP上只有workers-reload策略
- 看起来JVM与协程引擎不能很好的工作,或许我们应该为它添加一个检查
- Solaris和类Solaris系统并未获得严格测试
特别鸣谢¶
许多用户/开发者在1.9开发周期中提供了帮助。我们想要特别感谢:
Łukasz Mierzwa (fastrouters伸缩性测试)
Guido Berhoerster (让内部路由新天网)
Riccardo Magliocchetti (静态分析)
André Cruz (HTTPS和gevent battle测试)
Mingli Yuan (Clojure/Ring支持和测试套件)
联系¶
.
商业支持¶
你可以从http://unbit.com购买商业支持。
捐赠¶
uWSGI的发展是由意大利ISP Unbit 及其客户赞助的。你可以购买商业支持和许可。如果你不是一个Unbit客户,或者你不能/不想要购买一个商业uWSGI证书,那么可以考虑进行捐赠。当然,请在你的捐赠中随意要求新特性。
我们将信任任何想要赞助新特性的人。
访问该捐赠链接 http://unbit.it/uwsgi_donate 。你也可以通过 GitTip 来捐赠。