缓存后端在开发服务器上有效但在mod_wsgi上无效

0 投票
2 回答
1326 浏览
提问于 2025-04-15 19:23

我正在使用一个自定义的缓存后端,目的是为了在内置的缓存后端上加上一层,这样我就可以把当前的站点ID加到所有的缓存键里(这对于使用单个memcached实例的多站点功能很有用)。

不过不幸的是,这在Django自带的开发服务器上运行得很好,但在我尝试在使用mod_wsgi的实际服务器上运行时,却出现了很糟糕的错误。

这是我错误日志中的追踪信息:

[Wed Feb 17 00:39:34 2010] [error] [client 127.0.0.1] mod_wsgi (pid=22933): Exception occurred processing WSGI script '/home/jiaaro/webapps/op_wsgi/myProject/deploy/myProject.wsgi'.
[Wed Feb 17 00:39:34 2010] [error] [client 127.0.0.1] Traceback (most recent call last):
[Wed Feb 17 00:39:34 2010] [error] [client 127.0.0.1]   File "/home/jiaaro/webapps/op_wsgi/lib/python2.5/django/core/handlers/wsgi.py", line 230, in __call__
[Wed Feb 17 00:39:34 2010] [error] [client 127.0.0.1]     self.load_middleware()
[Wed Feb 17 00:39:34 2010] [error] [client 127.0.0.1]   File "/home/jiaaro/webapps/op_wsgi/lib/python2.5/django/core/handlers/base.py", line 40, in load_middleware
[Wed Feb 17 00:39:34 2010] [error] [client 127.0.0.1]     mod = import_module(mw_module)
[Wed Feb 17 00:39:34 2010] [error] [client 127.0.0.1]   File "/home/jiaaro/webapps/op_wsgi/lib/python2.5/django/utils/importlib.py", line 35, in import_module
[Wed Feb 17 00:39:34 2010] [error] [client 127.0.0.1]     __import__(name)
[Wed Feb 17 00:39:34 2010] [error] [client 127.0.0.1]   File "/home/jiaaro/webapps/op_wsgi/myProject/apps/site_settings/__init__.py", line 6, in <module>
[Wed Feb 17 00:39:34 2010] [error] [client 127.0.0.1]     import cache_wrapper
[Wed Feb 17 00:39:34 2010] [error] [client 127.0.0.1]   File "/home/jiaaro/webapps/op_wsgi/myProject/apps/site_settings/cache_wrapper.py", line 4, in <module>
[Wed Feb 17 00:39:34 2010] [error] [client 127.0.0.1]     from django.core.cache.backends.base import BaseCache
[Wed Feb 17 00:39:34 2010] [error] [client 127.0.0.1]   File "/home/jiaaro/webapps/op_wsgi/lib/python2.5/django/core/cache/__init__.py", line 73, in <module>
[Wed Feb 17 00:39:34 2010] [error] [client 127.0.0.1]     cache = get_cache(settings.CACHE_BACKEND)
[Wed Feb 17 00:39:34 2010] [error] [client 127.0.0.1]   File "/home/jiaaro/webapps/op_wsgi/lib/python2.5/django/core/cache/__init__.py", line 68, in get_cache
[Wed Feb 17 00:39:34 2010] [error] [client 127.0.0.1]     return getattr(module, 'CacheClass')(host, params)
[Wed Feb 17 00:39:34 2010] [error] [client 127.0.0.1] AttributeError: 'module' object has no attribute 'CacheClass'

如果我在同一台机器上(也就是实际服务器)运行开发服务器,它就能正常工作……我可以毫无问题地做到这一点:

$ cd /home/jiaaro/webapps/op_wsgi/myProject
$ python2.5 manage.py runserver

而且在一个单独的SSH会话中……

$ wget 127.0.0.1:8000

页面能够正确显示,使用的是memcached和实际的数据库。请问mod_wsgi在导入模块的方式上有什么不同吗?我应该知道吗?

也许是开发服务器的单进程、单线程特性导致的?

