Обеспечение ограничения одновременных потоков на IP в приложении WSGI / apache - PullRequest
0 голосов
/ 12 сентября 2018

Мы запускаем приложение Flask, которое предоставляет данные, хранящиеся в базе данных.Возвращает много 503 ошибок.Насколько я понимаю, они генерируются Apache при достижении максимального количества одновременных потоков.

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

  • Поставщики данных отправляют данные с высокой скоростью.Я считаю, что их программа получает много 503, и просто попробуйте / поймайте их, чтобы повторить попытку до успеха.

  • Потребители данных используют приложение с гораздо меньшей скоростью, и я бы хотел, чтобы ониэти проблемы меня не слишком беспокоят.

Я думаю об ограничении числа одновременных обращений с IP каждого провайдера.Они могут получить более низкую пропускную способность, но они будут жить с этим, как они уже делают, и это облегчит жизнь обычным потребителям.


Я определил mod_limitipconn , который, кажется,для этого.

mod_limitipconn [...] позволяет администраторам ограничивать количество одновременных запросов, разрешенных с одного IP-адреса.

Я бы хотелубедитесь, что я понимаю, как это работает и как установлены ограничения.

Я всегда полагал, что из-за настроек WSGI было максимально 5 одновременных соединений: threads=5.Но я прочитал Процессы и потоки в документации mod_wsgi, и я запутался.

Учитывая приведенную ниже конфигурацию, правильны ли эти предположения?

  • Толькоодновременно запускается один экземпляр приложения.

  • Может быть создано не более 5 одновременных потоков.

  • Когда выполняется 5 запросовобрабатывается, если поступает шестой запрос, клиент получает 503.

  • Ограничение количества одновременных запросов для IP xxxx на уровне apache до 3 обеспечит, что только 3 из этих 5потоки могут использоваться этим IP, оставляя 2 другим IP.

  • Увеличение количества потоков в конфигурации WSGI может помочь разделить пул соединений между клиентами, обеспечивая большую степень детализации в скоростиограничения (вы можете ограничить до 3 для каждого из 4 провайдеров и оставить еще 5 с общим количеством 17), но это не улучшит общую производительность, даже если сервер имеет незанятые ядра, потому что предотвращает использование Python GILНесколько одновременно работающих потоков .

  • Увеличение числа потоков до большого числа, например 100, может сделать запросы длиннее, но ограничит ответы 503.Этого может даже быть достаточно, если клиенты устанавливают не слишком высокий лимит для своих одновременных запросов, и если они этого не делают, я могу применить это с помощью чего-то вроде mod_limitipconn.

  • Увеличение числаслишком много потоков сделает запросы настолько длинными, что клиенты получат тайм-ауты вместо 503, что не очень хорошо.


Текущая конфигурация ниже.Не уверен, что имеет значение.

apachectl -V:

Server version: Apache/2.4.25 (Debian)
Server built:   2018-06-02T08:01:13
Server's Module Magic Number: 20120211:68
Server loaded:  APR 1.5.2, APR-UTIL 1.5.4
Compiled using: APR 1.5.2, APR-UTIL 1.5.4
Architecture:   64-bit
Server MPM:     event
  threaded:     yes (fixed thread count)
    forked:     yes (variable process count)

/etc/apache2/apache2.conf:

# KeepAlive: Whether or not to allow persistent connections (more than
# one request per connection). Set to "Off" to deactivate.
#
KeepAlive On

#
# MaxKeepAliveRequests: The maximum number of requests to allow
# during a persistent connection. Set to 0 to allow an unlimited amount.
# We recommend you leave this number high, for maximum performance.
#
MaxKeepAliveRequests 100

/etc/apache2/mods-available/mpm_worker.conf (но это не должно иметь значения в event еще, верно?):

<IfModule mpm_worker_module>
        StartServers                     2
        MinSpareThreads          25
        MaxSpareThreads          75
        ThreadLimit                      64
        ThreadsPerChild          25
        MaxRequestWorkers         150
        MaxConnectionsPerChild   0
