多线程FastCGI应用程序

8

我希望编写一个FastCGI应用程序,它应该使用线程处理多个同时请求。我查看了SDK附带的threaded.c示例:

#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规范中有解释,Web服务器将确定FastCGI应用程序支持的连接数的方法:
引用: 应用程序内部可以查询特定变量。服务器通常会在应用程序启动时执行查询以自动化某些系统配置方面的操作。
• FCGI_MAX_CONNS:此应用程序将接受的最大并发传输连接数,例如“1”或“10”。
• FCGI_MAX_REQS:此应用程序将接受的最大并发请求数,例如“1”或“50”。
• FCGI_MPXS_CONNS:如果此应用程序不复用连接(即处理每个连接上的并发请求),则为“0”,否则为“1”。
但是这个查询的返回值被硬编码到FastCGI SDK中,FCGI_MAX_CONNS和FCGI_MAX_REQS返回1,FCGI_MPXS_CONNS返回0。因此,threaded.c示例永远不会接收多个连接。
我使用lighttpd和nginx测试了该示例,并且应用程序一次只处理一个请求。如何使我的应用程序处理多个请求?或者这种方法是错误的吗?

1
你是如何测试并发性的?有很多不同的信息可用于说明FCGI支持并发性的程度。虽然我不敢说已经找到了所有答案,但我尝试了一个多线程测试来验证并发性:配置两个线程以接受连接。第一个线程在接受连接后会休眠10秒钟,阻塞该线程。第二个连接被接受并响应。最后,第一个线程完成并响应。你的问题已经快一年了,但我一直在努力寻找关于FCGI的答案,希望这可以帮到你。 - HikeOnPast
threaded.c的链接已经失效。这是一个新链接:https://github.com/FastCGI-Archives/fcgi2/blob/123dc84662e6b52962fb2281a4b1918d49ecdc40/examples/threaded.c - Flux
3个回答

6

使用http_load对threaded.c程序进行了测试,程序运行在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应用程序中,当一个线程为第一个请求提供服务时,另一个线程不会等待第一个线程完成,而是会拿起第二个请求并开始处理。以此类推。
因此,您只会失去从队列中读取请求所需的时间。与处理请求所需的时间相比,这段时间通常可以忽略不计。

很棒的答案。现在一切都清晰明了。 - Hello World

2
对于这个问题,并没有一个单一的答案,因为这不仅取决于FastCGI协议,还并且更重要的是取决于FastCGI进程管理器的使用情况。对于Apache2 Web服务器,FastCGI进程管理器通常可能是mod_fastcgimod_fcgid。这两者的行为不同。mod_fastcgi似乎是多线程感知的,如果FastCGI服务器声明支持它,则会向其发送并发请求。mod_fcgid目前(未来可能会改变?)不具备多线程感知能力,将始终在并发请求上生成新的FastCGI服务器进程,并且永远不会将并发请求发送到FastCGI服务器。
所有这些都可以说明:是的,FastCGI提供了多线程FastCGI服务器的功能,但运行FastCGI服务器的环境也必须使此功能成为现实...在实践中,它可能存在或不存在,并且不幸的是,至少到目前为止mod_fcgid没有实现该功能。
如果您的FastCGI SDK来自mod_fcgid,那么这可能是FCGI_MAX_CONNS管理请求的响应总是返回固定硬编码值1的原因。
您可能会对我的最近一个问题以及其他两个Web链接感兴趣,这三个链接都提到了多线程FastCGI服务器的特定主题:

1

我认为你可能在进行测试时限制了单线程。我曾经遇到过类似的情况,使用libfcgi和lighttpd,但是我确定如果我使用Firefox进行测试,Firefox会人为地限制向服务器提交HTTP请求,直到先前发送到同一服务器的请求完成。你正在使用的测试工具可能会做类似的事情。

你不需要修改FCGI_MAX_CONNSFCGI_MAX_REQSFGCI_MPXS_CONNS。硬编码的值对于像nginx或lighttpd这样的现代Web服务器来说并不重要。

使用像curl这样的命令行工具,并同时生成20个curl进程以全部命中服务器,结果所有20个线程都会激活,并且所有20个curl进程在2秒后同时完成,当针对SDK提供的示例threaded.c运行时(其中有一个显式的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个curl进程,则前20个应该在2秒内完成,然后最后一个应该在另外2秒内完成。生成40个curl进程应该需要与21个几乎相同的时间(总共略超过4秒)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接