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

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

统计访客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 设置的最大值。

由此断定是网站访问人数过多造成了访问过慢。 

 

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、熟悉服务器的 MPM 配置

刚开始的时候,对于Apache服务器的 MPM 配置方式不是特别的熟悉,认为修改服务器配置可以解决问题。

MPM是Apache的核心,它的作用是管理网络连接、调度请求。

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

perfork 从Apache1.3中继承下来的,它采用的是进程管理方式,所以它可以提供更可靠的性能和更好的兼容性;

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

event仍处于试验阶段,它为每个任务分配不同的进程池,目前不应该采用。

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

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

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

 

本文主要介绍的Apache配置,包括 prefork 或者 work 模式的配置

1) prefork 模式

以 prefork 模式工作的 apache 的默认配置: 

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

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

MaxSpareServers 设置了最大的空闲进程数,如果空闲进程数大于这个值,Apache会自动kill掉一些多余进程。这个值不要设得过大,但如果设的值比 MinSpareServers小,Apache会自动把其调整为 MinSpareServers+1。

如果站点负载较大,可考虑同时加大MinSpareServers和MaxSpareServers

 

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

MaxClients 缺省值150,一般是远远不够的,如果请求总数已达到这个值(可通过 ps -ef | grep httpd | wc -l 来查看),那么后面的请求就要排队,直到某个已处理请求完毕。这就是系统资源还剩下很多而HTTP访问却很慢的主要原因

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

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

MaxRequestsPerChild用来控制每个进程在处理了多少次请求之后自动销毁,这个参数可以设置为0表示无限,即不销毁进程

而且 ServerLimit 必须大于等于 MaxClients 数量,否则报错

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

使用场景:

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

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

优化设置如下:

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

 

2)worker 模式

以 worker 模式工作的 apache 的默认配置为: 

<IfModule worker.c> 
    StartServers           2 
    MaxClients           150 
    MinSpareThreads       25 
    MaxSpareThreads       75 
    ThreadsPerChild       25 
    MaxRequestsPerChild    0 
</IfModule> 

Worker 由主控制进程生成“StartServers”个子进程,每个子进程中包含固定的ThreadsPerChild线程数,各个线程独立地处理请求。同样,为了不在请求到来时再生成线程, 

MinSpareThreads MaxSpareThreads 设置了最少和最多的空闲线程数;

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

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

ThreadsPerChild 是worker MPM中与性能相关最密切的指令。 

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

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

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

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

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

 

3)itk.c 模式

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

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

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

 

米扑博客 的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  3000 
</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 服务将会无法再启动,也就导致了上面的网站打不开。

 

 

总结

通过优化 prefork 模式,启用限制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

 

 

参考推荐

Apache 设置禁止访问网站目录

Apache 封禁IP及IP段访问

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

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