米扑博客采用的Apache服务器,搭建在阿里云上,最近总是出现访问慢,甚至无法访问。

重启Apache服务后,才可以正常访问,初步原因是米扑博客的访客增量过多,百度、谷歌、Bing等爬虫抓取压力过大。

统计访客IP和PV数量,发现跟平时差不多,甚至还略低(米扑博客服务器无法访问,造成访客访问不了),服务器应该不至于响应这么慢,从而需要针对这个问题进行分析,来解决网站访问过慢。

 

原因分析

1、发现网站访问变慢,使用 top 命令查看了服务器的负载情况,发现负载并不高,初步判断不是程序的问题。

top - 13:20:06 up  2:22,  3 users,  load average: 1.08, 1.43, 1.27
Tasks: 137 total,   1 running, 134 sleeping,   0 stopped,   2 zombie
Cpu(s): 47.7%us,  2.0%sy,  0.0%ni, 50.3%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Mem:   1920944k total,  1738316k used,   182628k free,    38040k buffers
Swap:  4095984k total,    11892k used,  4084092k free,   264372k cached

  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND                                                                                     
 3393 mysql     20   0 1455m 833m 6972 S  4.0 44.4   3:21.95 mysqld                                                                                       
23728 apache    20   0 1139m  59m  26m S  0.0  3.2   0:31.30 httpd                                                                                        
24891 apache    20   0 1140m  57m  23m S  0.0  3.0   0:01.02 httpd                                                                                        
23922 apache    20   0 1139m  57m  24m S  0.0  3.0   0:20.55 httpd                                                                                        
23975 apache    20   0 1141m  56m  23m S  0.0  3.0   0:26.89 httpd                                                                                        
24058 apache    20   0 1138m  55m  23m S  0.0  3.0   0:19.04 httpd                                                                                        
24004 apache    20   0 1140m  55m  21m S  0.0  2.9   0:14.15 httpd                                                                                        
24390 apache    20   0 1139m  54m  21m S 24.6  2.9   0:18.93 httpd                                                                                        
23947 apache    20   0 1138m  53m  21m S  0.0  2.9   0:26.20 httpd                                                                                        
22835 apache    20   0 1138m  53m  21m S 19.6  2.9   0:49.06 httpd                                                                                        
24396 apache    20   0 1133m  47m  21m S  0.0  2.5   0:05.40 httpd                                                                                        
24155 apache    20   0 1130m  47m  23m S  0.0  2.5   0:10.82 httpd                                                                                        
24608 apache    20   0 1129m  44m  21m S  0.0  2.4   0:09.03 httpd

 

2、查看 httpd 进程数量

ps -ef | grep httpd | wc -l

查看结果

# ps -ef | grep httpd | grep -v grep | wc -l
13
# ps -ef | grep httpd | grep -v grep 
root     25024     1  0 13:20 ?        00:00:00 /usr/sbin/httpd
apache   25026 25024  1 13:20 ?        00:00:00 /usr/sbin/httpd
apache   25027 25024 14 13:20 ?        00:00:07 /usr/sbin/httpd
apache   25028 25024  2 13:20 ?        00:00:01 /usr/sbin/httpd
apache   25029 25024  5 13:20 ?        00:00:02 /usr/sbin/httpd
apache   25032 25024  7 13:20 ?        00:00:03 /usr/sbin/httpd
apache   25034 25024  0 13:20 ?        00:00:00 /usr/sbin/httpd
apache   25035 25024  9 13:20 ?        00:00:04 /usr/sbin/httpd
apache   25037 25024  4 13:20 ?        00:00:02 /usr/sbin/httpd
apache   25038 25024  9 13:20 ?        00:00:04 /usr/sbin/httpd
apache   25039 25024  5 13:20 ?        00:00:02 /usr/sbin/httpd
apache   25040 25024  0 13:20 ?        00:00:00 /usr/sbin/httpd
apache   25051 25024  1 13:20 ?        00:00:00 /usr/sbin/httpd

上面数据发现,线程数已经达到了 apache 设置的最大值,由此断定是网站访问人数过多造成了访问过慢。

若想查看线程数量,可以执行命令

# ps -efL | grep -v grep | grep httpd

查看结果

# ps -efL | grep -v grep | grep httpd
root      1347     1  1347  0    1 Aug18 ?        00:00:27 /usr/local/httpd/bin/httpd -k start
apache   26672  1347 26672  0    1 20:24 ?        00:00:00 /usr/local/httpd/bin/httpd -k start
apache   26673  1347 26673  0   44 20:24 ?        00:00:00 /usr/local/httpd/bin/httpd -k start
apache   26673  1347 26675  0   44 20:24 ?        00:00:00 /usr/local/httpd/bin/httpd -k start
apache   26673  1347 26676  0   44 20:24 ?        00:00:00 /usr/local/httpd/bin/httpd -k start
apache   26673  1347 26677  0   44 20:24 ?        00:00:00 /usr/local/httpd/bin/httpd -k start
apache   26673  1347 26678  0   44 20:24 ?        00:00:00 /usr/local/httpd/bin/httpd -k start
apache   26673  1347 26679  0   44 20:24 ?        00:00:00 /usr/local/httpd/bin/httpd -k start
apache   26673  1347 26680  0   44 20:24 ?        00:00:00 /usr/local/httpd/bin/httpd -k start
apache   26673  1347 26681  0   44 20:24 ?        00:00:00 /usr/local/httpd/bin/httpd -k start
apache   26673  1347 26682  0   44 20:24 ?        00:00:00 /usr/local/httpd/bin/httpd -k start
.....

提前剧透,若Apache 采用 perfork 模式则无线程(只有进程工作模式),若以 worker 和 event 模式 则有线程(进程 + 线程 工作模式)

 

3、查看了服务器连接数和当前的建立连接数

1)查看连接数

netstat -ant | grep -E ":80|:443"

结果如下:

# netstat -ant | grep -E ":80" | wc -l     
95
# netstat -ant | grep -E ":443" | wc -l  
19
# netstat -ant | grep -E ":80|:443" | wc -l 
93
# netstat -ant | grep -E ":80|:443" | tail
tcp        0      0 115.29.237.28:36458         111.13.101.139:80           TIME_WAIT   
tcp      340      0 115.29.237.28:443           111.206.221.28:57598        ESTABLISHED 
tcp        0      0 115.29.237.28:36360         111.13.101.139:80           TIME_WAIT   
tcp        0      0 115.29.237.28:36317         111.13.101.139:80           TIME_WAIT   
tcp        0      0 115.29.237.28:55044         124.65.195.162:8080         TIME_WAIT   
tcp        0      0 115.29.237.28:50159         180.149.132.151:80          TIME_WAIT   
tcp        0      0 115.29.237.28:443           123.125.71.52:21474         ESTABLISHED 
tcp        0      0 115.29.237.28:36374         111.13.101.139:80           TIME_WAIT   
tcp        0      0 115.29.237.28:443           183.27.179.242:32514        FIN_WAIT2   
tcp        0      0 115.29.237.28:60051         180.149.131.98:80           TIME_WAIT

 

2)查看建立连接数

netstat -ant | grep ESTABLISHED | grep -E ":80|:443"

结果如下:

# netstat -ant | grep ESTABLISHED | grep -E ":80" | wc -l     
6
# netstat -ant | grep ESTABLISHED | grep -E ":443" | wc -l    
6
# netstat -ant | grep ESTABLISHED | grep -E ":80|:443" | wc -l 
13
# netstat -ant | grep ESTABLISHED | grep -E ":80|:443" 
tcp        0      0 115.29.237.28:443           199.30.25.63:6419           ESTABLISHED 
tcp        0      0 115.29.237.28:38044         140.205.140.205:80          ESTABLISHED 
tcp        0      0 115.29.237.28:443           220.181.108.113:11102       ESTABLISHED 
tcp        0      0 115.29.237.28:443           111.206.221.33:49204        ESTABLISHED 
tcp        0      0 115.29.237.28:50808         180.149.132.151:80          ESTABLISHED 
tcp        0      0 115.29.237.28:58839         119.36.92.42:80             ESTABLISHED 
tcp        0      0 115.29.237.28:53981         36.42.32.216:8080           ESTABLISHED 
tcp        0      0 115.29.237.28:443           95.108.181.81:43824         ESTABLISHED 
tcp        0      0 115.29.237.28:80            173.212.254.158:55468       ESTABLISHED 
tcp        0      0 115.29.237.28:443           199.30.24.54:40307          ESTABLISHED 
tcp        0      0 115.29.237.28:443           173.212.254.158:46540       ESTABLISHED 
tcp        0      0 115.29.237.28:52123         179.176.118.31:8080         ESTABLISHED