我已经为这个问题挣扎了好几天,任何帮助都将不胜感激。

额外信息:

  • Webfaction共享主机(centos)
  • Apache 2
  • Python 2.5
  • Django 1.1.1
  • mod_wsgi 2.5
  • MySql 5.0

更多细节:

  • 我已经设置了缓存后端(并且它在追踪信息中显示正确的模块被导入,说明它是有效的)
  • 模块中有一个叫“CacheClass”的类:

site_settings/cache_wrapper.py :

from django.conf import settings
CACHE_BACKEND = getattr(settings, 'CUSTOM_CACHE_BACKEND')

from django.core.cache.backends.base import BaseCache

class CacheClass(BaseCache):
    from decorators import accept_site

    def __init__(self, *args):
        from django.core.cache import get_cache
        self.WRAPPED_CACHE = get_cache(CACHE_BACKEND)

    @accept_site
    def add(self, site, key, *args):
        return self.WRAPPED_CACHE.add(self._key(site, key),*args)

    @accept_site
    def get(self, site, key, *args):
        return self.WRAPPED_CACHE.get(self._key(site, key),*args)

    ... (all the rest of the wrapped methods)

    def _key(self, site, key):
        from exceptions import NoCurrentSite
        if not site:
            raise NoCurrentSite
        return "%s|%s" % (site.id, key)

accept_site装饰器与一些中间件一起工作,以确定当前的站点。它们如下:

decorators.py:

def accept_site(fn):
    def decorator(self, *args, **kwargs):
        site = kwargs.get('site', None)
        try: 
            del kwargs['site']
        except KeyError: 
            pass

        from .middleware import get_current_site
        site = site or get_current_site()

        if not site:
            raise NoCurrentSite("The current site is not available via thread locals, please specify a site with the 'site' keyword argument")

        return fn(self, site, *args, **kwargs)

    return decorator

以及middleware.py

try:
    from threading import local
except ImportError:
    from django.utils._threading_local import local

from django.conf import settings
from django.contrib.sites.models import Site

DEFAULT_SITE_ID = 1

_thread_locals = local()
def get_current_site():
    return getattr(_thread_locals, 'site', None)

def set_current_site(site):
    setattr(_thread_locals, 'site', site)

class SiteSettings(object):
    """Middleware that gets various objects from the
    request object and saves them in thread local storage."""
    def process_request(self, request):
        if settings.DEBUG:
            site_id = request.GET.get('site_id', DEFAULT_SITE_ID)
        else:
            site_id = DEFAULT_SITE_ID

        current_site_domain = request.META["HTTP_HOST"]
        try:
            current_site = Site.objects.get(domain__iexact=current_site_domain())
        except:
            current_site = Site.objects.get(id=site_id)
        set_current_site(current_site)

所有这些在开发服务器上都能正常工作,使用的设置与wsgi服务器相同,并且配置了相同的Python路径(就我所看到的情况而言)。

此时我希望我在某个地方创建了一个导入循环(尽管这不太合理,因为它只在开发服务器上发生)。

编辑:我在Django文档中找到了一份关于开发服务器和Apache之间差异的列表,上面提到:

开发服务器会将已安装的应用添加到 sys.path,而Apache则不会。

也许这和问题有关?

2 个回答

1

你有没有在你的settings.py文件里设置这个选项?当DEBUG=True时,这个问题就不大,因为我想系统会自动安装一个假后端。但在生产环境中,你需要设置这个值,即使你自己写了一个后端。

关于缓存后端的文档

如果你已经设置了这个选项,但还是有问题,可以试试切换到假缓存模块或者本地内存模块(虽然这在生产环境中并不是一个好的选择),看看这样是否能解决问题。如果可以,那可能是你的WSGI的python路径中缺少了某个包,比如python-memcached

0

问题的根源在于 sys.path。我需要确保 mod_wsgi 的设置和开发服务器的 sys.path 是一样的,并且没有多余的包留在不该出现的地方(这是因为重构时没有通过版本控制正确应用到服务器上)。

撰写回答