Apache 性能最优化分析(下)
有两种实现这个特性的办法:一是socket的SO_LINGER选项。但似乎是命中注定,在多数TCP/IP协议栈中它从来不能正确地实现。即使是在提供了正确实现的平台(即Linux 2.0.31)上,这种方法也要比第二种方法代价(指CPU时间)高得多。
大多数情况下,Apache在一个叫lingering_close的函数中实现了它(在 http_main.c)。这个函数大致如下所示:
void lingering_close (int s)
{
char junk_buffer[2048];
/* shutdown the sending side */
shutdown (s, 1);
signal (SIGALRM, lingering_death);
alarm (30);
for (;;) {
select (s for reading, 2 second timeout);
if (error) break;
if (s is ready for reading) {
read (s, junk_buffer, sizeof (junk_buffer));
/* just toss away whatever is here */
}
}
close (s);
}
这自然增加了连接结束时的开销,但它是可靠的实现所必需的。随着HTTP/1.1的日益盛行,所有连接都是持久的,这种开销将被众多的连接请求抵消。如果您想冒险禁止这一特性的话,可以定义宏NO_LINGCLOSE,但这显然是不被推荐的。实际上,由于在HTTP/1.0中持久的管道式连接越来越普遍,lingering_close几乎是必须的选择。(管道式连接非常高效,所以您还是希望支持它的吧)
5) 记分板文件
Apache利用一种叫做记分板(scoreboard)的技术在父、子进程间通讯。它的理想实现是在共享内存中。有的操作系统允许我们直接访问共享内存,或者提供它们的确切端口。在这些系统中的典型实现就是共享内存记分板。其他的系统则将磁盘上的文件作为缺省实现。磁盘文件不仅低效而且不稳定(又没有什么优势)。请为您的操作系统仔细阅读src/main/conf.h文件,并在其中寻找USE_MMAP_SCOREBOARD或者USE_SHMGET_SCOREBOARD。定义它们之一(以及相应的HAVE_MMAP和HAVE_SHMGET)将允许Apache使用共享内存。如果您系统的内存共享机制与众不同,请编辑src/main/http_main.c并增加Apache所需的挂钩函数(同时请把补丁寄给我们)
注:直到1.2版,Apache的Linux版才开始使用共享内存。这一疏忽使得以前版本的Apache在Linux上表现得很不理想。
DYNAMIC_MODULE_LIMIT
如果您不打算支持动态加载模块的话(准备榨出最后一滴性能的您可能希望如此),编译服务器时请设定参数-DDYNAMIC_MODULE_LIMIT=0。这将节省出为动态加载模块而分配的内存。
附录:对某次跟踪状况的详细分析
本附录描述了运行在Linux上的Apache 1.3系统调用的跟踪情况。运行时(run-time)配置文件中除了必要的缺省选项外还增加了:
AllowOverride none
Options FollowSymLinks
被请求的文件是一个6K的静态网页,其中不包含特殊内容。对于非静态或者伴随有内容协商的请求,跟踪结果将有明显的不同(一些情况下会十分晦涩)。我们将首先列出完整的跟踪结果,然后逐条进行分析。(它是由strace跟踪程序生成的,其他类似的程序包括truss、ktrace和par)
accept(15, {sin_family=AF_INET, sin_port=htons(22283), sin_addr=inet_addr("127.0.0.1")}, [16]) = 3
flock(18, LOCK_UN) = 0
sigaction(SIGUSR1, {SIG_IGN}, {0x8059954, [], SA_INTERRUPT}) = 0
getsockname(3, {sin_family=AF_INET, sin_port=htons(8080), sin_addr=inet_addr("127.0.0.1")}, [16]) = 0
setsockopt(3, IPPROTO_TCP1, [1], 4) = 0
read(3, "GET /6k HTTP/1.0\r\nUser-Agent: "..., 4096) = 60
sigaction(SIGUSR1, {SIG_IGN}, {SIG_IGN}) = 0
time(NULL) = 873959960
gettimeofday({873959960, 404935}, NULL) = 0
stat("/home/dgaudet/ap/apachen/htdocs/6k", {st_mode=S_IFREG|0644, st_size=6144, ...}) = 0
open("/home/dgaudet/ap/apachen/htdocs/6k", O_RDONLY) = 4
mmap(0, 6144, PROT_READ, MAP_PRIVATE, 4, 0) = 0x400ee000
writev(3, [{"HTTP/1.1 200 OK\r\nDate: Thu, 11"..., 245}, {"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 6144}], 2) = 6389
close(4) = 0
time(NULL) = 873959960
write(17, "127.0.0.1 - - [10/Sep/1997:23:39"..., 71) = 71
gettimeofday({873959960, 417742}, NULL) = 0
times({tms_utime=5, tms_stime=0, tms_cutime=0, tms_cstime=0}) = 446747
shutdown(3, 1 /* send */) = 0
oldselect(4, [3], NULL, [3], {2, 0}) = 1 (in [3], left {2, 0})
read(3, "", 2048) = 0
close(3) = 0
sigaction(SIGUSR1, {0x8059954, [], SA_INTERRUPT}, {SIG_IGN}) = 0
munmap(0x400ee000, 6144) = 0
flock(18, LOCK_EX) = 0
accept串行化:
flock(18, LOCK_UN) = 0
...
flock(18, LOCK_EX) = 0
这两个调用可以被上文提到的宏SINGLE_LISTEN_UNSERIALIZED_ACCEPT去掉。
对信号SIGUSR1的处理:
sigaction(SIGUSR1, {SIG_IGN}, {0x8059954, [], SA_INTERRUPT}) = 0
...
sigaction(SIGUSR1, {SIG_IGN}, {SIG_IGN}) = 0
...
sigaction(SIGUSR1, {0x8059954, [], SA_INTERRUPT}, {SIG_IGN}) = 0