发现连接数特别多,远远超过了米扑博客设置的Apache服务器允许的估计值。

 

4、熟悉 apache 服务器的多路处理器​ MPM 配置

MPM是Apache的核心,它的作用是管理进程数量、进程销毁、线程管理、最大处理请求数、调度请求等。

Apache2.0中MPM分为3种:perfork、worker、event

perfork 从Apache1.3中继承下来的,它采用的是进程管理方式,prefork 本身并没有使用线程,apache 2.0 版本使用它是为了与1.3版保持兼容性;perfork用单独的子进程来处理不同的请求,进程之间是彼此独立的,这也使其成为最稳定的MPM之一,所以它可以提供更可靠的性能和更好的兼容性;

worker 是Apache2.0中新增加的方式,它采用了线程控制方法,可以比perfork更节约系统开销、处理更多的请求量,但同时兼容性并不是很好,很多旧的程序无法工作在worker下;

event 在 apache2.2 版本中,仍处于试验阶段,它为每个任务分配不同的进程池和工作线程。在 apache2.4 版本中,已经把 event MPM纳入正式版,不再是实验状态,安装时 apache2.4 会默认将event MPM一起安装进去了,并默认采用 event 模式,但对于老的CPU可能是不支持的。

通过命令 httpd -l 可以获取目前Apache采用的是哪种 MPM

例如米扑博客 的Apache 服务器,采用的就是 prefork.c

# httpd -l
Compiled in modules:
  core.c
  prefork.c
  http_core.c
  mod_so.c

也可以通过 apachectl -V | grep -i mpm 查看当前的 MPM

# apachectl -V | grep -i mpm
Server MPM:     Prefork
 -D APACHE_MPM_DIR="server/mpm/prefork"

以上是 Apache 2.2.x 以前的配置,若已经升级到了 Apache 2.4.x ,则 MPM 大多采用了 worker 或 event 模式

# httpd -l
Compiled in modules:
  core.c
  mod_so.c
  http_core.c
  event.c
# apachectl -V | grep -i mpm
Server MPM:     event

 

 

米扑博客本文主要介绍的Apache MPM 配置,包括 prefork worker 、event 等三种模式的配置

apache2 三种模式

从2.0开始,apache引入了MPM(Multi-Processing Module,多进程处理模块),参见米扑博客:Apache 工作的三种模式:Prefork、Worker、Event

MPM有 prefork worker 、event ,在2.4版本中三种模式拥有不同的特点和性能,它们代表了Apache的演变和发展

在apache的早期2.0版本是prefork,2.2版本是worker,2.4版本是event。

在configure配置编译参数的时候,可以使用--with-mpm=prefork|worker|event 来指定编译为哪一种MPM,也可以用编译为三种都支持:--enable-mpms-shared=all,这样在编译的时候会在modules目录下自动编译出三个MPM文件的so,然后通过修改httpd.conf配置文件更改MPM

1、prefork MPM

prefork是比较古老而又稳定的apache模式,特点是进程单线程模式,在一个时间点一个进程对应一个线程只能处理一个连接,因此需要启动大量的进程来处理高并发的请求。

Prefork MPM实现了一个非线程的、预派生的web服务器。它在Apache启动之初,就先预派生一些子进程,然后等待连接;可以减少频繁创建和销毁进程的开销,每个子进程只有一个线程,在一个时间点内,只能处理一个请求。这是一个成熟稳定,可以兼容新老模块,由于是单线程进程,因而无须考虑线程安全的问题,可以使用非线程安全的库,例如 mod_php,但是一个进程相对更占用资源,消耗大量内存,并发支持受限于进程数量,对高并发支持稍差不擅长处理高并发的场景。

 

2、worker MPM

worker同样使用多个进程,但每个进程又拥有多个线程,每个线程处理一个连接,属于多进程 + 多线程模式

worker模式同样会先预派生一些子进程,然后每个子进程创建一些线程,同时包括一个监听线程,每个请求过来会被分配到一个线程来服务。线程比起进程会更轻量,因为线程是通过共享父进程的内存空间,因此,内存的占用会减少一些,在高并发的场景下会比prefork有更多可用的线程,表现会更优秀一些;另外,如果一个线程出现了问题也会导致同一进程下的线程出现问题,如果是多个线程出现问题,也只是影响Apache的一部分,而不是全部。由于用到多进程多线程,需要考虑到线程的安全了,在使用keep-alive长连接的时候,某个线程会一直被占用,即使中间没有请求,需要等待到超时才会被释放(该问题在prefork模式下也存在)。

由于线程是轻量级的,因而具有较高的并发性,同时,多个进程又获得了一定的稳定性。

worker模式特点是占用内存少,并发性比较高,缺点是必须考虑线程安全。

如果使用了keep-alive方式,一个线程可能会被一直保持一个连接,但中间没有请求,直到超时。

如果有多个线程被这样占据,在高并发场景下同样会出现无线程可用的情形。

3、event MPM

event模式是Apache最新的工作模式,它和worker模式很像,不同的是在于它解决了keep-alive长连接的时候占用线程资源被浪费的问题,在event工作模式中,会有一些专门的线程用来管理这些keep-alive类型的线程,当有真实请求过来的时候,将请求传递给服务器的线程,执行完毕后,又允许它释放,这增强了在高并发场景下的请求处理。

event模式是在2.4版本中才稳定发布的模式,它在worker的基础上,解决了keep-alive连接不能释放的问题,属于多进程 + 多线程模式

event MPM中,会有一个专门的线程来管理这些keep-alive类型的线程,当有真实请求过来的时候,将请求传递给服务线程,执行完毕后,又允许它释放。

这样增强了高并发场景下的请求处理能力。

 

MPM 配置参数详解

1) prefork 模式

默认工作环境 apache 2.2.x,以 prefork 模式工作的默认配置:

vim /etc/httpd/conf/httpd.conf

默认配置和自定义配置:

# prefork MPM
# StartServers: number of server processes to start
# MinSpareServers: minimum number of server processes which are kept spare
# MaxSpareServers: maximum number of server processes which are kept spare
# ServerLimit: maximum value for MaxClients for the lifetime of the server
# MaxClients: maximum number of server processes allowed to start
# MaxRequestsPerChild: maximum number of requests a server process serves
<IfModule prefork.c>
#    StartServers       4
#    MinSpareServers    4
#    MaxSpareServers   20
#    ServerLimit      128
#    MaxClients       100
#    MaxRequestsPerChild  2000
    StartServers       10		# 初始化进程数
    MinSpareServers    10		# 最小空闲进程数,少于这个值就新建进程达到此值
    MaxSpareServers    20		# 最多空闲进程数,多余这个值就销毁多余的进程数
    ServerLimit        64		# 设置同时处理请求的最大阈值,MaxClients不得超过此值,最大为20000
    MaxClients         64		# 同时可以处理的最大请求数,影响性能的核心参数
    MaxRequestsPerChild  1000	# 每个进程可以处理的最大连接数,达到了此值后,自动销毁此进程,释放内存
</IfModule>

如上,#注释掉的是默认配置,没注释的是自定义配置,如上运行默认会创建11个httpd进程,其中1个root父进程 + 10个子进程,最多进程不超过21个

 

apache 2.4.x 中 prefork 模式的参数如下,仅供参考对比,其不是  apache 2.4.x 工作模式,因此不讲解。apache 2.4.x 工作模式是 event,下文会重点讲解

# prefork MPM
# StartServers: number of server processes to start
# MinSpareServers: minimum number of server processes which are kept spare
# MaxSpareServers: maximum number of server processes which are kept spare
# MaxRequestWorkers: maximum number of server processes allowed to start
# MaxConnectionsPerChild: maximum number of connections a server process serves before terminating
<IfModule mpm_prefork_module>
#    StartServers             5
#    MinSpareServers          5
#    MaxSpareServers         10
#    MaxRequestWorkers      250
#    MaxConnectionsPerChild   0
    StartServers             10		# 初始化进程数
    MinSpareServers          10		# 最小空闲进程数,少于这个值就新建进程达到此值
    MaxSpareServers          20		# 最多空闲进程数,多余这个值就销毁多余的进程数
    ServerLimit        	  20000		# 默认配置没有此值,可以自己添加,并设置为上限20000
    MaxRequestWorkers       250		# 同时可以处理的最大请求数,影响性能的核心参数
    MaxConnectionsPerChild  500		# 每个进程可以处理的最大连接数,达到了此值后,自动销毁此进程,释放内存
