先说说背景:一个LAMP在线测试网站,日均PV1万左右,比赛时一小时就好几万吧。目前数据库大约有30万条记录。服务器配置很高。近期出现性能问题,当访问量增大的时候,数据库服务器的压力非常大,mysql的内存占用率通常能到400%,这时候基本不能提供服务了,连网站页面都打不开。理论上说现在的数据量还不算大,访问量也不是很大,服务器的配置也很高,出现这种状况是不正常的。这个问题究结了很久,昨天终于找到问题的原因了,记录一下,以后遇到类似的问题可以有个参考。先看一张图片,这是glances监控软件的截图,可以看到红色的496.6就是mysql的CPU占用率
之前遇到这个问题,只能重启mysql服务,但这只是暂时的,重启之后不到1分钟CPU占用率接着就上来了,不是长久之计。
然后考虑修改数据库和apache的配置文件,加大缓存空间,但效果还是不明显。
后来又考虑是不是路由器的问题,因为目前是两台服务器通过一个小路由器连起来,组成一个小局域网,一个放apache(压力很小),另一个服务器放数据库(压力很大),交换数据都要先经过路由器。于是开始怀疑路由器的性能问题,干脆直接用一根网线把两个服务器连起来(服务器有两个网卡)。这样两个服务器就能直接通信了。事实证明,这样还是不能解决数据库压力大的问题。
接下来,还是各种纠结,想过各种方法,比如换nginx,换其他数据库,弄个数据库集群什么的,都比较麻烦,还需要修改php代码,比较费劲,关键是php这东西看着就恶心……
有一天,我发现了memcached,memcached是一个缓存系统,这么说吧,假如有一个查询语句,很费时间,如果没有缓存的话,每次刷新页面就要访问数据库查询一次,这样数据库压力就比较大。 如果有缓存就不一样了,如果要查询的数据在缓存中存在的话,就直接从缓存中取出来,这样就不用去数据库查询了,如果访问量大的话可以明显减少数据库的查询次数,当然也减轻了数据库的压力。先贴一段没有缓存的代码:
$list = $statics->getFirstTenList();
下面是加上缓存的代码:
$list = null; if (!($list = $cache->get('top_ten_list'))) { $list = $statics->getFirstTenList(); $cache->set('top_ten_list', $list, 7200); }
没有缓存的代码,每次都要调用查询函数从数据库获取数据。下面的有缓存的就不一样了。当需要查询一个数据之前,首先看看缓存里面有没有,就是代码中$cache->get(),如果从缓存取得了,就不用执行那条查询数据库的语句了。如果缓存服务器没有,就要从数据库里面查询,查询以后,用$cache->set()方法就刚才的查询结果保存到缓存服务器,并设置这个缓存存在的时间,这里是7200秒,7200秒之后,这个缓存就失效了,就要从新更新。还有一点比较重要,就是要给每一个缓存取一个名字,不同的缓存名字不能相同,缓存服务器是根据缓存的名字来区分缓存,如果名字相同的话,从缓存取出来的数据就不是我们需要的数据。举个例子来说:有两个比赛,比赛的排名页面做一个缓存,如果两个缓存名字都叫ranklist,首先刷新一下第一个比赛的排名页面,那么第一个比赛的排名数据就存在缓存服务器了。如果这时再刷新一下第二个比赛的排名页面,因为ranklist在缓存中存在,直接就从缓存中取出来了,但是取出来的数据并不是我们需要的,这种问题还是比较小的。如果是不同类型的数据,那么网站就乱了。
缓存是个好东西,然后打算通过大量增加缓存来减轻服务器的压力。但后来我发现,这样也不行。因为我们的网站是一个在线测评系统,是一个实时动态的系统,一个用户提交一个答案以后,想立即看到评判结果,当答案正确的时候,要立即更新排名信息。如果增加缓存的话,从缓存中取出来的信息就不是最新的,不能显示实时的排名等信息。这就好比你订了一张票,但是系统好久才提示你订票成功,中间的等待时间是最难熬的。还有一个原因也限制了缓存的使用,就是对于大量的页面,比如说服务器上有2000个题,一个题一个页面,如果一个页面一个缓存的话肯定会降低缓存的性能。但是对于一些数据,长时间变动不大,比如说总做题数的排名,就可以考虑增加一个缓存,可以设置半天或者一天的生存时间,也就是半天或者一天更新一次。
由于大量缓存并不适用与我们这种实时性很高的系统,因此我并没有增加很多缓存。数据库压力还是那么大。还是没找到问题的根源。
找到一本mysql性能优化的书,然后就开始研究,首先想到的还是加索引,后来用show index from table_name来查看,发现里面已经对常用的查询词加索引了,我甚至开始考虑是不是因为索引太多加重了数据库的压力?经过一些测试(删除某些索引),发现还是不行,看来通过增加索引已经不能很好得解决OJ问题。
后来发现了一个语句,是 show processlist,这条命令的作用是显示哪些线程正在执行,执行了多长时间,还有一些其他信息,看截图:
上面的图片是在负载不是很大的时候截的,从上图我们能看到有两条sql查询语句已经执行了7秒了,还有一个正在往临时表拷贝数据的操作。真正比赛的时候比这个问题严重多了!有几个sql语句执行时间超过10秒,总的sql查询语句个数有200多个,当然大部分线程是被阻塞的。 在这里我们就能很明显看出来到底是哪一条sql语句在浪费时间,接下来,我们就要找到底这条sql查询语句是从哪发起的,从php源吗中找这条语句,如果一条一条找就费劲了,这里我们用强大的grep命令来帮助我们查找。
很明显,这几条语句就在一个文件里面,让我们打开这个文件看看,下面是其中的一个函数,包含上面的查询语句
这个函数是从哪调用的?再找
看到了吧,这里有八条类似的语句,这8条语句的作用是什么?其中第一条的作用是从30万的表里找到今天提交的并且AC的题目总数,第二条就是统计今天的提交总数,下面几条分别是统计这周,这月,这年的AC和提交总数。显示提交状态的solution表中目前大约有30万条信息,并且随时都会更新,只要有人提交就会插入新数据并更新一些数据。在数据库理论里面将,统计和插入更新是互相冲突的,这里就需要加锁,当执行查询语句的时候,将这个表锁住,防止其他数据更新而影响查询结果。也就是说,当执行上面的查询语句时,数据库里面的其他语句就被阻塞了,就*去sleep。在提交高峰期,数据库里面被阻塞的语句能达到200多条甚至更多,mysql的CPU占用率达到能达到500%,肯定不能对外提供服务了!
接下来,我们要找找到底是在哪个页面里面有这个统计,哪个页面调用了这个函数,(强大的grep)
当看到上面的结果的时候,我的心凌乱了~~靠,怎么回事?这个多页面都有这个统计,然后打开页面文件找找,找到了
(上图是我修改之后的查询结果,大部分都被我用//注释掉了,原来是没有注释的~)
在一个不起眼的边栏里,我看到了上面的一个统计。我调查了一些用户,你们注意过这个统计信息吗?你们关注过这个统计信息吗?答案和我想的一样,没有!谁都不关注那这个统计还有什么意义?不知道当初设计者怎么想到的这个功能。当初数据量小的时候没有这个问题,随着数据量增加,问题就暴露出来了。就是这8个数字,拖慢了整个网站的速度!
接下来解决方案也很清晰了,两种方法:1.删除这个统计,2.给这个统计增加缓存。综合各方面的意见,我决定只在总的排名页面保留这个统计,当然是要增加缓存的,6个小时更新一次。其他页面全部去掉!
解决这个问题之后,再试试网站的访问速度,那叫一个快啊!泪奔啊!纠结了这么久的性能问题终于解决啦!orz
博主ma6174对本博客文章(除转载的)享有版权,未经许可不得用于商业用途。转载请注明出处http://www.cnblogs.com/ma6174/
对文章有啥看法或建议,可以评论或发电子邮件到ma6174@163.com
本文转自ma6174博客园博客,原文链接:http://www.cnblogs.com/ma6174/archive/2012/12/16/2818666.html,如需转载请自行联系原作者