在同一个python进程上运行多个站点

时间:2011-06-24 18:51:09

标签: django gunicorn

在我们公司,我们为大量当地报纸制作新闻门户(目前有13个,下个月将有30个,将来会有更多),每个页面每天的页面浏览量为2k到10万。由于我们正在从每个站点大量定制的情况发展到每个站点的配置或自定义模板的问题,我们的软件对于所有站点已经几乎相同。现在我们的部署策略是在16核服务器和12GB RAM上为每个站点(每个站点有1-17个工作站,具体取决于站点流量)的一个gunicorn实例。这种设置的问题是每个工作人员(常规预分叉炮弹)需要110MB,无论是否使用。现在有了新的站点,我们需要添加更多的RAM来提供更多的请求,所以基本上它不会扩展。此外,由于我们正在从每个站点独立的这个模型转移,每个站点都有自己的数据库,我非常喜欢它,特别是因为我们使用关系数据库(mysql,但迁移到pgsql),所以它更容易这样碎片。

我正在做一些研究并尝试在一个gunicorn实例上运行所有站点,因此我可以完全使用服务器并在负载均衡器后面添加更多服务器。问题是django假设在很多地方每个进程只运行一个站点,所以对于我到目前为止我想到的,我必须实现:

  • 从请求中获取HTTP_HOST并在threadlocal变量上放置标识符的中间件。
  • 使用该变量相应加载自定义模板的模板加载器。
  • 猴子补丁django.db.model.Model,可能添加了一个元类(甚至不确定是否可能,但我认为我需要它,因为我们有时需要使用的自定义管理器)会覆盖管理器首先会在原始管理器上调用db_manager(identifier),然后调用预期的方法。我还需要覆盖save和delete方法,以便始终包含using = identifier参数。
  • 我想我需要停止使用inclus_tag装饰器,这不是一个大问题,但我需要考虑其他类似的情况。
  • 如果我需要为每个网站提供自定义或额外网址,那么urlresolvers的重量级和丑陋的修补程序。我现在不需要它们,但可能会在某些时候。

这就是我想出来的,甚至没有实现它并看到它破坏的地方,我确信我需要更多的更改才能工作。所以我真的不想这样做,特别是我需要额外的维护工作,但我没有看到任何替代方案,并且很想知道有人已经以更好的方式解决了这个问题。当然我也可以完全停止使用django(我已经有很多理由这样做了)但是这意味着重大改写并且有两个维护软件的两个不兼容的分支,直到新的一个与django版本达到功能奇偶校验,所以到我似乎比所有丑陋的黑客更糟糕。

1 个答案:

答案 0 :(得分:6)

我最近开发了一个具有类似要求的电子商务系统 - 许多从同一个项目运行的实例几乎可以共享所有内容。该系统的先前版本是一堆独立安装(~30),因此它非常难以维护。我确定要求仍然与您的要求不同(例如,在我的情况下所有实例共享相同的模型),但分享我的经验可能仍然有用。

你是对的,Django没有开箱即用这样的场景,但实际上它很容易解决。以下是我所做的简要描述。

我可以看到我想要实现的目标与django.contrib.sites之间的协同作用。此外,因为许多第三方Django应用程序知道如何使用它并使用它,例如,生成当前站点的绝对URL。 sites的主要问题是它希望您在settings.SITE_ID中指定当前站点ID,这是一种非常天真的多主机问题方法。人们自然想要的,以及您还提到的是从Host请求标头确定当前站点。为了解决这个问题,我从django-multisitehttps://github.com/shestera/django-multisite/blob/master/multisite/threadlocals.py#L19

借用了这个问题

接下来,我创建了一个应用程序,它封装了与项目的多主机方面相关的所有功能。就我而言,该应用程序被称为stores,其中包括两个重要的类:stores.middleware.StoreMiddlewarestores.models.Store

模型类是django.contrib.sites.models.Site的子类。子类化Site的好处是,您可以将Store传递给期望Site的任何函数。因此,您实际上仍然只是使用旧的,记录良好且经过测试的sites框架。对于Store类,我添加了配置所有不同商店所需的所有字段。所以它包含urlconfthemerobots_txt以及诸如此类的字段。

中间件类的功能是将Host标头与数据库中相应的Store实例相匹配。检索到匹配的Store后,它会以类似https://github.com/shestera/django-multisite/blob/master/multisite/middleware.py的方式修补SITE_ID。此外,它查看了store的{​​{1}},如果它不是None,则会设置urlconf以应用其特殊的URL要求。之后,当前request.urlconf实例存储在Store中。事实证明这非常有用,因为我能够在我的观点中做到这样的事情:

request.store

def homepage(request): featured = Product.objects.filter(featured=True, store=request.store) ... 在整个项目中成为request.store对象的一个​​自然的额外维度。

request类上定义的另一件事是函数Store,其实现看起来大致如下:

get_absolute_url

因此,我可以轻松地为当前商店以外的对象生成URL,例如:

def get_absolute_url(self, to='/'):
    """
    Return an absolute url to this `Store` or to `to` on this store.

    The URL includes http:// and the domain name of the store.

    `to` can be an object with `get_absolute_url()` or an absolute path as string.

    """
    if isinstance(to, basestring):
        path = to
    elif hasattr(to, 'get_absolute_url'):
        path = to.get_absolute_url()
    else:
        raise ValueError(
            'Invalid argument (need a string or an object with get_absolute_url): %s' % to
        )

    url = 'http://%s%s%s' % (
        self.domain,
        # This setting allowed for a sane development environment
        # where I just set it to ".dev:8000" and configured `dnsmasq`.
        # The same value was also removed from the `Host` value in the middleware
        # before looking up the `Store` in database. 
        settings.DOMAIN_SUFFIX,
        path
    )

    return url

基本上我只需要能够实现一个系统,允许用户通过Django管理员在自己的域中创建一个新的电子商店。