uWSGI Spooler
更新至uWSGI 2.0.1
支持语言:Perl, Python, Ruby
Spooler是内置于uWSGI的队列管理器,它的工作方式像打印/邮件系统。
你可以排队大量的邮件发送、图像处理、视频编码等等,并且让spooler在后台为你努力工作,同时用户的请求会被正常的worker处理。
spooler通过定义”spooler文件”将会写入的目录来工作,每次spooler在它的目录下找到一个文件,它就会解析它,然后运行一个特定的函数。
你可以让多个spooler映射到不同的目录,甚至可以让多个spooler映射到相同的目录。
—spooler <directory>
选项允许你生成一个spooler进程,而</code>–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信号处理器的目标使用的时候,你可以使用绝对目录名来指定路由信号到哪个。