Запуск нескольких сайтов в одном процессе Python - PullRequest
14 голосов
/ 24 июня 2011

В нашей компании мы создаем новостные порталы для довольно большого количества местных газет (в настоящее время 13, будет 30 до следующего и более в будущем), каждый из которых имеет от 2 до 100 тысяч просмотров в день.Поскольку мы развиваемся из ситуации, когда каждый сайт был сильно настроен на тот, где каждое различие зависит от конфигурации или пользовательского шаблона, наше программное обеспечение уже практически одинаково для всех сайтов.В настоящее время наша стратегия развертывания - один экземпляр gunicorn для каждого сайта (с 1-17 рабочими каждый, в зависимости от трафика сайта), на 16-ядерном сервере и 12 ГБ ОЗУ.Проблема с этой настройкой заключается в том, что каждому рабочему (обычному предварительно разветвленному оружейному оружию) требуется 110 МБ, независимо от того, используется он или нет.Теперь с новыми сайтами нам нужно было бы добавить больше оперативной памяти, чтобы обслуживать не так много запросов, поэтому в основном она не масштабируется.Кроме того, поскольку мы переходим от этой модели, где каждый сайт является независимым, у каждого сайта есть своя собственная база данных, и мне это очень нравится, тем более что мы используем реляционные базы данных (mysql, но переходим на pgsql), поэтому гораздо прощеЭто будет осколок.

Я провожу некоторые исследования и экспериментирую с запуском всех сайтов на одном экземпляре gunicorn, чтобы я мог полностью использовать серверы и добавить больше серверов за балансировщиком нагрузки, когда он пришел.Проблема в том, что django предполагает во многих местах, что на один процесс работает только один сайт, поэтому для того, что я до сих пор думал, мне нужно реализовать:

  • Промежуточное программное обеспечение, которое требуетHTTP_HOST из запроса и помещает идентификатор в локальную переменную thread.
  • Загрузчик шаблонов, который использует эту переменную для соответствующей загрузки пользовательских шаблонов.
  • Патч Monkey django.db.model.Model, возможнодобавление метакласса (даже не уверен, что это возможно, но я думаю, что он мне понадобится из-за пользовательских менеджеров, которые нам иногда нужно использовать), который перезапишет менеджеры для того, который сначала вызовет db_manager (идентификатор) в исходном менеджере, а затем вызоветпредполагаемый метод.Мне также нужно было бы перезаписать методы сохранения и удаления, чтобы всегда включать в себя параметр using = identifier.
  • Полагаю, мне нужно было бы прекратить использование декораторов include_tag, это не большая проблема, но мне нужно подумать о других случаях.вот так.
  • Тяжелое и уродливое исправление urlresolvers, если мне нужны пользовательские или дополнительные URL для каждого сайта.Мне они сейчас не нужны, но, вероятно, когда-нибудь понадобятся.

И это только то, что я придумал, даже не реализовав его и не увидев, где он ломается, я уверен, что я 'Мне нужно еще много изменений, чтобы это работало.Поэтому я действительно не хочу этого делать, особенно с дополнительными усилиями по обслуживанию, которые мне понадобятся, но я не вижу альтернативы и хотел бы узнать, что кто-то уже решил это лучше.Конечно, я мог бы также полностью прекратить использование django (у меня уже есть много причин для этого), но это означало бы серьезную переписку и наличие двух поддерживаемых двух несовместимых ветвей программного обеспечения, пока новая не достигнет паритета возможностей с версией django, так чтомне это кажется даже хуже, чем все уродливые хаки.

1 Ответ

6 голосов
/ 13 июля 2011

Недавно я разработал систему электронной коммерции с аналогичными требованиями - многие экземпляры, запущенные из одного проекта, делятся практически всем.Предыдущая версия системы представляла собой набор независимых установок (~ 30), поэтому она была практически не поддерживаемой.Я уверен, что требования по-прежнему отличаются от ваших (например, в моем случае все модели использовали одни и те же модели), но все же было бы полезно поделиться своим опытом.

Вы правы в том, что Джанго непомогите с подобными сценариями из коробки, но на самом деле удивительно легко обойти это.Вот краткое описание того, что я сделал.

Я видел синергию между тем, чего я хотел достичь, и django.contrib.sites.Кроме того, потому что многие сторонние приложения Django знают, как работать с ним и использовать его, например, для создания абсолютных URL-адресов для текущего сайта.Основная проблема с sites заключается в том, что он хочет, чтобы вы указали текущий идентификатор сайта в settings.SITE_ID, что является очень наивным подходом к проблеме с несколькими хостами.Естественно, и то, что вы также упоминаете, - это определить текущий сайт из заголовка запроса Host.Чтобы решить эту проблему, я заимствовал идею ловушки из django-multisite: https://github.com/shestera/django-multisite/blob/master/multisite/threadlocals.py#L19

Затем я создал приложение, включающее в себя все функциональные возможности, связанные с аспектом нескольких хостов моего проекта.В моем случае приложение называлось stores, и среди прочего оно включало два важных класса: stores.middleware.StoreMiddleware и stores.models.Store.

Класс модели является подклассом django.contrib.sites.models.Site.Хорошая вещь о подклассе Site заключается в том, что вы можете передать Store любой функции, где ожидается Site.Таким образом, вы по-прежнему просто используете старую, хорошо документированную и протестированную среду sites.В класс Store я добавил все поля, необходимые для настройки всех различных хранилищ.Таким образом, у него есть такие поля, как urlconf, theme, robots_txt и еще много чего.

Функция класса промежуточного программного обеспечения заключалась в сопоставлении заголовка Host с соответствующим экземпляром Store в базе данных.Как только соответствующий Store был получен, он исправил бы SITE_ID способом, аналогичным https://github.com/shestera/django-multisite/blob/master/multisite/middleware.py.. Кроме того, он посмотрел на store urlconf и, если бы не было None, он быустановите request.urlconf, чтобы применить его специальные требования к URL.После этого текущий экземпляр Store был сохранен в request.store.Это оказалось невероятно полезным, потому что я мог делать такие вещи в своих представлениях:

def homepage(request):
    featured = Product.objects.filter(featured=True, store=request.store)
    ...

request.store стал для меня естественным дополнительным измерением объекта request на протяжении всего проекта.

Еще одна вещь, определенная в классе Store, была функция get_absolute_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

Так что я мог легко генерировать URL-адреса для объектов, отличных оттекущий магазин, например:

# Redirect to `product` on `store`.
redirect(store.get_absolute_url(product)) 

Это было в основном все, что мне было нужно для создания системы, позволяющей пользователям создавать новый интернет-магазин, живущий на его собственном домене, через администратора Django.

...