人人都能做的性能优化之Apache性能优化

blogdaren 2014-12-27 抢沙发 1512人次

目前httpd守护进程越来越丰富,而Apache2或许是大家最熟悉,应用范围最广泛的。该篇幅主要探讨一下Apache2与性能相关的配置。我们从简单的配置说起:
HostnameLookups Off

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 None

一 般,将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 FollowSymLinks

Options 的选值很多,除了All之外,我们还比较关心Indexes、FollowSymLinks和SymLinksIfOwnerMatch。其中 FollowSymLinks表示允许在此目录使用符号连接。这是一种什么概念呢,比如/va/ftp/data是你存ftp上传数据的地方,而web目 录是/var/www/perfgeeks,假定你想通过http://www.perfgeeks.com/ftp访问/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(),验证是否为符号连接。
DirectoryIndex index.php index.html index.html.var

当 用户访问的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

我 们应该尽量避免使用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,因为这种配置效率较低。
EnableMMAP on

尽 可能地打开内存映射文件设置。所谓内存映射文件,指的就是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()去将磁盘数据读入用户态内存空间了。这样省去了与用户空间与内核空间读文件上下切换的开销。
EnableSendfile On

如 果你的系统支持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 from ip_address

我 们可以通过Deny/Allow指令限制一些非法访问。比如Deny from www.baidu.com或者Deny from 220.181.6.175。虽然,二者都拒绝了相同的访问者。但是后者是我们比较推荐的做法,能够得到更好的性能。因为,前者Apache需要进行 nslookup操作,而且需要进行二次DNS查询,即正、反查询。所以,为了更好的性能,我们推荐使用Deny/Allow from ip_address,而不推荐使用Deny/Allow from domain。这里,我们进演示一下Deny from www.baidu.com配置的一个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映射到内存,然后查找www.baidu.com的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  

Keep-Alive、Mod_Expires、Header、Gzip Compress(Deflate)

这些内容,已经上一篇幅<<人人都能做的性能优化—Web前端优化>>中详细地探讨过,这里就不再重复了。这里只贴一下,我们相关的配置吧。

KeepAlive On  MaxKeepAliveRequests 100  KeepAliveTimeout 10  <IfModule mod_expires.c>      ExpiresActive 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"  </IfModule>  <IfModule mod_deflate.c>      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  </IfModule>  <FilesMatch "\.(js|css)$">      Header append Cache-Control "publish, must-revalidate"  </FilesMatch>  <FilesMatch "\.(gif|png|jpe?g|swf)$">      Header append Cache-Control publish  </FilesMatch>  

如果没有必要记日志,把日志记录关闭

有时候出于维护原因。我们可能会建立一些虚拟目录,进行一些数据受限访问。这种情况,我们建议不要记录日志,因为读日志文件与写日志是磁盘I/O操作。操作I/O操作始终很昂贵。
目录的层级不要太深,越简单越好

目录的层次结构,不要太深。我们推荐3层就差不多了。因为,我们常常需要lstat(), stat(), cd()等操作于这些目录。虽然,这些内核调用微不足道,但是我们还是要遵守这样一条优化原则:减少不必要的系统内核调用。
Worker MPM与Prefork MPM。(待续)

我们将在其它篇幅更深入地介绍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


版权声明:除非注明,本文由( blogdaren )原创,转载请保留文章出处。

本文链接:人人都能做的性能优化之Apache性能优化

发表评论:

您的昵称:
电子邮件:
个人主页: