【问题标题】:Why are my mod_python global variables resetting?为什么我的 mod_python 全局变量会重置?
【发布时间】:2014-09-19 23:17:27
【问题描述】:

我有一个使用 apache2 和 mod_python 的服务器,它似乎在一段时间后重置了全局变量。在这种情况发生之前,服务器和全局变量的所有功能都会完全按照我的预期进行处理。

为了排除某些运行时错误的可能性(尽管我确实进行了调试并且没有看到任何错误),我制作了这个简化的脚本来演示如何重置全局变量。它只是保存一个全局计数器,并在每次访问服务器时递增:

from mod_python import apache

counter = 0

def handler(req):
    global counter
    counter += 1

    req.content_type = 'text/plain'
    req.write('counter: '+str(counter))

    return apache.OK

我可以继续刷新页面并观察计数器按预期上升。但是,在某些时候,计数器会跳回 1。

我尝试简单地按住 F5 键以快速刷新页面,看看在我不断访问它时它是否仍会重置,我注意到了别的东西。起初它仍然会下降到 1 几次,但最终它会下降到其他数字。例如,我最多刷新了大约 200 次,但它刚刚下降到大约 100 次,有时甚至会向上跳回 200 次。

似乎有多个用于启动服务器脚本的虚拟 shell,而服务器只是在它们之间随机切换或启动新的。我怎样才能防止这种情况发生?

【问题讨论】:

    标签: python apache2 mod-python


    【解决方案1】:

    似乎有多个虚拟shell用于正在启动的服务器脚本,而服务器只是在它们之间随机切换或启动新的。

    没错。

    Apache 为最大限度地提高并发性做了各种不同的事情,其中​​之一就是产生一堆子进程。

    使用mod_python,每个子进程都有自己独立的 Python 解释器,这意味着它们每个都有自己的任何全局变量的副本。

    如何防止这种情况发生?

    好吧,您可以在 Apache 中禁用分叉。但这是个坏主意。除非你真的知道自己在做什么,否则这会扼杀你的可扩展性。如果你重新启动服务器,它仍然无济于事(Apache 有时会自行清除内存泄漏,除非你将其配置为不这样做)。

    Web 服务应该是无状态的——或者更确切地说,如果它们有任何状态,它们应该将其打包并在请求之间持久化。对于每个用户的状态,您可以将其存储在 cookie 或隐藏的表单字段中,并让他们的浏览器将其传回。但是对于系统范围的状态,这是行不通的;您需要将其保存到外部。

    通常的解决方案是数据库。但是对于这么简单的事情,你可以使用任何东西——甚至是纯文本文件和羊群。这很冗长,但很容易理解,无需学习 SQL 或其他数据库接口,它让您考虑并发问题(理解它们很关键),所以我将展示:

    import contextlib
    import fcntl
    from mod_python import apache
    
    @contextlib.contextmanager
    def flocking(f, flag=fcntl.LOCK_EX):
        fcntl.flock(f, flag)
        try:
            yield f
        finally:
            fcntl.flock(f, fcntl.LOCK_UN)
    
    def bump_counter():
        while True:
            try:
                with flocking(open('storage.lock', 'r+')) as f:
                    val = int(f.read())
                    f.seek(0)
                    f.write(str(val))
                    return val
            except OSError:
                pass
            try:
                with flocking(open('storage.lock', 'x')) as f:
                    val = 0
                    f.write(str(val))
                    return val
                except OSError:
                    pass
    
    def handler(req):
        req.content_type = 'text/plain'
        req.write('counter: '+str(bump_counter()))
    
        return apache.OK
    

    大部分代码都是关于错误处理的,主要是处理这是第一次请求,所以文件还不存在的情况。在这种情况下,我们尝试以x 模式打开它,只有在文件不存在时才会成功,以防在您启动时两个请求正确出现并且他们都认为他们是第一个。这样,其中一个会失败,然后会返回并再次尝试整个循环。

    在现实生活中,您会想要更好的错误处理,因为“找不到文件”不是失败的唯一原因,而且您不希望服务器在发生这种情况时永远阻塞。但在现实生活中,您可能会使用一个数据库来为您处理这些问题。

    【讨论】:

    • 感谢您的回复。我希望将数据存储在 RAM 中,但这将使服务器比读取和写入文件更具响应性。有什么办法吗?
    • @spacelike:首先,在优化之前进行测试。该文件很可能 99.99% 的时间都留在内核的磁盘缓存中。另外,请记住,如果您不使用磁盘,则每次重新启动服务器时都会将其重置为 0。如果您确实需要将其放在 RAM 中,则必须共享​​>内存。您可以使用mmap(使用非持久文件,这会强制它保留在缓存中)或shm。或者,您可以编写一个辅助进程来跟踪其他所有人的状态,并使用来自mod_python 进程的 IPC。或者你可以使用类似memcached 的东西来为你打包。
    • @spacelike:同样,标准解决方案是数据库。诸如 postgresql 或 MySQL 之类的数据库,或各种非 SQL 替代品,非常擅长缓存数据,它们为您处理锁定、持久性和其他所有事情。只存储一个变量似乎有点过头了,但要做到这一点并不需要太多工作,而且它为您提供了以后扩展的灵活性(和技能集)。
    • 感谢您提供的非常有帮助的建议。实际的服务器更像是一个论坛,因此它会存储比单个变量更多的数据,我发布的简单计数器变量只是一个示例所以使用数据库听起来是一个完美的解决方案。谢谢
    【解决方案2】:

    mod_python 在 web 服务器的上下文中运行。您必须只创建一个服务器进程来处理所有传入请求,并确保它永远不会死亡。因此您必须将MaxClients 指令的值设置为1,将MaxRequestsPerChild 指令的值设置为0。

    MaxClients 1
    MaxRequestsPerChild 0
    

    当然,前一段完全是开玩笑的,因为这样做会降低服务器性能。正确的解决方案是不要将全局变量与 mod_python 一起用于任何您无法承受的消失在您身下的数据。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-22
      • 1970-01-01
      • 2022-01-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多