Многопоточное приложение FastCGI

Я хочу написать приложение FastCGI, которое должно обрабатывать несколько одновременных запросов с использованием потоков. Я посмотрел на образец threadaded.c, который поставляется с SDK:

#define THREAD_COUNT 20
static int counts[THREAD_COUNT];

static void *doit(void *a)
{
    int rc, i, thread_id = (int)a;
    pid_t pid = getpid();
    FCGX_Request request;
    char *server_name;

    FCGX_InitRequest(&request, 0, 0);

    for (;;)
    {
        static pthread_mutex_t accept_mutex = PTHREAD_MUTEX_INITIALIZER;
        static pthread_mutex_t counts_mutex = PTHREAD_MUTEX_INITIALIZER;

        /* Some platforms require accept() serialization, some don't.. */
        pthread_mutex_lock(&accept_mutex);
        rc = FCGX_Accept_r(&request);
        pthread_mutex_unlock(&accept_mutex);

        if (rc < 0)
            break;

        server_name = FCGX_GetParam("SERVER_NAME", request.envp);

        FCGX_FPrintF(request.out,…
        …     

        FCGX_Finish_r(&request);
    }

    return NULL;
}

int main(void)
{
    int i;
    pthread_t id[THREAD_COUNT];

    FCGX_Init();

    for (i = 1; i < THREAD_COUNT; i++)
        pthread_create(&id[i], NULL, doit, (void*)i);

    doit(0);

    return 0;
}

В спецификации FastCGI есть объяснение, как веб-сервер будет определять, сколько соединений поддерживается приложением FastCGI:

Веб-сервер может запрашивать определенные переменные в приложении. Сервер обычно выполняет запрос при запуске приложения, чтобы автоматизировать определенные аспекты конфигурации системы.

...

• FCGI_MAX_CONNS: максимальное количество одновременных транспортных соединений, которое может принять это приложение, например, "1" или "10".

• FCGI_MAX_REQS: максимальное количество одновременных запросов, которое может принять это приложение, например, "1" или "50".

• FCGI_MPXS_CONNS: "0", если это приложение не мультиплексирует соединения (т.е. обрабатывает параллельные запросы по каждому соединению), "1" в противном случае.

Но возвращаемые значения для этого запроса жестко закодированы в FastCGI SDK и возвращают 1 для FCGI_MAX_CONNS и FCGI_MAX_REQS и 0 для FCGI_MPXS_CONNS. Таким образом, образец threadaded.c никогда не получит несколько соединений.

Я протестировал образец с lighttpd и nginx, и приложение обрабатывало только один запрос одновременно. Как я могу получить свое приложение для обработки нескольких запросов? Или это неправильный подход?

3 ответа

Решение

Протестировал программу threadaded.c с http_load. Программа работает за nginx. Работает только один экземпляр программы. Если запросы обслуживаются последовательно, я ожидаю, что для 20 запросов потребуется 40 секунд, даже если они отправляются параллельно. Вот результаты (я использовал те же цифры, что и у Эндрю Брэдфорда - 20, 21 и 40) -

20 запросов, 20 параллельно, заняли 2 секунды -

$ http_load -parallel 20 -fetches 20 request.txt
20 fetches, 20 max parallel, 6830 bytes, in 2.0026 seconds
341.5 mean bytes/connection
9.98701 fetches/sec, 3410.56 bytes/sec
msecs/connect: 0.158 mean, 0.256 max, 0.093 min
msecs/first-response: 2001.5 mean, 2002.12 max, 2000.98 min
HTTP response codes:
  code 200 -- 20

21 запрос, 20 параллельно, занял 4 секунды -

$ http_load -parallel 20 -fetches 21 request.txt
21 fetches, 20 max parallel, 7171 bytes, in 4.00267 seconds
341.476 mean bytes/connection
5.2465 fetches/sec, 1791.55 bytes/sec
msecs/connect: 0.253714 mean, 0.366 max, 0.145 min
msecs/first-response: 2001.51 mean, 2002.26 max, 2000.86 min
HTTP response codes:
  code 200 -- 21

40 запросов, 20 параллельно, заняли 4 секунды -

$ http_load -parallel 20 -fetches 40 request.txt
40 fetches, 20 max parallel, 13660 bytes, in 4.00508 seconds
341.5 mean bytes/connection
9.98732 fetches/sec, 3410.67 bytes/sec
msecs/connect: 0.159975 mean, 0.28 max, 0.079 min
msecs/first-response: 2001.86 mean, 2002.62 max, 2000.95 min
HTTP response codes:
  code 200 -- 40

Таким образом, это доказывает, что даже если значения FCGI_MAX_CONNS, FCGI_MAX_REQS и FCGI_MPXS_CONNS жестко заданы, запросы обслуживаются параллельно.

Когда Nginx получает несколько запросов, он помещает их все в очередь приложения FCGI вплотную. Он не ожидает ответа от первого запроса перед отправкой второго запроса. В приложении FCGI, когда поток в течение какого-то времени обслуживает первый запрос, другой поток не ожидает завершения первого, он подхватывает второй запрос и начинает работать над ним. И так далее.

Таким образом, единственное время, которое вы потеряете, это время, которое требуется для чтения запроса из очереди. Это время обычно ничтожно мало по сравнению со временем обработки запроса.

На этот вопрос нет однозначного ответа, поскольку это зависит не только от протокола FastCGI, но и, прежде всего, от используемого менеджера процессов FastCGI. Для веб-серверов Apache2 диспетчер процессов FastCGI обычно может быть mod_fastcgi или же mod_fcgid, Оба они ведут себя по-разному. mod_fastcgi похоже, поддерживает многопоточность и будет отправлять параллельные запросы серверу FastCGI, который объявил о своей поддержке. mod_fcgid до сих пор (может измениться в будущем?) не поддерживает многопоточность и всегда будет создавать новый процесс сервера FastCGI по параллельному запросу и никогда не будет отправлять параллельные запросы на сервер FastCGI.

Все это говорит о том, что да, в FastCGI предусмотрены многопоточные серверы FastCGI, но среда, в которой работает сервер FastCGI, также должна сделать эту функцию реальностью… на практике, может, а может и нет, и, к сожалению,, mod_fcgid не, по крайней мере пока.

Если ваш FastCGI SDK был из mod_fcgidЭто может быть причиной того, что ответ на FCGI_MAX_CONNS запрос управления всегда возвращает фиксированное жестко закодированное значение 1,

Возможно, вас заинтересует мой недавний вопрос и две другие веб-ссылки, в которых все три упоминают конкретную тему многопоточного сервера FastCGI:

Я думаю, что вы можете тестировать таким способом, который ограничивает вас однопоточностью. Я столкнулся с подобной ситуацией, используя libfcgi и lighttpd, но решил, что если я буду использовать Firefox для тестирования, то Firefox будет искусственно ограничивать отправку HTTP-запроса на сервер, пока предыдущий запрос на тот же сервер не завершится. Инструмент, который вы используете для тестирования, может сделать что-то подобное.

Вам не нужно изменять FCGI_MAX_CONNS, FCGI_MAX_REQS, или же FGCI_MPXS_CONNS, Жестко заданные значения не должны иметь значения для современных веб-серверов, таких как nginx или lighttpd.

Использование инструмента командной строки, такого как curl, и одновременного запуска 20 процессов curl для всех попаданий на сервер приводит к активации всех 20 потоков и завершению всех 20 процессов curl одновременно, через 2 секунды, при работе с примером с многопоточностью.c предоставляется SDK (который имеет явный sleep(2) вызов).

Моя конфигурация lighttpd установлена ​​так:

fastcgi.server = (
    "/test" => (
        "test.fastcgi.handler" => (
            "socket" => "/tmp/test.fastscgi.socket",
            "check-local" => "disable",
            "bin-path" => "/tmp/a.fastcgi",
            "max-procs" => 1,
        )
    )
)

max-procs значение 1 будет порождать только одну копию вашей программы fcgi, и lighttpd должен сообщать об увеличении "нагрузки" на сокет по мере поступления запросов до завершения предыдущего запроса.

Если вы вызываете 21 процесс скручивания, первые 20 должны завершиться через 2 секунды, затем последний должен завершиться еще через 2 секунды. Нерест 40 процессов скручивания должен занимать почти столько же времени, сколько 21 (всего более 4 секунд).

Другие вопросы по тегам