一次B/S架构系统的调优实践

需调优的系统采用B/S架构,apache2+php5.6+thinkphp3.2 +mysql5.6+redis

      在开发阶段没有出现什么性能问题,但在上线一段时间后,随着在线用户数的增加,其实也不多(应该不到20人同时使用),就开始频繁地报告页面卡顿,有时甚至出现卡死。

    系统部署时web服务器与mysql数据库服务器是分开的两台虚拟机,都是windows server 2008操作系统。

    开始时怀疑apache服务器并发压力大,打开服务器查看,硬件没有什么压力,16G内存占用1/8左右,CPU占用10%以下。检查服务器连接数 40个左右,没有什么并发压力。

Apache+php开始用的是默认的php_mod方式,后来改为用fcgid(fast cgi)方式,用loadrunner做简单压力测试,承受并发的能力有所增强,但页面卡慢现象没有改善。

     经以上检查初步排除硬件资源不足与web服务器压力过大的原因。

     观察浏览器页面出现的卡慢现象,用浏览器自带的开发者工具,发现静态资源的加载时间都是ms级,但php的加载在卡慢时,waiting(TTFB)时间会达到几十秒甚至几分钟,卡慢现象非常严重,卡慢的原因是php执行时间太长。开发时没有出现这种情况,但在上线时却出现了,说明还是有并发冲突。

    在观察mysql数据库服务器的资源情况时发现cpu占用很低,内存占用很低,磁盘I/O速率也不高。但“磁盘最长活动时间”却长时间保持在100% ,初步分析有大量的小的随机读写。怀疑mysql数据库的执行有问题。于是打开mysql的慢查询日志,慢查询的门槛时间设置为2s(备注:mysql默认未打开慢查询日志,打开慢查询日志的方法在网上很容易找到)

在慢查询日志中果然发现了大量的慢查询语句,有select语句、insert 语句和update语句,很多语句的查询时间为几十秒至上百秒。由此工作重点转向了如何解决这些慢查询。

对innodb来说,select语句不应该锁表,为什么会出现慢查询呢?

经与开发人员沟通,可分为几类情况进行处理

1 开发人员为了产生唯一的流水号,采用了 select max(xxx) from xxx for update 的方式,这样一来就形成了表锁,与开发人员沟通后流水号用redis产生,去掉了所有的select … for update语句;

2 按理说update 与 insert 都应是很快的,但在慢查询日志中出现了update 语句查询时间上百秒,检查数据行上亿条,一开始真是百思不得其解,后与开发人员深度沟通才找到原因。原来开发人员为了提高查询效率,做了伪物化视图处理,在update与insert时加上了触发器,在触发器中调用存储过程,存储过程中查询一个很复杂的视图后,删除过时的数据,插入新的数据到伪物化视图。由于查询语句未做优化,导致查询时间很长,从而引起update语句的执行时间超长,检查数据行数超多。经优化相应的查询语句,并且优化了触发条件,避免不必要的触发器调用。Update与insert的执行时间回到了1s以内。

3 复杂的select语句,由于查询的是层层嵌套的视图,每层视图中又经常出现left join等,导至大量的全表扫描,查询复杂度很高。对这一类select语句,只能在分析业务的基础上做优化,优化的基本原则是早过滤,晚连接,避免查找不必要的数据,避免不必要的连接;尽量使用索引,避免全表扫描。这类工作只能具体问题具体分析,在实践中摸索经验。经调优后,查询时间都减少到ms级甚至更低。

经过以上调优后,数据库I/O压力明显减小,慢查询日志中相应的慢查询语句消失。

在调优过程中还有几件值得记录的事情

1.一个很低级的错误是 thinkphp 的app_debug忘记关闭,导致性能严重下降, 这是由于部署人员的疏忽造成的,尽管很不应该,但是发生了,所以这一类的开关还是应该检查一下。

2.在慢查询日志中发现了大量的 show columns from ,经分析这是thinkphp造成的,应设置DB_FIELDS_CACHE=true ,这样就不会频繁地调用 show columns from。

按理说show columns from执行也很快,为什么会出现在慢查询日志中呢?原来开发人员为了检查用户的权限,在控制器的基类中执行了对user表的查询,导致大量的select * from user …操作,而在没有设置DB_FIELDS_CACHE=true情况下导致了大量的 show columns from user, 经实验大量并发的show columns from 会导致一些语句在 opening tables阶段阻塞,时间可以长达30s以上。为此打开了mysql的查询缓存,使得大量重复的select * from user …查询可以从缓存中取值(备注:mysql的查询缓存并不是越大越好,太大了由于其上的全局锁可能会导致update ,insert 语句变慢,甚至会使得相应的select 语句在sending data 阶段阻塞)

需要补充说明的是设置DB_FIELDS_CACHE=true后,如果以后数据表字段发生了变化,部署时要注意清除过时的缓存。

3.除了查看慢查询日志进行调优之外,也可以开启 general log 将所有到达MySQL的SQL语句记录下来。一般不应开启此功能,因为log的量会非常庞大。但调优时可以临时开一会儿general log,方便看看到底是一些什么查询操作在执行,有时就可以发现一些不合理的查询序列。为了方便实时监控正在执行的sql语句,可以从网上下载一个免费的小软件:BareTail ,用它打开general_log文件后,它会自动向上滚动最新的内容。

4.考虑到mysql服务器的I/O负担重,可以针对mysql的一些内存参数做调整,原则就是尽量地通过缓存减少对磁盘的读写。内存参数的设置网上相关文献很多,这里就不再详细说明。

5.页面的卡慢除了后台php与mysql的原因外,前端的浏览器上的js代码如果不够优化,也可能导致卡慢,这个问题打算以后专门说明。

6.浏览器与后台服务器的连接数有限制,因此在一个页面上有很多ajax在请求时,可能会有阻塞的现象发生,打开浏览器的开发者工具时应该看到stalled 时间很长而不是waiting时间很长。有一个小现象也值得记录一下。由于php的会话数据默认保存在文件中(备注:不同的会话id对应不同的文件),而在读写会话相关的文件时会加文件锁,thinkphp的控制器方法默认会打开会话,如果这个控制器方法执行时间很长,如果不主动提交会话,则在控制器方法执行完成后才会自动提交会话。在这个过程中文件锁会一直存在并阻塞对同一个会话文件的操作。因此同一个用户在用多个ajax请求或者在同一个浏览器中打开了多个页面时可能会出现卡顿现象。这种卡顿是由于浏览器的连接数限制或者会话相关操作的文件锁造成的,与mysql无关,在调优时应注意区分是前端原因造成的,还是后端原因造成的卡慢。

猜你喜欢

转载自blog.csdn.net/littlezhuhui/article/details/85311036