12年 linux 系统运维工程师,网络架构设计、优化,故障处理。
分类: LINUX
2013-08-27 16:29:25
目前httpd守护进程越来越丰富,而Apache2或许是大家最熟悉,应用范围最广泛的。该篇幅主要探讨一下Apache2与性能相关的配置。我们从简单的配置说起。我们开始吧…
HostnameLookups设置如果一旦启用,服务器会对客户端的hostname进行nslookup查询。这将延迟对用户的响应。我们截取了一段,开启了HostnameLookups选项的进程调用记录。
open("/etc/hosts", O_RDONLY) = 22 fcntl64(22, F_GETFD) = 0 fcntl64(22, F_SETFD, FD_CLOEXEC) = 0 fstat64(22, {st_mode=S_IFREG|0644, st_size=174, ...}) = 0 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7ff1000 read(22, "# Do not remove the following li"..., 4096) = 174 read(22, "", 4096) = 0 close(22) = 0 munmap(0xb7ff1000, 4096) = 0 socket(PF_INET, SOCK_DGRAM, IPPROTO_IP) = 22 connect(22, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("202.96.209.5")}, 28) = 0 fcntl64(22, F_GETFL) = 0x2 (flags O_RDWR) fcntl64(22, F_SETFL, O_RDWR|O_NONBLOCK) = 0 gettimeofday({1271519250, 942822}, NULL) = 0 poll([{fd=22, events=POLLOUT}], 1, 0) = 1 ([{fd=22, revents=POLLOUT}]) send(22, "0q\1\0\0\1\0\0\0\0\0\0\00265\0014\003173\00261\7in-addr"..., 42, MSG_NOSIGNAL) = 42 poll([{fd=22, events=POLLIN}], 1, 5000) = 1 ([{fd=22, revents=POLLIN}]) ioctl(22, FIONREAD, [102]) = 0 recvfrom(22,"0q\201\200\0\1\0\1\0\0\0\0\00265\0014\003173\00261\7in-addr"...,1024,0, {sa_family=AF_INET,sin_port=htons(53), sin_addr=inet_addr("202.96.209.5")}, [16]) = 102 close(22)
从这份快照可以看出,Apache首先会从/etc/hosts中查找是否有与客户端hostname相同的DNS记录,如果没有找到,则会连接指定的域名服务器进行nslookup操作。
一般,将AllowOverride设置为AllowOverride None的性能是最优的。如果该值设置All,目录设置允许被.htaccess文件覆盖。那么Apache则会在文件名的每一个组成部分都尝试打开.htaccess文件。要避免这种情况,可以将AllowOverride设置为None。 下面是一段将AllowOverride all设置的系统调用快照,以此来说明取值all的劣性。
stat64("/opt/virtaul_hosts/perfgeeks.com/wp-content/themes/perfgeeks/style.css", {st_mode=S_IFREG|0755, st_size=7929, ...}) = 0 open("/opt/virtaul_hosts/perfgeeks.com/.htaccess", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory) open("/opt/virtaul_hosts/perfgeeks.com/wp-content/.htaccess", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory) open("/opt/virtaul_hosts/perfgeeks.com/wp-content/themes/.htaccess", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory) open("/opt/virtaul_hosts/perfgeeks.com/wp-content/themes/perfgeeks/.htaccess", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory) open("/opt/virtaul_hosts/perfgeeks.com/wp-content/themes/perfgeeks/style.css/.htaccess", O_RDONLY|O_LARGEFILE) = -1 ENOTDIR (Not a directory) open("/opt/virtaul_hosts/perfgeeks.com/wp-content/themes/perfgeeks/style.css", O_RDONLY|O_LARGEFILE) = 22 mmap2(NULL, 7929, PROT_READ, MAP_SHARED, 22, 0) = 0xb7f85000 munmap(0xb7f85000, 7929) = 0
我们可以看到,自stat64()开始,Apache一共进行了5次open()的内核调用,并且都返回-1(表示文件不存在),分别在文件名/wp-content/themes/perfgeeks/style.css每一个组成部分下面试图打开.htaccess文件。必竟这5次open内核函数调用不是很必要。如果有条件的读者可将.htacess中的配置合并到相应的Apache的配置文件中,并且把AllowOverride设置为None,以获得最佳性能。
Options的选值很多,除了All之外,我们还比较关心Indexes、FollowSymLinks和SymLinksIfOwnerMatch。其中FollowSymLinks表示允许在此目录使用符号连接。这是一种什么概念呢,比如/va/ftp/data是你存ftp上传数据的地方,而web目录是/var/www/perfgeeks,假定你想通过访问/var/ftp/data目录的数据,你可以在/var/www/perfgeeks目录下建立一个符号连接ftp指向/var/ftp/data。而FollowSymLinks就是指明这种操作是允许的,指示Apache不必去检查ftp文件是否为链接。另外SymLinksIfOwnerMatch则要求和符号连接与其指向的目录或文件属主是同一人(相同的uid)才允许上述操作,即/var/www/perfgeeks/ftp与/var/ftp/data拥有者的uid要求是一样的,这样Apache就必须通过请求系统内核调用stat()来检查文件名每一个组成部分是否为链接,如果是链接就要去核实是否与链接指向的原文件具有相同的uid。我们推荐设置FollowSymLinks,而不要设置SymLinksIfOwnerMatch,这样可以获取更高的性能。因为,假定没有设置FollowSymLinks或者一旦设置了SymLinksIfOwnerMatch,则会额外地调用系统内核函数lstat()来验证目录是否为符号连接。而且是验证文件的每一个组成部分。下面,我们来看二份快照,分别是设置了FollowSymLinks和SymLinksIfOwnerMatch的strace记录。
#-----Option FollowSymLinks------------------------ read(11, "GET /test/test.php HTTP/1.1\r\nHos"..., 8000) = 417 gettimeofday({1271731674, 100730}, NULL) = 0 stat64("/var/www/html/test/test.php", {st_mode=S_IFREG|0644, st_size=27, ...}) = 0 setitimer(ITIMER_PROF, {it_interval={0, 0}, it_value={60, 0}}, NULL) = 0 #-----Option SymLinksIfOwnerMatch--------------- read(11, "GET /test/test.php HTTP/1.1\r\nHos"..., 8000) = 417 gettimeofday({1271731349, 444196}, NULL) = 0 stat64("/var/www/html/test/test.php", {st_mode=S_IFREG|0644, st_size=27, ...}) = 0 lstat64("/var", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0 lstat64("/var/www", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0 lstat64("/var/www/html", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0 lstat64("/var/www/html/test", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0 lstat64("/var/www/html/test/test.php", {st_mode=S_IFREG|0644, st_size=27, ...}) = 0 setitimer(ITIMER_PROF, {it_interval={0, 0}, it_value={60, 0}}, NULL) = 0
对比两份快照,很明显。SymLinksIfOwnerMatch比FollowSymLinks设置额外地调用了5次lstat(),验证是否为符号连接。
当用户访问的URL以一个”/”结尾的时候,DirectoryIndex则指明了要寻找的资源列表。也就是说,Apache会依次找/path/index.php, /path/index.html等等。所以,DirectoryIndex指定的资源列表顺序与数量都会影响性能,我们推荐数量不宜太多,把最常用的资源放在列表的最前面。这里,我们分别提供不同顺序,不同长度的三份strace快照:
#--配置 DirectoryIndex index.html index.html.var index.php 且资源列表的所有资源都不存在 read(11, "GET /test/ HTTP/1.1\r\nHost: 192.1"..., 8000) = 366 gettimeofday({1271745805, 985450}, NULL) = 0 stat64("/var/www/html/test/", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0 stat64("/var/www/html/test/index.html", 0xbfd3691c) = -1 ENOENT (No such file or directory) lstat64("/var", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0 lstat64("/var/www", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0 lstat64("/var/www/html", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0 lstat64("/var/www/html/test", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0 lstat64("/var/www/html/test/index.html", 0xbfd3691c) = -1 ENOENT (No such file or directory) stat64("/var/www/html/test/index.html.var", 0xbfd3691c) = -1 ENOENT (No such file or directory) lstat64("/var", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0 lstat64("/var/www", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0 lstat64("/var/www/html", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0 lstat64("/var/www/html/test", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0 lstat64("/var/www/html/test/index.html.var", 0xbfd3691c) = -1 ENOENT (No such file or directory) stat64("/var/www/html/test/index.php", 0xbfd3691c) = -1 ENOENT (No such file or directory) lstat64("/var", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0 lstat64("/var/www", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0 lstat64("/var/www/html", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0 lstat64("/var/www/html/test", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0 lstat64("/var/www/html/test/index.php", 0xbfd3691c) = -1 ENOENT (No such file or directory) gettimeofday({1271745805, 988039}, NULL) = 0 #--配置:DirectoryIndex index.html index.html.var index.php 其中index.php资源存在 read(11, "GET /test/ HTTP/1.1\r\nHost: 192.1"..., 8000) = 366 gettimeofday({1271746049, 388916}, NULL) = 0 stat64("/var/www/html/test/", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0 stat64("/var/www/html/test/index.html", 0xbfd3691c) = -1 ENOENT (No such file or directory) lstat64("/var", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0 lstat64("/var/www", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0 lstat64("/var/www/html", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0 lstat64("/var/www/html/test", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0 lstat64("/var/www/html/test/index.html", 0xbfd3691c) = -1 ENOENT (No such file or directory) stat64("/var/www/html/test/index.html.var", 0xbfd3691c) = -1 ENOENT (No such file or directory) lstat64("/var", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0 lstat64("/var/www", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0 lstat64("/var/www/html", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0 lstat64("/var/www/html/test", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0 lstat64("/var/www/html/test/index.html.var", 0xbfd3691c) = -1 ENOENT (No such file or directory) stat64("/var/www/html/test/index.php", {st_mode=S_IFREG|0644, st_size=27, ...}) = 0 setitimer(ITIMER_PROF, {it_interval={0, 0}, it_value={60, 0}}, NULL) = 0 #--配置DirectoryIndex index.php index.html index.html.var 且资源index.php存在 read(11, "GET /test/ HTTP/1.1\r\nHost: 192.1"..., 8000) = 392 gettimeofday({1271746373, 504237}, NULL) = 0 stat64("/var/www/html/test/", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0 stat64("/var/www/html/test/index.php", {st_mode=S_IFREG|0644, st_size=27, ...}) = 0 setitimer(ITIMER_PROF, {it_interval={0, 0}, it_value={60, 0}}, NULL) = 0
对比上面三份不同的DirectoryIndex配置与快照,我们可以看到正确位置、合适的资源列表数目给我们省下了不少的系统内核函数调用。当用户访问一个目录的时候,Apache会根据DirectoryIndex指定的资源列表依次查找。所以,第三份是性能最佳的配置。
我们应该尽量避免使用Options MultiViews来实现内容协商,它非常低效,因为要搜索整个目录。我们可以通过type-map的方式实现内容协商。把所有要用到的type-map放在一个文件里面,比搜索文件性能会更高。所谓,内容协商,指从几个有效资源中选择一个最匹配用户端请求的资源给用户端,这个过程就叫做内容协商。比如,用户请求了资源/path/test/foo, 而且/path/test/foo文件并不存在。MultiViews的做法就是在整个目录查找foo.*所有文件,并且跟据用户请求的content-type, content-encoding等信息,把一个最接近用户请求的资源作为用户请求的资源,返回给用户。这里给出了一份Options MultiViews方式内容协商的快照,以供参考。
read(11, "GET /test/foo HTTP/1.1\r\nHost: 19"..., 8000) = 369 gettimeofday({1271751239, 855470}, NULL) = 0 stat64("/var/www/html/test/foo", 0xbf93f62c) = -1 ENOENT (No such file or directory) lstat64("/var", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0 lstat64("/var/www", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0 lstat64("/var/www/html", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0 lstat64("/var/www/html/test", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0 lstat64("/var/www/html/test/foo", 0xbf93f62c) = -1 ENOENT (No such file or directory) open("/var/www/html/test/", O_RDONLY|O_NONBLOCK|O_LARGEFILE|O_DIRECTORY) = 12 fstat64(12, {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0 fcntl64(12, F_SETFD, FD_CLOEXEC) = 0 getdents64(12, /* 9 entries */, 4096) = 272 stat64("/var/www/html/test/foo.gif", {st_mode=S_IFREG|0644, st_size=0, ...}) = 0 stat64("/var/www/html/test/foo.php", {st_mode=S_IFREG|0644, st_size=0, ...}) = 0 stat64("/var/www/html/test/foo.pdf", {st_mode=S_IFREG|0644, st_size=0, ...}) = 0 stat64("/var/www/html/test/foo.jpg", {st_mode=S_IFREG|0644, st_size=0, ...}) = 0 stat64("/var/www/html/test/foo.txt", {st_mode=S_IFREG|0644, st_size=0, ...}) = 0 getdents64(12, /* 0 entries */, 4096) = 0 close(12) = 0 … lstat64("/var/www/html/test/foo.php", {st_mode=S_IFREG|0644, st_size=0, ...}) = 0 open("/var/www/html/test/foo.php", O_RDONLY) = 12 fstat64(12, {st_mode=S_IFREG|0644, st_size=0, ...}) = 0 lseek(12, 0, SEEK_CUR) = 0 read(12, "", 8192) = 0 close(12) = 0 chdir("/etc/httpd/conf") = 0 setitimer(ITIMER_PROF, {it_interval={0, 0}, it_value={0, 0}}, NULL) = 0 writev(11, [{"HTTP/1.1 200 OK\r\nDate: Tue, 20 A"..., 254}], 1) = 254 write(8, "192.168.0.98 - - [20/Apr/2010:16"..., 172) = 172 shutdown(11, 1 /* send */)
我们在/test目录下面分别创建了foo.*共5个文件,当我们请求/test/foo的时候,我们可以从加粗部分,很容易地发现Apache在找不到/test/foo的情况下,它会在/test查看所有foo.*文件状态。我们再从红色部分可以发现Apache经过内容协商之后,最终决定以/test/foo.php作为用户的响应,而并没有返回用户404错误。所以,我们在配置Apache的时候,尤其要避免使用Option MultiViews,因为这种配置效率较低。
尽可能地打开内存映射文件设置。所谓内存映射文件,指的就是Linux内核通过内核调用mmap()将磁盘文件与进程某段内存地址空间(有可能是物理内存,但也有可能是虚拟内存)对映起来,自此Apache就可以通过访问内存直接访问磁盘文件,它不需要使用read()和write()等系统内核调用来访问磁盘了。所以,在大多数情况下,可以通过内存映射文件的机制来提高磁盘I/O性能。Apache2开始支持mmap功能,对于较大的静态文件,Apche是不会使用mmap的,因为需要不小的内存开销。另外,mmap仅对较小的静态文件有效。也就是说,Apache2对于较小的静态文件,才会启用mmap将文件映射到进程内存地空间。
read(11, "GET /test/test.html HTTP/1.1\r\nHo"..., 8000) = 375 gettimeofday({1271778564, 175940}, NULL) = 0 stat64("/var/www/html/test/test.html", {st_mode=S_IFREG|0644, st_size=10, ...}) = 0 open("/var/www/html/test/test.html", O_RDONLY|O_LARGEFILE) = 12 mmap2(NULL, 10, PROT_READ, MAP_SHARED, 12, 0) = 0xb6107000 writev(11, [{"HTTP/1.1 200 OK\r\nDate: Tue, 20 A"..., 260}, {"hello wold", 10}], 2) = 270 munmap(0xb6107000, 10) = 0 write(8, "192.168.0.98 - - [20/Apr/2010:23"..., 179) = 179
在这里,加粗部分我们可以看得出。open()以只读的方式打开请求文件,接下来通过mmap2()将文件映射到进程内存地址空间。我们并没有看到系统内核read()调用,这是因为Apache直接读取了进程内存空间的数据,属于用户态行为。即,一旦映射之后,用户就不用像从前一次一次通过系统内核调用read()去将磁盘数据读入用户态内存空间了。这样省去了与用户空间与内核空间读文件上下切换的开销。
如果你的系统支持Sendfile机制的话,通过EnableSendfile On设置开启该机制。Sendfile机制可以提高Apache性能。所谓Sendfile,就是我们平常所提到过的”零拷贝”。一般,我们对一个磁盘文件写操作,要经过几次拷贝才能够完成。首先用户进程,通过fgets()向请求系统内核调用read,这时候read会将磁盘文件数据拷到系统内核空间的缓冲区,然后系统内核调用返回的数据拷到用户空间的缓冲区。当回写数据的时候,又会通过fwrite()函数请求系统内核调用write,将数据写回磁盘。磁盘设备->内核缓冲(内存)->用户缓冲(内存)来回共4次拷贝。对于一个静态文件请求,我们会发现,首先从磁盘设备将数据拷贝到内核空间缓冲区,又从内核空间缓冲区拷贝到用户空间缓冲区,马上又从用户空间缓冲区拷贝回内核缓冲区,接着从内核缓冲区写入到网络接口设备。我们发现,静态数据从内核空间交给用户空间之后,并没有被程序做任何处理,又原原本本地传回了内核空间,数据就这样白白地兜了一圈。所以,Linux自支持Sendfile机制以后,它可以让数据不用交给用户空间缓冲区,直接在内核空间处理掉。这样节约了内核态的切换和用户态数据的复制开销。Apache对于较大的静态资源请求,会启用Sendfile。下面,我们来对比一下启用与不启用sendfile的快照
open("/var/www/html/test/20090316.pdf", O_RDONLY|O_LARGEFILE) = 13 writev(11, [{"HTTP/1.1 200 OK\r\nDate: Wed, 21 A"..., 260}], 1) = 260 _llseek(13, 0, [0], SEEK_SET) = 0 read(13, "%PDF-1.4\n%\n3 0 obj\n<<\n/Producer "..., 8192) = 8192 write(11, "%PDF-1.4\n%\n3 0 obj\n<<\n/Producer "..., 8192) = 8192 read(13, "\310Y\10\302\263\3330\r W"..., 8192) = 8192 write(11, "\310Y\10\302\263\3330\r/H\265x W"..., 8192) = 8192 read(13, "\315~\273:\371w\t\324\246\307"..., 8192) = 8192 write(11, "\315~\273:\371w\t\324\0333\215F\303Pa\246\307"..., 8192) = 8192 read(13, "\236\263\207\330\336\370\16{[\231\323;\206"..., 8192) = 8192 write(11, "\236\263\207\330\16{[\231\323;\206"..., 8192) = 8192 .... poll([{fd=11, events=POLLOUT, revents=POLLOUT}], 1, 120000) = 1 #---------------------sendfile-------------- stat64("/var/www/html/test/20090316.pdf", {st_mode=S_IFREG|0644, st_size=502106, ...}) = 0 open("/var/www/html/test/20090316.pdf", O_RDONLY|O_LARGEFILE) = 12 setsockopt(11, SOL_TCP, TCP_CORK, [1], 4) = 0 writev(11, [{"HTTP/1.1 200 OK\r\nDate: Wed, 21 A"..., 260}], 1) = 260 sendfile64(11, 12, [0], 502106) = 34780 setsockopt(11, SOL_TCP, TCP_CORK, [0], 4) = 0 poll([{fd=11, events=POLLOUT, revents=POLLOUT}], 1, 120000) = 1 sendfile64(11, 12, [34780], 467326) = 33580 poll([{fd=11, events=POLLOUT, revents=POLLOUT}], 1, 120000) = 1 sendfile64(11, 12, [68360], 433746) = 21900 poll([{fd=11, events=POLLOUT, revents=POLLOUT}], 1, 120000) = 1 sendfile64(11, 12, [90260], 411846) = 55480
快照显示,启用sendfile之后一次性读取的数据会比read()读取得多好多倍,read()每次只读取8192bytes到缓冲区。而没有启用sendfile功能,需要请求read()和write()不少回才调用一次poll事件。同时每次poll事件都需要多次复制数据。需要注意的是,使用sendfile最好把ipv6内核模块卸掉。同时,对于很小的静态文件请求,sendfile发挥的作用并不十分明显,这是因为处理小文件时,发送数据的整个周期占用的时间比例并不多,效果也自然不明显。
我们可以通过Deny/Allow指令限制一些非法访问。比如Deny from 或者Deny from 220.181.6.175。虽然,二者都拒绝了相同的访问者。但是后者是我们比较推荐的做法,能够得到更好的性能。因为,前者Apache需要进行nslookup操作,而且需要进行二次DNS查询,即正、反查询。所以,为了更好的性能,我们推荐使用Deny/Allow from ip_address,而不推荐使用Deny/Allow from domain。这里,我们进演示一下Deny from 配置的一个strace快照,以供参考
stat64("/var/www/html/test/index.php", {st_mode=S_IFREG|0644, st_size=27, ...}) = 0 open("/etc/hosts", O_RDONLY) = 12 fcntl64(12, F_GETFD) = 0 fcntl64(12, F_SETFD, FD_CLOEXEC) = 0 fstat64(12, {st_mode=S_IFREG|0644, st_size=180, ...}) = 0 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb60d3000 read(12, "# Do not remove the following li"..., 4096) = 180 read(12, "", 4096) = 0 close(12) = 0 munmap(0xb60d3000, 4096) = 0 socket(PF_INET, SOCK_DGRAM, IPPROTO_IP) = 12 connect(12, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("192.168.0.2")}, 28) = 0 fcntl64(12, F_GETFL) = 0x2 (flags O_RDWR) fcntl64(12, F_SETFL, O_RDWR|O_NONBLOCK) = 0 gettimeofday({1271861040, 74406}, NULL) = 0 poll([{fd=12, events=POLLOUT, revents=POLLOUT}], 1, 0) = 1 send(12, "\317\n\1\0\0\1\0\0\0\0\0\0\00298\0010\003168\003192\7in-add"..., 43, MSG_NOSIGNAL) = 43 poll([{fd=12, events=POLLIN, revents=POLLIN}], 1, 5000) = 1 ioctl(12, FIONREAD, [133]) = 0 recvfrom(12,"\317\n\205\203\0\1\0\0\0\1\0\0\00298\0010\003168\003192\7in-add"...,1024,0, {sa_family=AF_INET,sin_port=htons(53), sin_addr=inet_addr("192.168.0.2")}, [16]) = 133 close(12)
这里我们可以看到,Apache先将/etc/hosts映射到内存,然后查找的DNS记录。在没有找到相应DNS记录的时候,创建了一个socket,并且连接到本地域名服务器192.168.0.2,并且发出了DNS查询请求。
服务器的物理内存资源是非常有限且宝贵,加载没有使用到的模块会占用额外的内存空间,而且一直占用着,直到关闭进程。我们来看一看,这是CentOS5.3默认的Apache占用的内存量。下面两行分Apache默认加载模块与禁用掉不必要加载模块内存占用对比。虽然只节约了2k不到的内存,但需要注意的是这是一个Apache子进程节约的内存空间。
apache 11069 0.0 0.4 54780 9320 pts/1 S+ 22:38 0:00 /usr/sbin/httpd apache 11651 0.1 0.3 52428 7500 pts/4 S+ 10:46 0:00 /usr/sbin/httpd
我们经过测试后,根据我们的实际情况,最终只打开了以下Apache模块
@ cat /usr/local/httpd/conf/httpd.conf |grep "LoadModule" |grep -v "^#" LoadModule authz_host_module modules/mod_authz_host.so LoadModule log_config_module modules/mod_log_config.so LoadModule env_module modules/mod_env.so LoadModule ext_filter_module modules/mod_ext_filter.so LoadModule mime_magic_module modules/mod_mime_magic.so LoadModule expires_module modules/mod_expires.so LoadModule deflate_module modules/mod_deflate.so LoadModule headers_module modules/mod_headers.so LoadModule setenvif_module modules/mod_setenvif.so LoadModule mime_module modules/mod_mime.so LoadModule autoindex_module modules/mod_autoindex.so LoadModule dir_module modules/mod_dir.so LoadModule alias_module modules/mod_alias.so LoadModule rewrite_module modules/mod_rewrite.so
这些内容,已经上一篇幅<<人人都能做的性能优化—Web前端优化>>中详细地探讨过,这里就不再重复了。这里只贴一下,我们相关的配置吧。
KeepAlive On MaxKeepAliveRequests 100 KeepAliveTimeout 10ExpiresActive On ExpiresByType image/* "access plus 1 year" ExpiresByType text/css "access plus 1 year" ExpiresByType image/ico "access plus 1 year" ExpiresByType image/icon "access plus 1 year" ExpiresByType image/x-icon "access plus 1 year" ExpiresByType text/javascript "access plus 1 year" ExpiresByType text/js "access plus 1 year" ExpiresByType application/x-javascript "access plus 1 year" ExpiresByType application/javascript "access plus 1 year" SetOutputFilter DEFLATE SetEnvIfNoCase Request_URI \.(?:exe|t?gz|zip|iso|tar|bz2|sit|rar)$ no-gzip dont-vary SetEnvIfNoCase Request_URI \.(?:gif|jpe?g|jpg|ico|png)$ no-gzip dont-vary SetEnvIfNoCase Request_URI \.pdf$ no-gzip dont-vary SetEnvIfNoCase Request_URI \.flv$ no-gzip dont-vary BrowserMatch ^Mozilla/4 gzip-only-text/html BrowserMatch ^Mozilla/4\.0[678] no-gzip BrowserMatch \bMSIE !no-gzip !gzip-only-text/html Header append Vary User-Agent env=!dont-vary Header append Cache-Control "publish, must-revalidate" Header append Cache-Control publish
有时候出于维护原因。我们可能会建立一些虚拟目录,进行一些数据受限访问。这种情况,我们建议不要记录日志,因为读日志文件与写日志是磁盘I/O操作。操作I/O操作始终很昂贵。
目录的层次结构,不要太深。我们推荐3层就差不多了。因为,我们常常需要lstat(), stat(), cd()等操作于这些目录。虽然,这些内核调用微不足道,但是我们还是要遵守这样一条优化原则:减少不必要的系统内核调用。
我们将在其它篇幅更深入地介绍Apache Worker MPM与Apache Prefork MPM
1. 避免不必要的DNS查询
a) HostnameLookups Off
b) Deny/Allow from ip_address
2. Sendfile对于较大静态资源请求效率更高,同时建议关闭操作系统ipv6内核模块
3. 减少不必要的系统内核调用
a) AllowOverride None 禁止去尝试打开.htaccess
b) Options FollowSymLinks 禁止去判断访问目录是否为连接
c) DirectoryIndex index.php index.html 合理的资源列表数握与位置顺序会节约更多的系统内核调用
d) Options MultiViews 低效的内容协商
e) 保护简洁的目录层级结构
f) 关闭不要必要的日志记录功能
4. 节约系统内存资源:不要加载没有使用到的模块
5. MMap、Deflate(Gzip)、Expires、Header、Keep-Alive
6. 有条件的话,自己编译Apache,并且支持Worker MPM
7. strace /usr/sbin/httpd -X -f /etc/http/conf/httpd.conf方便跟踪系统内核调用。-X即debug模式,只启用单个worker,且不从控制台分离