本文最初写于 几年前,涉及特定软件版本。在调优系统时,请始终注意你所运行的版本。
这只是一些关于调优一个完整 LAMP 服务器以应对一定用户流量和服务负载的小经验。需要重点注意的是,本文中的所有内容并非“标准答案”。你可能需要根据自己服务器的实际使用情况、服务器负载、开发及架构进行更多微调。因此,请将这些技巧作为灵感来源,而非“操作手册”。别忘了在进行此类调优时,注意备份之前的配置文件。- 当前操作系统:Debian GNU Linux Kernel 2.4.32 ipv4 + GRSEC
- Intel(R) Celeron(R) CPU 2.66GHz
- 运行的服务:Qmail、Bind9、mrtg、Apache 2.2.2、PHP 5.1.4、MySQL 5.0.21
调优服务器的最佳方式是将专用服务部署在单台服务器上,或者采用多台服务器,特别是将 MySQL 和 Apache 分开部署。我们当时运行着一个流量较大的 DotClear 网站,以及负载较重的 PhpADS(包含 GeoIP、各种计数器等功能)。服务器在某些高峰时段负载达到了 114,并且交换分区被完全占满!随之而来的是服务大面积卡死…… 日均邮件量 7 万封,日均页面浏览量 11 万,日均独立访客 1.2 万,每秒 SQL 查询 47 次。实际上,服务本身负载并没有那么高,但机器频繁崩溃,且在不怎么使用 CPU 的情况下经常发生大量交换。我做的第一件事是将 Linux 内核从 2.4.32 升级到 2.6.18。2.6 内核有许多改进。我建议你阅读以下文章:http://www-128.ibm.com/developerworks/linux/library/l-web26/http://developer.osdl.org/craiger/hackbench/http://kerneltrap.org/node/903升级之后,我又花时间更新了所有软件版本,使用了 MySQL 5.0.27、PHP 5.2 等。即使不看更新日志,这些修复对我们也有帮助 :-)之后,我们将调优仍在使用默认值的软件配置(这真的很糟糕!:-)),然后在不重新编译内核的情况下对内核进行一些微调。Apache 2.2.2 Prefork
我们的HTTPD使用了一些模块,如 URL 重写、服务器信息、php5、GeoIP 和其他基本模块。我们可以通过使用 Apache 2.2.3 Worker 模式并仅加载必要模块来进一步优化,甚至可以采用更极端的做法:使用静态页面交付并通过代理处理动态页面。所有这些都取决于你的开发和服务器使用情况。这里我们只关注 Apache Prefork。如今,保持启用KEEPALIVE功能很重要。这将提高许多现代浏览器(IE、Firefox、Safari、Opera 等)的页面交付速度。唯一需要做的是稍微调整默认值。实际上,如果 keepalive 超时时间过长,你可能会为一个可能已经离开的用户保持整个 Apache 进程的开启状态!4 秒的超时时间足够交付整个网页并应对网络拥塞。MaxKeepAliveRequests 用于定义在 keepalive 会话期间,一个 Apache 进程可以处理的最大请求数。除非你的网页需要加载大量图片,否则不需要将此值设得太大。KeepAlive On
KeepAliveTimeout 4
MaxKeepAliveRequests 500
由于服务器可用内存不多,我不得不将运行中的服务器进程数从 150 大幅降低到 60。由于我的 Apache 进程大约占用 13MB 内存(扣除 3MB 共享内存),当所有 Apache 子进程都运行时,大约需要 600MB 的可用内存。在后续调优中,我们必须考虑到这部分内存已被使用。在我们的案例中,为内存预留空间以避免过多交换和机器卡死非常重要。你可以使用TOP命令并查看 apache/httpd 进程来监控内存使用情况。(快速执行 man top 以了解更多信息。)如果你有更多空闲内存,可以参考 Apache 文档 进行进一步的调优。ServerLimit 60
MaxClients 60
我们的服务器经常过载,流量很大。当我需要重启 Apache 时,或者发生崩溃时,Apache 服务器启动时只会产生 5 个子进程,然后会在 1 秒后增加 1 个新进程,2 秒后增加 2 个新进程,3 秒后增加 4 个新进程,依此类推。在高峰时段,这太慢了!因此,我将 StartServers 配置为直接启动 30 个子进程。这将帮助我们快速响应客户端,并最大限度地减少服务器重启带来的影响。MinSpareServers 和 MaxSpareServers 的用法与 StartServer 类似。当 Apache 服务器负载不高时,会有空闲子进程等待连接。让所有子进程都保持开启状态并不划算,但在新的高峰来临时,为了最小化对服务器的影响,最好的办法是尽快交付网页。因此,保留一些空闲子进程等待客户端并非愚蠢之举。此外,在我们的敏感服务器场景中,我们考虑分配 600MB 的 RAM。所以,即使这些内存用于空闲子进程,我们也可以使用它们,因为我们已经为 Apache 预留了这部分 RAM。为了避免模块内存泄漏,并确保子进程完全可用,我将 MaxRequestPerChild 设置为 1000,这意味着每处理 1000 个请求,该子进程将被杀死,Apache 服务器会重新生成一个新的。你可能需要将此值设置得更高,这取决于你网页的结构。在更改之后,你需要监控服务器,确保不会有太多子进程被杀死/重新生成,而不是在交付网页。StartServers 30
MinSpareServers 30
MaxSpareServers 30
MaxRequestsPerChild 1000
出于安全考虑,我们不显示太多关于服务器的信息。因为我们不需要对客户端 IP 进行反向查找,所以将 HostnameLookups 保持为 Off,这样可以节省一些网络流量和服务器负载。ServerTokens Prod
ServerSignature Off
HostnameLookups Off
PHP 5.1.4
为了提高页面生成性能并节省 CPU,我们使用了 PHP 扩展 eaccelerator。请查阅其文档进行安装。我们为 eaccelerator 分配了 32MB 内存(shm_size),并将其与共享内存和文件缓存一起使用(键、会话和内容变量设置为 "shm_and_disk")。(在我们的场景中,内存非常有用,因为大量的邮件、Apache 日志和 MySQL 磁盘访问产生了大量的 I/O,严重拖慢了整个服务器)。由于我们不会频繁更改服务器上的 PHP 脚本,因此不需要使用check_mtime功能。当设置为 "1" 时,它会对 PHP 脚本进行 stat 操作以检查最后修改日期。我们不需要这个功能,因为我们要节省磁盘访问,而且运行中的脚本更新并不频繁。我们只需要在更新后清理缓存目录即可。eaccelerator.shm_size="32"
eaccelerator.cache_dir="/www/tmp/eaccelerator"
eaccelerator.enable="1"
eaccelerator.optimizer="1"
eaccelerator.check_mtime="0"
eaccelerator.debug="0"
eaccelerator.filter=""
eaccelerator.shm_max="0"
eaccelerator.shm_ttl="3600"
eaccelerator.shm_prune_period="1"
eaccelerator.shm_only="0"
eaccelerator.compress="1"
eaccelerator.compress_level="9"
eaccelerator.keys = "shm_and_disk"
eaccelerator.sessions = "shm_and_disk"
eaccelerator.content = "shm_and_disk"
MySQL 5.0.24
由于我无法管理许多运行脚本的编码方式,我减少了所有 MySQL 连接超时时间以避免拥塞。然后,我增加了 MySQL 的同时连接数,因为我们遇到了很多 "Too many connection" 错误消息。wait_timeout=6
connect_timeout=5
interactive_timeout=120
max_connections = 500
max_user_connections = 500
现在我们更改 MySQL 配置中最敏感的部分:内存使用。这很敏感,因为错误的值会显著降低服务器性能并导致大量交换。经过一些测试后,我将表缓存和键缓冲区缓存减少到 256MB。实际上,我们并没有那么多可用内存,因为我们为HTTPD分配了 600MB,而且还有很多其他服务在运行。我尝试将它设置得更高一些,希望交换不会太大,但事实上,由于我们的 I/O 负载,交换对 MySQL 来说完全不是好事 :-)如果你使用 MYISAM 表,我建议你使用 "concurrent_insert=2",这在很多情况下会显著提高服务器性能。MYISAM 使用表锁,启用并发插入后,引擎有时会绕过锁,允许 INSERT 和 SELECT 并发运行。我们还禁用了所有未使用的引擎(innodb, bdb)。请参考 MySQL 文档 进行更好的调优。join_buffer_size=1M
sort_buffer_size=1M
read_buffer_size=1M
read_rnd_buffer_size=1M
table_cache=256M
max_allowed_packet=4M
key_buffer=256M
key_buffer_size=256M
thread_cache=256M
thread_concurrency=2
thread_cache_size=40
thread_stack=128K
concurrent_insert=2
query_cache_limit=1M
query_cache_size=256M
query_cache_type=1
skip-bdb
skip-innodb
Linux Kernel 2.6.18
这是调优中最敏感的部分之一。我们将尝试根据服务器负载调整 Linux 内核行为,以节省内存并避免过多交换。此外,由于我们已经在上面做了很多工作,我们需要管理更多的 TCP 连接并正确应对高峰。我们将使用 sysctl 命令来更新这些值。# 显示变量或变量组的值sysctl [-n] [-e] variable ...# 为指定变量设置新值sysctl [-n] [-e] [-q] -w variable=value ...# 显示所有变量sysctl [-n] [-e] -a# 加载 sysctl 配置文件sysctl [-n] [-e] [-q] -p (default /etc/sysctl.conf)
对于我们的测试,我们将创建一个测试配置文件 "/etc/sysctl.conf.testing",并使用以下命令行加载它:sysctl -p /etc/sysctl.conf.testing
当你对更改感到满意时,可以将文件重命名为 "/etc/sysctl.conf"。所有的 sysctl 变量都在内核源代码中有所记录。我建议你下载与你内核版本对应的文档,并在决定更改某些值之前仔细阅读。Security Focus 上有一篇非常好的文章,提供了一些减少SYN ATTACK/SYN SPOOFING影响的关键点。为此,我们激活了 syncookies 和路由验证。net.ipv4.conf.default.rp_filter=1
net.ipv4.tcp_syncookies=1
net.ipv4.tcp_synack_retries=3
net.ipv4.tcp_syn_retries=3
由于我们遇到了一些交换问题,一个重要的事情是更改vm.swappiness的值,其默认值为 60。这个变量控制内核应该偏向于交换出应用程序的程度,其值范围为 0 到 100。我将其设置为 10 以最小化交换。vm.swappiness=10
我们增加了最大积压队列以支持更多的 TCP 流量,并将拥塞控制算法更改为 BIC。Linux 内核支持许多拥塞控制算法,如 Reno(默认)、htcp、vegas、westwood 等。net.core.netdev_max_backlog=2500 # 接口缓冲
net.ipv4.tcp_max_syn_backlog=4096
net.core.somaxconn=1024 # socket listen() 积压队列限制。默认是 128。
net.ipv4.tcp_congestion_control=bic
为了避免大的 TCP 队列以及由此带来的、用于非活跃连接的内存使用,我减少了一些 TCP 超时时间,并强制内核快速回收 TCP 连接。我们不缓存ssthresh(慢启动阈值)的值,以避免影响给定主机,使其所有后续连接都使用降低的 ssthresh。net.ipv4.tcp_keepalive_time=900
net.ipv4.tcp_fin_timeout=30
net.ipv4.tcp_max_orphans=16384
net.ipv4.tcp_tw_reuse=1
net.ipv4.tcp_tw_recycle=1
net.ipv4.tcp_rfc1337=1
net.ipv4.tcp_no_metrics_save=1
对你所使用的链路来说,使用最佳的 SEND 和 RECEIVE 套接字缓冲区大小至关重要。在我们的案例中,我们使用的是 100Mbits 链路连接。因此,为了获得更好的 TCP 连接和拥塞控制,我们不得不增加 TCP 缓冲区。你可以在此了解更多信息。net.core.rmem_max=16777216
net.core.wmem_max=16777216
net.ipv4.tcp_rmem=4096 87380 16777216
net.ipv4.tcp_wmem=4096 65536 16777216
现在,这台服务器能够支撑两倍的流量负载。技术层面已成为我们流量增长的瓶颈。还有很多其他的调优可以提高性能(如 I/O 和磁盘访问、其他内核选项、编译新内核、使用 Apache Worker 等)。这篇文章只是关于如何调优服务器的一些线索。需要记住的重要一点是,无论你在服务器上做了多少调优,如果上面运行的是糟糕的应用程序代码,那一切都永远不够![1] http://www.dotclear.net/[2] http://www.phpads.org/[3] http://httpd.apache.org/docs/2.2/[4] http://eaccelerator.net/[5] http://dev.mysql.com/doc/refman/5.0/en/[6] https://www.kernel.org/doc/man-pages/[7] http://www.securityfocus.com/[8] https://en.wikipedia.org/wiki/BIC_TCP[9] https://www.ibm.com/developerworks/linux/library/l-hw1/index.html