Python,WSGI,多进程和共享数据

26

我对mod_wsgi的多进程特性有点困惑,并且对能够在具备多进程能力的WSGI服务器上执行的WSGI应用程序的一般设计也感到困惑。

考虑以下指令:

WSGIDaemonProcess example processes=5 threads=1
如果我理解正确,mod_wsgi将产生5个Python(例如CPython)进程,并且任何一个进程都可以接收用户的请求。
文档说:
“当共享数据需要对所有应用程序实例可见时,无论它们在哪个子进程中执行,以及由一个应用程序所做的更改立即对另一个应用程序可用时(包括在另一个子进程中执行的任何应用程序),必须使用外部数据存储,例如数据库或共享内存。普通Python模块中的全局变量不能用于此目的。”
但在这种情况下,如果想确保应用程序在任何WSGI条件下运行(包括多进程条件下),那么工作量就会非常大。
例如,一个包含当前连接用户数量的简单变量 - 应该从memcached安全地读取/写入,还是从数据库或(如果有这样的标准库机制)共享内存?
而像这样的代码:
counter = 0

@app.route('/login')
def login():
    ...
    counter += 1
    ...

@app.route('/logout')
def logout():
    ...
    counter -= 1
    ...

@app.route('/show_users_count')
def show_users_count():
    return counter

在多进程环境下表现不可预测?

谢谢!


5
引用:“一个简单的变量,其中包含当前连接用户的数量”。这是HTTP,没有“已连接”的用户概念,因此这样的计数不能“简单”。 (例如,用户可以通过忘记您给他们的任何令牌(例如通过清除其浏览器cookie)来注销)。 - André Caron
2
意思是应用程序认为其为“已连接”的用户,例如按最后HTTP会话时间戳+10分钟计算。 - Zaur Nasibov
对于安德烈的评论我表示赞同,但是虽然我认为会话计数存在固有的困难,但这更多地与良好的网页设计有关,而不是与特定的多进程/共享数据问题有关。另一个问题是没有代码来确保计数器以有序的方式被读取、更新和写回(据我所知,在Python中,+= 1不是原子操作...)。需要某种形式的锁定。 - marr75
2
大家确实需要锁定,这只是一个简单的例子。考虑使用线程安全操作包装a的描述符。问题是关于多进程,而不是多线程。 - Zaur Nasibov
1
@marr75:请注意,这条评论是开玩笑的,我不认为原帖作者真的期望在现实世界中使用它 :-) - André Caron
3个回答

26

你的问题有几个方面需要考虑。

首先,需要考虑apache MPM和mod_wsgi应用程序之间的交互。如果您在嵌入模式下运行mod_wsgi应用程序(不需要WSGIDaemonProcessWSGIProcessGroup %{GLOBAL}),则会从apache MPM继承多进程/多线程。这应该是最快的选项,您最终会拥有多个进程和每个进程中的多个线程,具体取决于您的MPM配置。相反,如果您在守护程序模式下运行mod_wsgi,使用WSGIDaemonProcess <name> [options]WSGIProcessGroup <name>,则可以以小overhead的代价对多进程/多线程进行精细控制。

在单个apache2服务器中,您可以定义零个、一个或多个命名的WSGIDaemonProcess,并且每个应用程序可以在其中一个进程中运行(WSGIProcessGroup <name>)或以嵌入模式运行,使用WSGIProcessGroup %{GLOBAL}

您可以通过检查 wsgi.multithreadwsgi.multiprocess 变量来检查多进程/多线程。
根据您的配置 WSGIDaemonProcess example processes=5 threads=1,您有 5 个独立的进程,每个进程都有一个执行线程:没有全局数据、没有共享内存,因为您无法控制生成子进程,但是 mod_wsgi 会为您完成。要共享全局状态,您已经列出了一些可能的选项:与您的进程进行接口的数据库、某种基于文件系统的持久性、守护进程(在 apache 外部启动)和基于套接字的 IPC。
正如 Roland Smith 指出的那样,后者可以使用 multiprocessing.managers 的高级 API 实现:在 apache 外部创建并启动一个 BaseManager 服务器进程。
m = multiprocessing.managers.BaseManager(address=('', 12345), authkey='secret')
m.get_server().serve_forever()

在您的应用程序中,您可以连接

m = multiprocessing.managers.BaseManager(address=('', 12345), authkey='secret')
m.connect()

上面的示例是虚拟的,因为 m 没有注册任何有用的方法,但是 这里(Python 文档)您将找到如何在进程之间创建和代理对象(例如您示例中的 counter)的方法。
关于您的示例,processes=5 threads=1。我知道这只是一个示例,但在实际应用中,我怀疑性能与 processes=1 threads=5 相当:只有当预期的性能提升超过“单进程多线程”模型时,您才应该深入研究在多进程共享数据的复杂性。

不用谢!我编辑了我的答案,以明确讨论适用于由mod_wsgi运行的应用程序,而不是整个mod_wsgi,因为我的措辞可能会让人产生误解。 - Stefano M

4

从WSGI进程和线程文档中可以得知:

当Apache以多个子进程的模式运行时,每个子进程将包含每个WSGI应用程序的子解释器。

这意味着在您的配置中,如果有5个进程,每个进程只有1个线程,则会有5个解释器且没有共享数据。您的计数器对象将对每个解释器都是唯一的。您需要构建一些自定义解决方案来计算会话(例如一个可以通信的常见进程,某种基于持久性的解决方案等),或者使用预构建的解决方案(Google Analytics和Chartbeat是很好的选择)。

我倾向于认为使用全局变量来共享数据是一种全局滥用的形式。在我进行并行处理的大多数环境中,这是一个错误和可移植性问题。如果突然您的应用程序要在多个虚拟机上运行,那么无论线程和进程的共享模型如何,这都会破坏您的代码。


这是一个关于通过Chartbeat API查看您的网站流量的页面(如果您需要以编程方式使用此数据):http://chartbeat.com/docs/api/explore/#endpoint=live/summary/v3/ - marr75

2
如果你正在使用 multiprocessing,有多种方法可以在进程之间共享数据。只有当进程存在父/子关系(通过继承共享)时,ValuesArrays 才能工作。如果不是这种情况,请使用 ManagerProxy 对象。多种方式ValuesArraysManagerProxy对象。

如果数据不是pickle可序列化的怎么办?例如:https://dev59.com/1mkMtIcB2Jgan1znbNLX - hafiz031

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接