</IfModule>

注明:

MaxClients 和 MaxRequestWorkers 是同一个意思,都表示同时处理请求的最大进程数量(maximum number of server processes allowed to start)

MaxRequestsPerChild 和 MaxConnectionsPerChild 是同一个意思,都表示处理多少个请求数后会自动销毁释放内存(maximum number of connections a server process serves before terminating)

 

如何确定合适的 MaxClients(MaxRequestWorkers)呢?

在apache2.3.13以前的版本MaxRequestWorkers被称为MaxClients ,两者是同一个参数。

MaxClients(MaxRequestWorkers)是设置同时处理的最大请求数,任何连接在超过MaxRequestWorkers上限时都将被排队,导致服务器处理性能下降,此为核心参数。

prefork模式下,影响并发性能最重要的参数就是 MaxClients(MaxRequestWorkers),它决定了apache的并发处理能力。但是这个参数不是越大越好,因为如果超出了系统硬件能力,机器会卡死。

首先,通过top命令查看apache进程占用的资源,主要看%CPU和%MEM这两个指标,例如,每个进程的CPU占用率不超过1%,每个进程的内存占用率不超过2%,考虑内存限制,比较合适的apache进程数量为50个。

然后,逐步测试最大值。通过观测得来的CPU和内存的指标有一定的误差,一般可以适当调节这个数值,例如调到1.5或者2倍,再通过峰值场景下的机器是否卡顿来判断是继续上调还是下调。例如内存占比不超过2%,那么最多可以创建 apache 进程数量为 50个,适当调节 50 / 1.5 = 33 个左右,一般是经验值,需要反复测试验证。

 

apache 2.2.x 简单示例,详细参数解释如下:

<IfModule prefork.c> 
	ServerLimit 	256		# 设置同时处理请求的最大阈值,MaxClients不得超过此值,最大为20000
	StartServers   	5		# 指定服务器启动时建立的子进程数量 
	MinSpareServers	10		# 指定空闲子进程的最小数量 
	MaxSpareServers	20		# 指定空闲子进程的最大数量 
	MaxClients   	150		# 客户端最大接入请求的数量(单个进程并发线程数),任何超过该限制的请求都将进入等候队列
	MaxRequestsPerChild	0	# 指定每个子进程在其生存周期内允许伺服的最大请求数量,达到后销毁本进程释放内存,默认为10000,0表示子进程永远不结束 
</IfModule>

如上,当 apache 2.2.x 被启动时,自动会采用 prefork 控制进程在最初建立 StartServers = 5个子进程后,为了满足 MinSpareServers 设置的需要创建一个进程,等待一秒钟,继续创建两个,再等待一秒钟,继续创建四个……如此按指数级增加创建的进程数,最多达到每秒32个,直到满足 MinSpareServers 设置的值为止。这种模式可以不必在请求到来时再产生新的进程,从而减小了系统开销以增加性能。

MaxSpareServers 设置了最大的空闲进程数,如果空闲进程数大于这个值,Apache会自动kill掉一些多余进程。这个值不要设得过大,但如果设的值比 MinSpareServers 还小,Apache会自动把其调整为 MinSpareServers+1。如果站点负载较大,可考虑同时加大MinSpareServers和MaxSpareServers

MaxClients 是这些指令中最为重要的一个,设定的是 Apache可以同时处理请求的最大进程数量,其是对Apache性能影响最大的参数。

MaxClients 缺省值150,一般是远远不够的(注:可以修改此值但最大不能大于256),如果请求总数已达到这个值(可通过 ps -ef | grep httpd | wc -l 来查看),那么后面的请求就要排队,直到某个已处理请求完毕。这就是系统资源还剩下很多而HTTP访问却很慢的主要原因,因为处理请求的进程数已经达到了上限,超过150个请求大多需要等待排队了,显得很慢。

虽然理论上 MaxClients 这个值越大,可以处理的请求就越多,但Apache默认的限制 MaxClients 不能大于256

在 apache2 中通过ServerLimit指令无须重编译Apache就可以加大MaxClients,此时必须 MaxClients ≤  ServerLimit  ≤ 20000

其中 ServerLimit 用于限制最大进程数量(perfork是单进程单线程模式), ServerLimit 必须大于等于 MaxClients 数量,否则报错

MaxRequestsPerChild 用来控制每个进程在处理了多少次请求之后自动销毁,这个参数可以设置为0表示无限,即不销毁进程;若为3000则表示处理了3000个请求后自己销毁本进程,释放掉进程占用的内存。

虽然通过设置ServerLimit,可以把MaxClients加得很大,但是往往会适得其反,系统耗光所有内存。以一台服务器为例:内存2G,每个apache进程消耗大约3%(可通过ps aux来确认,如上面的top命令结果)的内存,也就是60M,这样理论上这台服务器最多跑 2000 / 60M = 30多个apache进程就会耗光系统所有内存,所以,设置MaxClients要慎重。

使用场景:

周末晚上,在访问量高峰期,经常会出现突然之间发生非常多的并发连接(ps -ef | grep httpd | wc -l),然后突然之间减少了很多访问。如果Apache没有准备足够数量的预备进程,那访问只能等待Apache每秒1个的新增进程,随后又要将多余的进程删除,那Apache只能一直忙于新建和销毁进程,大大地降低了访问速度。可以适当的提前增加 StartServers、MinSpareServers、MaxSpareServers 来使得Apache不需要一直忙于作无用功。

最后,推荐MaxRequestsPerChild不要设置为0,强烈推荐设置为非0,可以保护Apache进程免遭内存泄漏的影响,因为你不知道运行在Apache上的应用程式在什么时候会出错导致内存泄漏。

米扑推荐的 perfork 模式优化设置如下:

<IfModule prefork.c>
#    StartServers       4
#    MinSpareServers    4
#    MaxSpareServers   20
#    ServerLimit      128
#    MaxClients       100
#    MaxRequestsPerChild  2000
    StartServers       10   
    MinSpareServers    10   
    MaxSpareServers    20   
    ServerLimit       128  
    MaxClients        128  
    MaxRequestsPerChild  1000 
</IfModule>

参数解释:

StartServers 10  启动时初始化10个进程

MinSpareServers 10 和 MaxSpareServers 20 表示启动后,实时空闲的子进程数量在 10 - 20 之间,低于10个进程则新创建,高于20个进程则销毁

ServerLimit 64 表示最大可以创建的进程数量,其只是一个限制,最大不超过 20000,没有实际作用,但决定着 ServerLimit >= MaxClients

MaxClients 64  表示最大同时可以处理的请求数,因为 perfork 是单进程单线程模式,因为最大可创建 64个进程同时处理64个请求

MaxRequestsPerChild 1000 表示一个进程累计处理了 1000个请求数,会自动销毁,释放掉内存,若再需要进程来处理则新创建一个进程

查看启动后的进程数量:

# ps -ef | grep -v grep | grep httpd | wc -l
11
# ps -ef | grep -v grep | grep httpd 
root      7451     1  0 10:48 ?        00:00:00 /usr/sbin/httpd
apache    7453  7451  0 10:48 ?        00:00:00 /usr/sbin/httpd
apache    7454  7451  0 10:48 ?        00:00:00 /usr/sbin/httpd
apache    7455  7451  0 10:48 ?        00:00:00 /usr/sbin/httpd
apache    7456  7451  0 10:48 ?        00:00:00 /usr/sbin/httpd
apache    7457  7451  0 10:48 ?        00:00:00 /usr/sbin/httpd
apache    7458  7451  0 10:48 ?        00:00:00 /usr/sbin/httpd
apache    7459  7451  0 10:48 ?        00:00:00 /usr/sbin/httpd
apache    7460  7451  0 10:48 ?        00:00:00 /usr/sbin/httpd
apache    7461  7451  0 10:48 ?        00:00:00 /usr/sbin/httpd
apache    7462  7451  0 10:48 ?        00:00:00 /usr/sbin/httpd

如上可见,启动后创建了11个进程,包含1个主进程(root)和10个子进程(apache)