</IfModule>

/etc/apache2/sites-available/my_app.conf:

WSGIDaemonProcess my_app threads=5

Ответы [ 2 ]

0 голосов
/ 19 октября 2018

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

"""Concurrency requests limiter

Inspired by Flask-Limiter
"""

from collections import defaultdict
from threading import BoundedSemaphore
from functools import wraps

from flask import request
from werkzeug.exceptions import TooManyRequests


# From flask-limiter
def get_remote_address():
    """Get IP address for the current request (or 127.0.0.1 if none found)

    This won't work behind a proxy. See flask-limiter docs.
    """
    return request.remote_addr or '127.0.0.1'


class NonBlockingBoundedSemaphore(BoundedSemaphore):
    def __enter__(self):
        ret = self.acquire(blocking=False)
        if ret is False:
            raise TooManyRequests(
                'Only {} concurrent request(s) allowed'
                .format(self._initial_value))
        return ret


class ConcurrencyLimiter:

    def __init__(self, app=None, key_func=get_remote_address):
        self.app = app
        self.key_func = key_func
        if app is not None:
            self.init_app(app)

    def init_app(self, app):
        self.app = app
        app.extensions = getattr(app, 'extensions', {})
        app.extensions['concurrency_limiter'] = {
            'semaphores': defaultdict(dict),
        }

    def limit(self, max_concurrent_requests=1):
        def decorator(func):
            @wraps(func)
            def wrapper(*args, **kwargs):
                # Limiter not initialized
                if self.app is None:
                    return func(*args, **kwargs)
                identity = self.key_func()
                sema = self.app.extensions['concurrency_limiter'][
                    'semaphores'][func].setdefault(
                        identity,
                        NonBlockingBoundedSemaphore(max_concurrent_requests)
                    )
                with sema:
                    return func(*args, **kwargs)
            return wrapper
        return decorator


limiter = ConcurrencyLimiter()


def init_app(app):
    """Initialize limiter"""

    limiter.init_app(app)
    if app.config['AUTHENTICATION_ENABLED']:
        from h2g_platform_core.api.extensions.auth import get_identity
        limiter.key_func = get_identity

Тогда все, что мне нужно сделать, это применить этот декоратор к моим представлениям:

@limiter.limit(1)  # One concurrent request by user
def get(...):
    ...

На практике я защищал только те, которые генерируют высокий трафик.

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

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

Обратите внимание, что это устанавливает разные ограничения для каждой функции просмотра. Если лимит должен быть глобальным, он может быть реализован по-другому. На самом деле это было бы еще проще.

0 голосов
/ 13 сентября 2018

Я бы хотел, чтобы их не беспокоили , чтобы отделить запросы поставщиков данных от потребителей данных (я не знаком с apache, поэтому я не показываю вам готовую к работе конфигурацию, но в целомподход):

<VirtualHost *>
    ServerName example.com

    WSGIDaemonProcess consumers user=user1 group=group1 threads=5
    WSGIDaemonProcess providers user=user1 group=group1 threads=5
    WSGIScriptAliasMatch ^/consumers_ulrs/.* /path_to_your_app/consumers.wsgi process-group=consumers
    WSGIScriptAliasMatch ^/providers_ulrs/.* /path_to_your_app/providers.wsgi process-group=providers

    ...

</VirtualHost>

Ограничивая количество запросов на каждый IP-адрес, вы можете навредить пользовательскому опыту и все же не решить вашу проблему.Например, обратите внимание, что многие независимые пользователи могут иметь одинаковые IP-адреса из-за того, как работают NAT и ISP.

PS Довольно странно, что ThreadsPerChild=25, но WSGIDaemonProcess my_app threads=5.Вы уверены, что с этой конфигурацией все созданные потоки Apache будут использоваться сервером WSGI?

...