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 Python
  • Fiber - 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引擎是:

循环引擎

循环引擎是既导出挂起/恢复技术,又导出事件系统的包/库。加载后,它们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, 等等。)。

静态文件

静态文件服务器 会自动使用已加载的异步引擎。