查看每个进程资源消耗:top 

  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND                                                            
 7451 root      20   0 1116m  30m  20m S  0.0  1.6   0:00.09 httpd                                                                
 7453 apache    20   0 1122m  45m  29m S  0.0  2.4   0:00.94 httpd                                                                  
 7454 apache    20   0 1116m  10m  796 S  0.0  0.6   0:00.00 httpd                                                               
 7461 apache    20   0 1116m  10m  796 S  0.0  0.6   0:00.00 httpd                                                               
 7462 apache    20   0 1116m  10m  796 S  0.0  0.6   0:00.00 httpd                                                               
 7523 apache    20   0 1116m  10m  796 S  0.0  0.6   0:00.00 httpd                                                               
 7455 apache    20   0 1116m  10m  792 S  0.0  0.6   0:00.00 httpd                                                               
 7456 apache    20   0 1116m  10m  792 S  0.0  0.6   0:00.00 httpd                                                               
 7457 apache    20   0 1116m  10m  792 S  0.0  0.6   0:00.00 httpd                                                               
 7458 apache    20   0 1116m  10m  792 S  0.0  0.6   0:00.00 httpd                                                               
 7459 apache    20   0 1116m  10m  792 S  0.0  0.6   0:00.00 httpd                                                               
 7460 apache    20   0 1116m  10m  792 S  0.0  0.6   0:00.00 httpd

如上可见,已有12个进程,包含1个主进程(root)和11个子进程(apache),pid = 7523 是动态新创建的子进程

仔细查看每一个子进程占用的CPU、内存资源,发现都差不多大小,说明每一个子进程负载都很均衡,非常赞

这里为什么重点说每一个子进程所耗资源都很均衡,因为 perfork 和 worker、event的子进程所耗资源很不一样

 

 

2)worker 模式

apache 2.x.x 以上都支持 worker 模式,

apache 2.2.x 以 worker 模式工作默认配置为:

# worker MPM
# StartServers: initial number of server processes to start
# MaxClients: maximum number of simultaneous client connections
# MinSpareThreads: minimum number of worker threads which are kept spare
# MaxSpareThreads: maximum number of worker threads which are kept spare
# ThreadsPerChild: constant number of worker threads in each server process
# MaxRequestsPerChild: maximum number of requests a server process serves
<IfModule worker.c>
#    StartServers         2
#    MaxClients         150
#    MinSpareThreads     25
#    MaxSpareThreads     75
#    ThreadsPerChild     25
#    MaxRequestsPerChild 0
    StartServers        10
    MaxClients         150
    MinSpareThreads     25
    MaxSpareThreads     75
    ThreadsPerChild     25
    MaxRequestsPerChild 1000
</IfModule>

apache 2.4.x 以 worker 模式工作默认配置为:

# worker MPM
# StartServers: initial number of server processes to start
# MinSpareThreads: minimum number of worker threads which are kept spare
# MaxSpareThreads: maximum number of worker threads which are kept spare
# ThreadsPerChild: constant number of worker threads in each server process
# MaxRequestWorkers: maximum number of worker threads
# MaxConnectionsPerChild: maximum number of connections a server process serves
#                         before terminating
<IfModule mpm_worker_module>
#    StartServers             3
#    MinSpareThreads         75
#    MaxSpareThreads        250 
#    ThreadsPerChild         25
#    MaxRequestWorkers      400
#    MaxConnectionsPerChild   0
    StartServers             10  
    MinSpareThreads          75  
    MaxSpareThreads         250 
    ThreadsPerChild          25  
    MaxRequestWorkers       400 
    MaxConnectionsPerChild 1000
</IfModule>

 

配置示例介绍参数:

<IfModule mpm_worker_module>
    StartServers         3        # 启动时子进程数
    MinSpareThreads      25       # 最小空闲线程数
    MaxSpareThreads      75       # 最大空闲线程数
    ServerLimit          64       # 默认没有此配置,可以自己添加,子进程的开启最大上线,默认为16,最大为20000
    ThreadLimit          64       # 默认没有此配置,可以自己添加,每个进程启动的线程数,设置后即为固定值
    ThreadsPerChild      25       # 每个进程可以启动的线程数量,不得大于 ThreadLimit 限制值
    MaxRequestWorkers    400      # 同时处理的最大请求数,即全部工作线程数量的最大值
    MaxConnectionsPerChild   0    # 最大连接数限制,超过此值后自动销毁进程,0表示永远不销毁此进程(不推荐)
</IfModule>

worker 配置文件和前面的prefork有一些变动,但是基本的概念差不多。原来以进程为单位的概念转变成了以线程为单位的概念,例如 MinSpareServers转变为MinSpareThreads,多了一个ThreadsPerChild,表示每个进程可以有的线程数量,其上限值为ThreadLimit。注意,修改了ThreadsPerChild后可以通过apache2 restart来生效,但是修改了ThreadLimit只能先stop,然后start,才能使 ThreadLimit 生效

在worker模式中,MaxRequestWorkers转变为了线程数量,比起prefork模式下可以有比较大的增长,通常可以有好几倍的差别。这是worker模式相比于prefork的优势,worker拥有比较多的处理线程,可以同时保持大量的连接,峰值应对能力比较强。尽管出于CPU或者数据库的原因,这些连接不能在第一时间内被处理,可能产生较大延迟。

 

Worker 由主控制进程生成“StartServers”个子进程,每个子进程中包含固定的 ThreadsPerChild 线程数,各个线程独立地处理请求。

因此,总线程数量 = 子进程数 * 每个进程中的线程数 = StartServers * ThreadsPerChild = 10 * 25 = 250 个线程数量,不得高于 MaxSpareThreads = 250,也不得低于 MinSpareThreads = 75

同样,为了不在请求到来时再生成线程, MinSpareThreads MaxSpareThreads 设置了最少和最多的空闲线程数;

apache 2.2.x 版本中,MaxClients 设置了同时连入的clients最大总数。如果现有子进程中的线程总数不能满足负载,控制进程将派生新的子进程。

MinSpareThreads 和 MaxSpareThreads的最大缺省值分别是75和250,这两个参数对Apache的性能影响并不大,可以按照实际情况相应调节。

ThreadsPerChild 是worker MPM中与性能相关最密切的指令,每个子进程产生多少个线程数量,以来处理请求数。

ThreadsPerChild的最大缺省值是64,如果负载较大,64也是不够的。这时要显式使用 ThreadLimit指令,它的最大缺省值是20000,ThreadsPerChild最大不得超过 ThreadLimit 上限的20000

这里需要注意,此处的 ThreadLimit 表示最大线程上限,不同于 perfork 模式里的 ServerLimit,ServerLimit是指最大的进程上限

Worker 模式下所能同时处理的请求总数是由子进程总数乘以ThreadsPerChild 值决定的,应该大于等于MaxClients(MaxRequestWorkers)

处理请求总数 = 子进程总数 * ThreadsPerChild  >= MaxClients(MaxRequestWorkers)

如果负载很大,现有的子进程数不能满足时,控制进程会派生新的子进程,worker模式默认最大的子进程总数是16,加大时也需要显式声明ServerLimit,最大值是20000

需要注意的是,如果显式声明了ServerLimit,那么它乘以 ThreadsPerChild的值必须大于等于MaxClients(MaxRequestWorkers),而且MaxClients必须是ThreadsPerChild的整数倍,否则 Apache将会自动调节到一个相应值。

例如:

ServerLimit = 64,ThreadsPerChild = 25,最大工作线程数为 ServerLimit * ThreadsPerChild = 64 * 25 = 1600 > 400(MaxRequestWorkers)

MaxRequestWorkers = 400,ThreadsPerChild = 25,整数倍为 MaxRequestWorkers / ThreadsPerChild = 400 / 25 = 16(倍)

 

 

3)event 模式

apache 2.2.x 版本中没有 event 模式,apache 2.4.x 版本中才有 event 模式

vim conf/extra/httpd-mpm.conf

默认配置和自定义配置:

# event MPM
# StartServers: initial number of server processes to start
# MinSpareThreads: minimum number of worker threads which are kept spare
# MaxSpareThreads: maximum number of worker threads which are kept spare
# ThreadsPerChild: constant number of worker threads in each server process
# MaxRequestWorkers: maximum number of worker threads
# MaxConnectionsPerChild: maximum number of connections a server process serves
#                         before terminating
<IfModule mpm_event_module>
#    StartServers             3
#    MinSpareThreads         75
#    MaxSpareThreads        250
#    ThreadsPerChild         25
#    MaxRequestWorkers      400
#    MaxConnectionsPerChild   0
    StartServers              5   	# 启动时子进程数
    MinSpareThreads          75  	# 最小空闲线程数
    MaxSpareThreads         250  	# 最大空闲线程数
    ServerLimit              64  	# 默认没有此配置,可以自己添加,子进程的开启最大上线,默认为16,最大为20000
    ThreadLimit              64   	# 默认没有此配置,可以自己添加,每个子进程启动的线程数,设置后即为固定值
    ThreadsPerChild          10  	# 每个进程可以启动的线程数量,不得大于 ThreadLimit 限制值
    MaxRequestWorkers       100 	# 同时处理的最大请求数,即全部工作线程数量的最大值
    MaxConnectionsPerChild  100 	# 最大连接数限制,超过此值后自动销毁进程,0表示永远不销毁此进程(不推荐设置为0)
</IfModule>

event模式下的参数意义和worker模式完全一样,按照上面的策略来调整即可

event相比于worker的优势是,它解决了worker模式下长连接线程的阻塞问题

值得一提的是,worker/event模式的请求处理模式,已经和nginx的libevent模式相同了。

由于event是明显优于worker的,所以在apache2.4及后续版本中,一般优先选择event模式。

上面的 event 配置里,参数解释如下:

假设我们需要 100 个线程同时处理请求,那么 MaxRequestWorkers = 100

然后,每一个子进程最多开10个线程 ThreadsPerChild  = 10,那么子进程数 MaxRequestWorkers / ThreadsPerChild = 100 / 10 = 10个子进程

接着,最小空闲线程数为 MinSpareThreads 75,那么最小进程数 MinSpareThreads / ThreadsPerChild = 75 / 10 = 7.5 不低于8个子进程

接着,最大空闲线程数为 MaxSpareThreads 250,那么最小进程数 MaxSpareThreads / ThreadsPerChild = 250 / 10 = 25 不高于25个子进程

最后,默认启动进程数 StartServers 5 ,若我们要求同时处理 100 个线程,最低需要 10个子进程,高于初始启动5个子进程,那么到底启动几个进程呢?

# ps -ef | grep -v grep | grep httpd
root     30242     1  0 23:38 ?        00:00:00 /usr/local/httpd/bin/httpd -k start
apache   30243 30242  0 23:38 ?        00:00:00 /usr/local/httpd/bin/httpd -k start
apache   30244 30242  0 23:38 ?        00:00:00 /usr/local/httpd/bin/httpd -k start
apache   30245 30242  0 23:38 ?        00:00:00 /usr/local/httpd/bin/httpd -k start
apache   30268 30242  0 23:38 ?        00:00:00 /usr/local/httpd/bin/httpd -k start
apache   30276 30242 21 23:38 ?        00:00:00 /usr/local/httpd/bin/httpd -k start
apache   30278 30242  0 23:38 ?        00:00:00 /usr/local/httpd/bin/httpd -k start
apache   30330 30242  0 23:38 ?        00:00:00 /usr/local/httpd/bin/httpd -k start
apache   30346 30242  0 23:38 ?        00:00:00 /usr/local/httpd/bin/httpd -k start
apache   30347 30242  0 23:38 ?        00:00:00 /usr/local/httpd/bin/httpd -k start

谁也不用去猜,用结果说话,实际运行是启动了 9 个子进程,以及一个主进程 root (pid = 30242)

为什么是9个子进程呢?因为启动后,首先按照 StartServers 5 初始化的5个子进程后,然后根据 MinSpareThreads 75 最小空闲线程数进程创建子进程,并且1、2、4、8...指数级创建

MinSpareThreads 75,ThreadsPerChild 10,因此最小空闲线程数 75 / 10 = 7.5个,即最低空闲子进程为 8个,因此 Spare 创建子进程数为 1、2 合计3个

初始化的子进程 StartServers = 5 ,再加上 Spare 子进程 3个,合计 5 + 3 = 8 个子进程数,完美解决启动的进程个数。

 

那么,如果把启动进程数 StartServers 10 设置为了10个进程,启动时会开多少个进程呢?

<IfModule mpm_event_module>
#    StartServers             3
#    MinSpareThreads         75
#    MaxSpareThreads        250
#    ThreadsPerChild         25
#    MaxRequestWorkers      400
#    MaxConnectionsPerChild   0
    StartServers             10  
    MinSpareThreads          75  
    MaxSpareThreads         250 
    ServerLimit              64  
    ThreadsPerChild          10  
    MaxRequestWorkers       100 
    MaxConnectionsPerChild  100 
</IfModule>

依然用程序说话

# ps -ef | grep -v grep | grep httpd
root     30242     1  0 23:38 ?        00:00:00 /usr/local/httpd/bin/httpd -k start
apache   30578 30242  0 23:41 ?        00:00:00 /usr/local/httpd/bin/httpd -k start
apache   30579 30242  0 23:41 ?        00:00:00 /usr/local/httpd/bin/httpd -k start
apache   30580 30242  0 23:41 ?        00:00:00 /usr/local/httpd/bin/httpd -k start
apache   30582 30242  0 23:41 ?        00:00:00 /usr/local/httpd/bin/httpd -k start
apache   30600 30242  0 23:41 ?        00:00:00 /usr/local/httpd/bin/httpd -k start
apache   30607 30242  0 23:41 ?        00:00:00 /usr/local/httpd/bin/httpd -k start
apache   30627 30242  0 23:41 ?        00:00:00 /usr/local/httpd/bin/httpd -k start
apache   30635 30242  0 23:41 ?        00:00:00 /usr/local/httpd/bin/httpd -k start
apache   30643 30242  0 23:41 ?        00:00:00 /usr/local/httpd/bin/httpd -k start
apache   30656 30242  9 23:41 ?        00:00:00 /usr/local/httpd/bin/httpd -k start
apache   30671 30242  0 23:41 ?        00:00:00 /usr/local/httpd/bin/httpd -k start

算上主进程 root,实际启动了 12 个进程,怎么算来的,自己去想,我也没想明白呢。。。(或许本来就是随机的个数)

我们按照上面的算法耐心计算,初始启动子进程数 StartServers = 10,最小空闲子进程数 MinSpareThreads 75 / ThreadsPerChild 10 = 75 / 10 = 7.5 个,取整数 8个,最低分配1个子进程

初始化的 10 个子进程,加上 Spare 分配的 1 个子进程,合计为 11 个子进程,符合规则。

 

但是有一个非常重要的原则:worker 和 event 模式,子进程个数不要太多,最好不超过5个子进程,因为子进程非常耗内存资源

米扑博客推荐的 event 模式优化设置如下:

<IfModule mpm_event_module>
#    StartServers             3
#    MinSpareThreads         75
#    MaxSpareThreads        250
#    ThreadsPerChild         25
#    MaxRequestWorkers      400
#    MaxConnectionsPerChild   0
    StartServers              5   
    MinSpareThreads          75  
    MaxSpareThreads         250 
    ServerLimit              16  
    ThreadLimit              32  
    ThreadsPerChild          25  
    MaxRequestWorkers       150 
    MaxConnectionsPerChild  100 
</IfModule>

参数解释:

StartServers 5  初始创建5个子进程

MinSpareThreads 75  最低闲时线程数,即最低有 75 个工作线程处理客户的请求数

MaxSpareThreads 250 最高闲时线程数,即最高有 250 个工作线程处理客户的请求数

ServerLimit 16  允许的最高 子进程数量为 16 个(apache 默认值,这里显示设置出来),若修改了需 apache stop、start 才生效

ThreadLimit 32 允许每一个子进程启用的最大线程数,ThreadsPerChild 不得超过此值,若修改了需 apache stop、start 才生效

ThreadsPerChild 25 每一个子进程的线程数,为固定值,不得超过 ThreadLimit 设定的上限

MaxRequestWorkers 150 同时最大处理的请求数,此数值 MaxRequestWorkers <= ThreadsPerChild * ServerLimit

MaxConnectionsPerChild 100  每一个子进程处理100个请求后则自动销毁释放内存,此参数是影响服务器占用大量内存的罪魁祸首,尽量设置小一些,如 100、80、50

 

查看启动后的进程数量:

# ps -Aef | grep -v grep | grep "bin/httpd" | sort -k2n | wc -l
7
# ps -Aef | grep -v grep | grep "bin/httpd" | sort -k2n 
root     13040     1  0 11:50 ?        00:00:00 /usr/local/httpd/bin/httpd -k start
apache   15230 13040  0 12:42 ?        00:00:00 /usr/local/httpd/bin/httpd -k start
apache   15231 13040  0 12:42 ?        00:00:00 /usr/local/httpd/bin/httpd -k start
apache   15232 13040  0 12:42 ?        00:00:00 /usr/local/httpd/bin/httpd -k start
apache   15250 13040  0 12:42 ?        00:00:00 /usr/local/httpd/bin/httpd -k start
apache   15273 13040  3 12:42 ?        00:00:00 /usr/local/httpd/bin/httpd -k start
apache   15278 13040  0 12:42 ?        00:00:00 /usr/local/httpd/bin/httpd -k start

如上可见,启动后创建了7个进程,包含1个主进程(root)和6个子进程(apache)

按照上面的计算子进程方法,初始化 StartServers 5 子进程,Spare空闲分配的子进程数为 MinSpareThreads 75 / ThreadsPerChild 25 = 75 / 25 = 3个子进程,最低分配1个子进程

因此,初始化 5 个子进程 + Spare 分配的 1 个子进程 = 合计 6个子进程,符合规则。

 

查看每个进程资源消耗:top 

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND                                                     
15278 apache    20   0 2205064 392044   7260 S   0.0 10.1   0:08.07 httpd                                                       
15273 apache    20   0 1992428 173776   7188 S   0.0  4.5   0:03.29 httpd                                                       
15231 apache    20   0 2109220 118496   6816 S  14.6  3.1   0:01.82 httpd                                                       
15232 apache    20   0 1879260  64976   7004 S   0.3  1.7   0:01.06 httpd                                                         
15250 apache    20   0 1891424  51604   7684 S   0.0  1.3   0:00.58 httpd                                                        
13040 root      20   0  555280  20384  11784 S   0.0  0.5   0:00.72 httpd                                                       
15230 apache    20   0  451804   8832   1260 S   0.0  0.2   0:00.00 httpd

如上可见,已有7个进程,包含1个主进程(root)和6个子进程(apache)

仔细查看每一个子进程占用的CPU、内存资源,发现event模式下的子进程相差很大,说明有些子进程繁忙,有些子进程空闲

这也是导致某些子进程占用 CPU、内存非常严重的原因所在,解决办法就是设置 MaxConnectionsPerChild 100 让其处理一些请求后,自动销毁子进程

这里回应了上文介绍 perfork 时,其子进程消耗CPU、内存很均衡,但 worker、event的子进程所消耗资源很不均衡

 

最后需要注意,event 配置仍然遵循一些原则,其与 worker 模式完全一样,如下配置:

<IfModule mpm_event_module>
    StartServers              5   
    MinSpareThreads          75  
    MaxSpareThreads         250 
    ServerLimit              16  
    ThreadLimit              32  
    ThreadsPerChild          10  
    MaxRequestWorkers       200 
    MaxConnectionsPerChild  100 
</IfModule>

如上,就有问题,重启 apache httpd 会报错,错误提示如下:

# /etc/init.d/httpd restart
AH00515: WARNING: MaxRequestWorkers of 200 would require 20 servers and would exceed ServerLimit of 16, decreasing to 160. To increase, please see the ServerLimit directive.

错误提示是什么意思呢?

MaxRequestWorkers 200 最大同时可处理的工作线程数为 200

ThreadsPerChild 10 每一个子进程设置的固定线程数为 10

因此,200个工作线程,需要 200 / 10 = 20 个子进程

但是,ServerLimit 16 限制了子进程数量为 16个,少于需要的最大 20个子进程数,因此就报错了

这里,apache 会自动按照 ServerLimit 16 的最大子进程去创建,并乘以 ThreadsPerChild 10 每一个子进程的线程数 10,结果就是最多可同时处理 16 * 10 = 160 个工作线程(客户端最大请求数)

得出结论,实际上 event 和 worker 配置是完全一样的,如果显式声明了ServerLimit,那么它乘以 ThreadsPerChild的值必须大于等于MaxClients(MaxRequestWorkers),而且MaxClients必须是ThreadsPerChild的整数倍,否则 Apache将会自动调节到一个相应值。

例如:

ServerLimit = 16,ThreadsPerChild = 10,最大工作线程数为 ServerLimit * ThreadsPerChild = 16 * 10 = 160 > 200(MaxRequestWorkers),不符合所以报错

MaxRequestWorkers = 200,ThreadsPerChild = 10,整数倍为 MaxRequestWorkers / ThreadsPerChild = 200 / 10 = 20(倍),符合倍数条件,不报错

 

 

4)itk.c 模式

apache 2.2.x 版本中有 itk.c 模式,apache 2.4.x 版本中没有 itk.c 模式,因此在今后的 apache 2.4.x 以上版本里此模式被舍弃了

仔细查看分析httpd.conf配置文件后,发现还有一种 itk 模式

直接给出其默认配置,供参考

<IfModule itk.c>
	StartServers       4    
	MinSpareServers    4    
	MaxSpareServers    4    
	ServerLimit      128  
	MaxClients       64   
	MaxRequestsPerChild  400  
</IfModule>

 

Apache 长连接 KeepAlive 配置

worker 和 event 都有长连接 KeepAlive 设置,并且 event 解决了 worker 长连接的问题

vim conf/extra/httpd-default.conf

# Timeout: The number of seconds before receives and sends time out.
Timeout 60  	# 一个请求连接多少时间后断开,这个参数设置在30-60

# KeepAlive: Whether or not to allow persistent connections (more than
# one request per connection). Set to "Off" to deactivate.
KeepAlive On  	# 对于perfork模式下的,有人认为是将KeepAlive Off会比较好,但是对于绝大多数的网站都会有些图片元素,所以将该项打开,并将KeepTimeOut设置在2-5秒,不但有效提高服务器性能,也能加快页面打开速度。

# MaxKeepAliveRequests: The maximum number of requests to allow
# during a persistent connection. Set to 0 to allow an unlimited amount.
# We recommend you leave this number high, for maximum performance.
MaxKeepAliveRequests 100 	# 一个连接最大的请求量,对于页面有较多的css、png等元素,可以适当调高一点,如果设置太高会导致httpd长时间不能退出释放内存的。

# KeepAliveTimeout: Number of seconds to wait for the next request from the
# same client on the same connection.
#KeepAliveTimeout 5
KeepAliveTimeout 15		# 当用户处理一次连接时,如果在该参数的时间内还有请求则会继续执行,不需要重新创建新的连接,直到达到MaxKeepAliveRequests的最大值才会退出

参数说明:

Timeout是一个连接多少时间后断开,这个参数设置在30-60是一般的php程序都是适用的,至少要运行一些要占用大量时间的php程序,那么适当调高也是可以的,但请不要太高,否则会影响apache性能,本次优化我们使用30就很足够了。

MaxKeepAliveRequests 是一个连接最大的请求量,对于页面有较多的图片等元素,可以适当调高一点,对于一般的网页设置在80-120是足够的,默认设置为100,如果设置太高会导致httpd长时间不能退出释放内存的

KeepAliveTimeout 是当用户处理一次连接时,如果在该参数的时间内还有请求则会继续执行,不需要重新创建新的连接,直到达到MaxKeepAliveRequests的最大值才会退出。对于perfork模式下的,有人认为是将KeepAlive Off会比较好,但是对于绝大多数的网站都会不多不少有些图片元素,所以将该项打开,并将KeepTimeOut设置在2-5秒,不但有效提高服务器性能,也能加快页面打开速度。

 

Apache优化加载模块

保留WordPress主要模块,注释掉不重要的模块(后期用到可去掉注释)

WordPress主要使用以下模块:

LoadModule authz_host_module modules/mod_authz_host.so
LoadModule log_config_module modules/mod_log_config.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
LoadModule negotiation_module modules/mod_negotiation.so

实际保留和注释的模块如下:

# grep "LoadModule" httpd.conf
# have to place corresponding `LoadModule' lines at this location so the
# LoadModule foo_module modules/mod_foo.so
LoadModule authn_file_module modules/mod_authn_file.so
#LoadModule authn_dbm_module modules/mod_authn_dbm.so
#LoadModule authn_anon_module modules/mod_authn_anon.so
#LoadModule authn_dbd_module modules/mod_authn_dbd.so
#LoadModule authn_socache_module modules/mod_authn_socache.so
LoadModule authn_core_module modules/mod_authn_core.so
LoadModule authz_host_module modules/mod_authz_host.so
#LoadModule authz_groupfile_module modules/mod_authz_groupfile.so
LoadModule authz_user_module modules/mod_authz_user.so
#LoadModule authz_dbm_module modules/mod_authz_dbm.so
#LoadModule authz_owner_module modules/mod_authz_owner.so
#LoadModule authz_dbd_module modules/mod_authz_dbd.so
LoadModule authz_core_module modules/mod_authz_core.so
#LoadModule authnz_ldap_module modules/mod_authnz_ldap.so        
LoadModule access_compat_module modules/mod_access_compat.so
LoadModule auth_basic_module modules/mod_auth_basic.so
#LoadModule auth_form_module modules/mod_auth_form.so
#LoadModule auth_digest_module modules/mod_auth_digest.so
#LoadModule allowmethods_module modules/mod_allowmethods.so
#LoadModule file_cache_module modules/mod_file_cache.so
#LoadModule cache_module modules/mod_cache.so
#LoadModule cache_disk_module modules/mod_cache_disk.so
#LoadModule cache_socache_module modules/mod_cache_socache.so
LoadModule socache_shmcb_module modules/mod_socache_shmcb.so
#LoadModule socache_dbm_module modules/mod_socache_dbm.so
#LoadModule socache_memcache_module modules/mod_socache_memcache.so
#LoadModule socache_redis_module modules/mod_socache_redis.so
LoadModule watchdog_module modules/mod_watchdog.so
#LoadModule macro_module modules/mod_macro.so
#LoadModule dbd_module modules/mod_dbd.so
#LoadModule dumpio_module modules/mod_dumpio.so
#LoadModule buffer_module modules/mod_buffer.so
#LoadModule ratelimit_module modules/mod_ratelimit.so
LoadModule reqtimeout_module modules/mod_reqtimeout.so
#LoadModule ext_filter_module modules/mod_ext_filter.so
#LoadModule request_module modules/mod_request.so
#LoadModule include_module modules/mod_include.so
LoadModule filter_module modules/mod_filter.so
#LoadModule substitute_module modules/mod_substitute.so
#LoadModule sed_module modules/mod_sed.so
#LoadModule deflate_module modules/mod_deflate.so
LoadModule mime_module modules/mod_mime.so
#LoadModule ldap_module modules/mod_ldap.so
LoadModule log_config_module modules/mod_log_config.so
#LoadModule log_debug_module modules/mod_log_debug.so
LoadModule logio_module modules/mod_logio.so
LoadModule env_module modules/mod_env.so
#LoadModule expires_module modules/mod_expires.so
LoadModule headers_module modules/mod_headers.so
#LoadModule unique_id_module modules/mod_unique_id.so
LoadModule setenvif_module modules/mod_setenvif.so
#LoadModule version_module modules/mod_version.so
LoadModule remoteip_module modules/mod_remoteip.so
LoadModule proxy_module modules/mod_proxy.so
#LoadModule proxy_connect_module modules/mod_proxy_connect.so
#LoadModule proxy_ftp_module modules/mod_proxy_ftp.so
#LoadModule proxy_http_module modules/mod_proxy_http.so
#LoadModule proxy_fcgi_module modules/mod_proxy_fcgi.so
#LoadModule proxy_scgi_module modules/mod_proxy_scgi.so
#LoadModule proxy_uwsgi_module modules/mod_proxy_uwsgi.so
#LoadModule proxy_fdpass_module modules/mod_proxy_fdpass.so
#LoadModule proxy_wstunnel_module modules/mod_proxy_wstunnel.so
#LoadModule proxy_ajp_module modules/mod_proxy_ajp.so
#LoadModule proxy_balancer_module modules/mod_proxy_balancer.so
#LoadModule proxy_express_module modules/mod_proxy_express.so
#LoadModule proxy_hcheck_module modules/mod_proxy_hcheck.so
#LoadModule session_module modules/mod_session.so
#LoadModule session_cookie_module modules/mod_session_cookie.so
#LoadModule session_crypto_module modules/mod_session_crypto.so
#LoadModule session_dbd_module modules/mod_session_dbd.so
#LoadModule slotmem_shm_module modules/mod_slotmem_shm.so
LoadModule ssl_module modules/mod_ssl.so
#LoadModule lbmethod_byrequests_module modules/mod_lbmethod_byrequests.so
#LoadModule lbmethod_bytraffic_module modules/mod_lbmethod_bytraffic.so
#LoadModule lbmethod_bybusyness_module modules/mod_lbmethod_bybusyness.so
#LoadModule lbmethod_heartbeat_module modules/mod_lbmethod_heartbeat.so
#LoadModule unixd_module modules/mod_unixd.so
LoadModule dav_module modules/mod_dav.so
#LoadModule status_module modules/mod_status.so
#LoadModule autoindex_module modules/mod_autoindex.so
#LoadModule info_module modules/mod_info.so
#LoadModule cgid_module modules/mod_cgid.so
#LoadModule dav_fs_module modules/mod_dav_fs.so
#LoadModule vhost_alias_module modules/mod_vhost_alias.so
#LoadModule negotiation_module modules/mod_negotiation.so
LoadModule dir_module modules/mod_dir.so
#LoadModule actions_module modules/mod_actions.so
#LoadModule speling_module modules/mod_speling.so
#LoadModule userdir_module modules/mod_userdir.so
LoadModule alias_module modules/mod_alias.so
LoadModule rewrite_module modules/mod_rewrite.so
LoadModule php_module           modules/libphp.so
LoadModule http2_module         modules/mod_http2.so
#LoadModule mpm_prefork_module   modules/mod_mpm_prefork.so
#LoadModule mpm_worker_module    modules/mod_mpm_worker.so
#LoadModule mpm_event_module     modules/mod_mpm_event.so
LoadModule dav_svn_module     modules/mod_dav_svn.so
LoadModule authz_svn_module   modules/mod_authz_svn.so

 

 

米扑博客 的Apache 服务器,采用的就是 prefork.c

# httpd -l
Compiled in modules:
  core.c
  prefork.c
  http_core.c
  mod_so.c

服务器的apache采用的是 prefork 的工作模式

对 MaxClients 进行了相应的调整,发现服务启动后很短时间,连接数就能够达到最大。

 

5、查看用户访问日志

查看用户访问日志和详情页面,将配置中的 access_log 打开,发现85%以上的访问都是直接访问的资源文件,

由此判定,用户可能使用了多线程的下载工具,或者这些资源遭受了盗链(可能性更大)。

 

优化方案

1. 限制单个IP进行连接的线程,不允许多线程连接资源

对于IP限制,采用了 mod_limitipconn 这个模块。

这个模块的优点是配置简单,缺点是不能够针对单独的文件夹或者文件进行设置,而且不支持虚拟主机。

在 apache 中安装了这个模块后,在配置文件中添加如下几段就可以生效了:

ExtendedStatus On 
< IfModule mod_limitipconn.c > 
    < Location / >   		# 所有虚拟主机的/目录 
        MaxConnPerIP 3     	# 每IP只允许3个并发连接 
        NoIPLimit image/*  	# 对图片不做IP限制 
    < /Location > 
	< Location /mp3 >  			# 所有主机的/mp3目录 
		MaxConnPerIP 1         		# 每IP只允许一个连接请求    
		OnlyIPLimit audio/mpeg video    # 该限制只对视频和音频格式的文件 
    < /Location > 
< /IfModule >

 

2. 添加URL重写,防止盗链

防止盗链,一个重要的方法就是判断请求的 refer

但是如果一些浏览器发出请求的时候将 refer 去掉,或者伪装,这个办法就无能为力了。

但是貌似还有更高级的方法,还是可以实现这个功能。

安装apache的 mod_rewrite 模块后,在apache配置文件中添加

RewriteEngine On 
RewriteCond %{HTTP_REFERER} !^https://blog.mimvp.com/.*$ [NC] 
RewriteCond %{HTTP_REFERER} !^https://blog.mimvp.com$ [NC] 
RewriteCond %{HTTP_REFERER} !^https://blog.mimvp.com/.*$ [NC] 
RewriteCond %{HTTP_REFERER} !^https://blog.mimvp.com$ [NC] 
RewriteRule .*\.(gif|jpg|swf)$ https://blog.mimvp.com/404.png [R,NC]

这样盗链的请求会被重定向到一个错误页面,从而减少下载带给服务器的压力。

 

3. 网站打不开,httpd无法启动,提示错误“No space left on device: Cannot create SSLMutex”

错误信息如下:

[Sat Dec 16 06:12:32 2017] [error] server reached MaxClients setting, consider raising the MaxClients setting
[Sat Dec 16 06:20:38 2017] [notice] suEXEC mechanism enabled (wrapper: /usr/sbin/suexec)
[Sat Dec 16 06:20:38 2017] [warn] Init: Name-based SSL virtual hosts only work for clients with TLS server name indication support (RFC 4366)
[Sat Dec 16 06:20:38 2017] [notice] Digest: generating secret for digest authentication ...
[Sat Dec 16 06:20:38 2017] [notice] Digest: done 
[Sat Dec 16 06:20:39 2017] [warn] Init: Name-based SSL virtual hosts only work for clients with TLS server name indication support (RFC 4366)
[Sat Dec 16 06:20:39 2017] [notice] Apache/2.2.27 (Unix) DAV/2 mod_ssl/2.2.27 OpenSSL/1.0.1e-fips SVN/1.6.11 mod_perl/2.0.4 Perl/v5.10.1 configured -- resuming normal operations
[Sat Dec 16 06:21:07 2017] [error] server reached MaxClients setting, consider raising the MaxClients setting
[Sat Dec 16 06:30:06 2017] [notice] suEXEC mechanism enabled (wrapper: /usr/sbin/suexec)
[Sat Dec 16 06:30:06 2017] [error] (28)No space left on device: Cannot create SSLMutex
Configuration Failed

根据错误提示第一行“[error] server reached MaxClients setting, consider raising the MaxClients setting”,认为是MaxClients数量太少

查看 apache 工作模式:

# httpd -l
Compiled in modules:
core.c
prefork.c
http_core.c
mod_so.c

工作模式为 prefork.c ,于是进一步查看 httpd.conf 的 prefork.c 配置

vim  httpd.conf

<IfModule prefork.c>
#    StartServers       4
#    MinSpareServers    4
#    MaxSpareServers   20
#    ServerLimit      128
#    MaxClients       100
#    MaxRequestsPerChild  2000
    StartServers       10   
    MinSpareServers    10   
    MaxSpareServers    20   
    ServerLimit        64   
    MaxClients         64   
    MaxRequestsPerChild  1000 
</IfModule>

因为服务器配置不高、日访客并发不大,于是 MaxClients 配置数较小,是合理的。

进一步分析,关注到错误提示“No space left on device: Cannot create SSLMutex”,这一句的含义是没有剩余资源创建 SSLMutex 共享变量

联想到了Linux无法创建句柄,并会无法提供服务,于是查看共享变量资源的占用情况

ipcs : ipcs provides information on the ipc facilities for which the calling process has read access.

# ipcs -s

------ Semaphore Arrays --------
key        semid      owner      perms      nsems     
0x00000000 0          root       600        1         
0x00000000 32769      root       600        1         
0x00000000 163842     apache     600        1         
0x00000000 196611     apache     600        1         
0x00000000 229380     apache     600        1         
0x00000000 262149     apache     600        1         
0x00000000 294918     apache     600        1  
.......

发现共享变量有数百个被apache进程占用,无法释放,资源耗尽了。

于是,需要删除占尽的共享变量信号资源,删除命令如下:

ipcs -s | perl -ane '/^0x00000000/ && `ipcrm -s $F[1]`'

重新启动Apache httpd 服务器:

/etc/init.d/httpd restart

httpd无法重启的问题解决!

 

查看正常情况下的共享变量信号:ipcs -s

# ipcs -s

------ Semaphore Arrays --------
key        semid      owner      perms      nsems     
0x00000000 4587520    apache     600        1         
0x00000000 4620289    apache     600        1         
0x00000000 4653058    apache     600        1

 

查看 ipcs 的限制参数:ipcs -l

# ipcs -l

------ Shared Memory Limits --------
max number of segments = 4096
max seg size (kbytes) = 67108864
max total shared memory (kbytes) = 17179869184
min seg size (bytes) = 1

------ Semaphore Limits --------
max number of arrays = 128
max semaphores per array = 250
max semaphores system wide = 32000
max ops per semop call = 32
semaphore max value = 32767

------ Messages: Limits --------
max queues system wide = 3751
max size of message (bytes) = 65536
default max size of queue (bytes) = 65536

显示发现,Semaphore Limits 最大为128,超过了这个数量,httpd 服务将会无法再启动,也就导致了上面的网站打不开。

 

 

总结

apache性能调试是一个比较复杂的问题,除了apache本身配置、PHP配置,还会涉及到数据库。

后台的难点就在于如何应对高并发、大流量的请求,这往往不是单个点可以解决的,需要系统各个模块来协调。

通过优化 prefork 或 event 模式,启用限制IP和防盗链,米扑博客的访问速度提升非常明显

而且,CPU、内存、负载、MySQL等指标不仅没有上升,反而大幅下降了

top - 15:05:06 up  4:07,  3 users,  load average: 0.73, 0.67, 1.44
Tasks: 150 total,   1 running, 147 sleeping,   0 stopped,   2 zombie
Cpu(s): 34.2%us,  1.7%sy,  0.0%ni, 63.8%id,  0.0%wa,  0.0%hi,  0.3%si,  0.0%st
Mem:   1920944k total,  1422288k used,   498656k free,    37156k buffers
Swap:  4095984k total,   786528k used,  3309456k free,   257536k cached

  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND                                                                                     
 3393 mysql     20   0 1506m 222m 4620 S  2.0 11.9   7:58.70 mysqld                                                                                       
 1733 apache    20   0 1146m  66m  28m S  0.0  3.5   0:09.77 httpd                                                                                        
 1753 apache    20   0 1147m  62m  23m S  0.0  3.3   0:05.92 httpd                                                                                        
 1735 apache    20   0 1145m  62m  23m S  0.0  3.3   0:07.30 httpd                                                                                        
 1937 apache    20   0 1145m  60m  21m S  0.0  3.2   0:06.85 httpd                                                                                        
 2196 apache    20   0 1144m  58m  21m S  0.0  3.1   0:02.53 httpd                                                                                        
 1737 apache    20   0 1140m  56m  24m S  0.0  3.0   0:09.53 httpd                                                                                        
 1942 apache    20   0 1137m  54m  23m S  0.0  2.9   0:03.96 httpd                                                                                        
 1734 apache    20   0 1138m  54m  22m S  0.0  2.9   0:05.24 httpd                                                                                        
 1932 apache    20   0 1138m  53m  21m S  0.0  2.9   0:04.42 httpd                                                                                        
 2200 apache    20   0 1139m  53m  21m S  0.0  2.9   0:02.10 httpd

 

客户请求连接数

# ps -ef | grep httpd | wc -l                                 
32
# netstat -ant | grep -E ":80|:443" | wc -l
37
# netstat -ant | grep ESTABLISHED | grep -E ":80|:443" | wc -l              
6
# netstat -ant | grep ESTABLISHED | grep -E ":80|:443" 
tcp        0      0 115.29.237.28:38044         140.205.140.205:80          ESTABLISHED 
tcp        0      0 115.29.237.28:80            203.208.60.200:41095        ESTABLISHED 
tcp        0      0 115.29.237.28:443           1.203.144.157:53865         ESTABLISHED 
tcp        0      0 115.29.237.28:80            52.39.115.165:56716         ESTABLISHED 
tcp        0      0 115.29.237.28:80            113.13.100.150:9134         ESTABLISHED 
tcp        0      0 115.29.237.28:443           113.13.100.150:8138         ESTABLISHED

 

米扑博客访问速度明显提升,亲测同时支持百度、搜狗、360、Bing、飞翔谷歌索引爬取峰值

https://blog.mimvp.com

 

总结

本文知识量比较大,很多参数概念容易混淆,因此阅读理解起来可能比较困难,推荐初学者多读几遍

本文我已经反复修改订正了10次以上,参考各种资料20多次,反反复复在服务器上调优了50次以上

写一篇博客,真心不容易,耗费了大量的时间和精力,不敢保证都正确,有错误之处还请留言斧正

 

 

参考推荐

Apache 工作的三种模式:Prefork、Worker、Event

Apache 设置禁止访问网站目录

Apache 封禁IP及IP段访问

Apache 日志格式详解

Apache 实现https+Apache http访问转到https

Nginx 配置文件禁止访问目录或文件