snwxf 发表于 2017-08-31 20:24

从1秒到0.01秒

百度等搜索引擎以每秒10次至1000次的水平的持续抓取,这给我们的服务器造成极大的困扰。每天早上经常出现后台打不开,网站无法访问,等502、504、500错误。

我在几个月的时间内,经过以下6个步骤,目前从平均执行时间2秒降到了0.03秒左右。



1. 优化SQL

最开始,每个页面的执行时间长度1-2秒,首先想到的是优化SQL语句,增加索引,这些都是常规,此时的抓取频率在10次以,造成的后果是服务器过载,频繁出现504错误,经常对SQL语句的优化,基本解决这些问题。

经过这次调整,执行时间降到了0.5秒左右。



2. 启用缓存(Redis)

时间不长,大约也就是几个星期,服务器再次频繁出现过载,网站频繁出现500、504错误,经过查看,爬虫提高了频率,达到每秒20次或者更多,经过分析,发现瓶颈在数据读取上,每个页面要执行上百个SQL语句,这时,减少SQL语句执行的数量成为关键,经常分析,发现页面上有一些内容是重复的,有些是全局性重复,有些是局部性重复,例如目录列表、标签列表,找不到相关仪器时给出的推荐仪器列表等,根据相关的参数,我们启用了REDIS缓存,有效的降低了每一个页面平均执行的SQL语句数量。

经过这次调整,页面平均执行时间降到了0.1秒左右。



3. 使用新的数据库(Postgresql)

同样,好景不长,几个星期后,再次出现问题,爬虫抓取频率经常达到100次/秒以上,这次,SQL已经没有多少优化空间,能缓存的也都缓存了,经过分析,我们发现有些语句在MYSQL上的执行时间过长,例如递归性质的读取数据以及多表关联时的数据读取时间都比较长, 而且MYSQL是锁表性质的读(Myisam),造成在频率读取时,与其他程序竞争系统资源,造成MYSQL死锁,无法接受新的请求。

我发现Postgresql可以解决这个问题,读表时不锁表,且递归读取性能非常好,还有分词搜索功能(为模糊搜索作准备),我将这些页面上可能用到的数据,从Mysql数据表同步到Postgresql数据库,将数据连接改为从Postgresql 数据库读取,经过这个优化,Mysql不再出现锁死的情况,虽然速度没有多少提升,但是至少可以防止整个服务器过载,影响其他页面的访问。

经过这次调整,执行时间降到了0.09秒。



4. 优化服务器配置(NGINX)

然而,我知道,好景一定不会太长,果然,一段时间后,问题再次到来,爬虫的抓取频率达到了200次/秒~1000次/秒,服务器再次出现大规模过载。

到这里,有两个方案,一个是增加服务器,一个是限制抓取,增加服务器成本过高,而且增加一台显示解决不了问题,一次性增加10台(5台Webserver,5台数据库),有可能能解决这个问题,但显然,爬虫还会再次提高频率。

后来,我决定通过限制抓取频率来解决这个问题,使用Nginx的Limit_conn 模块,将爬虫连接频率降到50次/秒以下,每秒超过30次的请求,返回503错误。

经过这次调整,执行时间没有下降,但是一定程度上解决了过载的问题。



5. 增加Socket连接数(LINUX)

虽然,前面提到了“一定程度”上解决了过载的问题,但是显然问题还是不停的出现,具体来看有两种表现:

1) 一种是访问无法访问或者访问时间很长(达30秒以上),或者是504错误,而此时,数据库的负载并不高,WebServer的负载也不高;

2) 另一种情况下,百度得到的503错误随着他自身抓取频率的提高而提高,这显示也不利于搜索引擎优化。

经过分析,我发现WebServer虽然负载不高,但是PHP错误日志里提示无法分配端口给Mysql,我运行了一下netstat -an ,发现TCP连接数达到上万,问了一下度娘,原来一个服务器能同时维持的TCP连接数是有限的,需要加大TCP连接限制,经过一番研究,将该服务器的TCP连接端口范围改为1024~65535,也就是达到64000多个(默认这个范围只有不到2万个),期间也使用了诸如加大文件描述符数据、TCP连接重用、长连接等等方式的调整。

经过这次调整,服务器又安静了一段时间。



6. 再次优化程序

更大的考验来了,这次,60000个端口也不够了,难道我们只能通过增加服务器的数量来解决?显示服务器的负载并不高,只是端口不够用而已。

那么问题来了,为啥端口不够用呢?每秒执行的次数也就1000次,按说1000个端口不就够了吗?为啥现在会出现这么多的端口占用呢?

分析了TCP连接后,发现,有大量的数据库连接处于TIME_WAIT状态,也就是WebSERVER关闭了数据库连接,等待系统回收,然而,这个回收时间需要120秒,显示这样很快就积累到一个非常大的数字了。

如何快速回收端口?没戏,找了半天,Linux只能设置Fin_Wait时间,不能设置Time_wait时间,所以没有用,转回来,是什么原因造成1000次页面执行需要这么多的商品呢?

经过再次检查程序,

原来,我们的程序中有很多的“new db()“ ,特别是在一些循环、函数内容有这个New DB(),也就是每次循环或者每次调用函数都New一个新的数据库连接,看来这就是问题所以了。

这次,解决的办法是,一个页面执行,尽量使用一个全局的数据库连接,在页面开始前生成这个全局连接变量,在函数里,使用 global $db 来调用。

所以,现在很多的函数,开头前,都有这样一句内容:

Function foo(){

Global $dbproxy;

If(empty($dbproxy)){

$dbproxy
= new mydb_proxy(‘read’);

}

……

}

这次,连接降到了3000个左右了,世界安静了好几天了……



期间,通过将标签内容也缓存起来,提升了0.02秒的速度,在进行最后一步前,执行平均速度是0.08秒左右,最后修改了程序,使用全局数据库连接后,平均执行时间降到了0.038秒(并且有大量的页面执行时间在0.01秒左右,也就是说,当系统只运行这类页面时,时间已经降到0.01秒了)。全局数据库连接有以下几个好处:

1) 减少了内存占用(每一个新连接都需要占用一定的内存);

2) 减少了TCP连接时间,每一个新的数据库连接生成时,都需要建立TCP连接,这个累积起来时间也非常可观;

3) 减少了端口占用,第一个TCP连接都需要占用一个新的商品,即使马上关闭,也需要等待2分钟系统回收。



仅仅依靠启用数据库的长连接方式是不能解决问题的,这个已经尝试过,而使用全局数据库连接变量,效果显著,按PHP手册的说明,如果同时也启用了长连接,似乎也会有一些效果。



总结:程序逻辑、编写方式,对于性能影响实在过大!

shang2010 发表于 2017-09-07 17:24

请问你们优化是研发人员后期继续增加优化环节,还是交给专业的优化团队??

snwxf 发表于 2018-04-13 15:08

回复 2# shang2010

自己干

jdgdf566 发表于 2018-09-01 07:50

屏蔽百度虫子的爬取
页: [1]
查看完整版本: 从1秒到0.01秒