概述
优化了一次前后端处理不当导致的CPU的一次爆机行为,当然,这和服务器的配置低也有着密不可分的关系,简单的逻辑学告诉我们,要找到真正的问题,进行解决,CPU爆机的关键点在于前后端两个方面,下面针对具体的问题,进行分析和解决。
定位问题
看监控的图表,CPU已经达到了100%,但是内存的使用曲线很平缓(也说明内存没有被合理的使用),大概率是代码或者循环中产生的问题,服务器进程处理产生多条阻塞,产生的积压,导致的崩溃。
服务端Join影响了性能
顺着代码分析,找到了影响性能的几个关键点,服务端导致性能慢的关键点在于18w的用户表分别和26w的评估记录表、88w的训练动作表、19w的用户签到表进行Join所产生的进程处理缓慢,下面我们用explan工具分别看一下所在的性能差别。
Mysql主要看到的是type和rows的指标,下面的语句告诉我们是全量(all)扫描了179223条数据,优化到了range级别的349条。
+----+-------------+-------+------+-------------------+---------+---------+----------+--------+----------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+-------------------+---------+---------+----------+--------+----------------------------------------------+
| 1 | SIMPLE | u | ALL | PRIMARY,origin_id | NULL | NULL | NULL | 179223 | Using where; Using temporary; Using filesort |
| 1 | SIMPLE | a | ref | user_id | user_id | 4 | cc.u.uid | 1 | Using where |
+----+-------------+-------+------+-------------------+---------+---------+----------+--------+----------------------------------------------+
2 rows in set (0.01 sec)
+----+-------------+---------------+-------+---------------+---------+---------+------+------+-----------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+---------------+-------+---------------+---------+---------+------+------+-----------------------------+
| 1 | SIMPLE | cc_assessment | range | user_id | user_id | 4 | NULL | 349 | Using where; Using filesort |
+----+-------------+---------------+-------+---------------+---------+---------+------+------+-----------------------------+
1 row in set (0.01 sec)
和上面的问题差不多,都是全量检索了80w+数据,优化后range方式检索了1.2w+条数据。
+----+-------------+-------+--------+-------------------+---------+---------+--------------+--------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+--------+-------------------+---------+---------+--------------+--------+-------------+
| 1 | SIMPLE | t | ALL | user_id | NULL | NULL | NULL | 881949 | Using where |
| 1 | SIMPLE | u | eq_ref | PRIMARY,origin_id | PRIMARY | 4 | cc.t.user_id | 1 | Using where |
+----+-------------+-------+--------+-------------------+---------+---------+--------------+--------+-------------+
+----+-------------+-----------------+-------+---------------+---------+---------+------+-------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-----------------+-------+---------------+---------+---------+------+-------+-------------+
| 1 | SIMPLE | cc_train_action | range | user_id | user_id | 4 | NULL | 12979 | Using where |
+----+-------------+-----------------+-------+---------------+---------+---------+------+-------+-------------+
1 row in set (0.02 sec)
Mysql以page为基础,采用Be+Tree的结构存储在硬盘中,对硬盘的I/O传输效率非常明显和敏感,一般的CPU爆机可能产生的情况就是代码中的循环和递归使用的不当,还有一种可能的情况就是Mysql的Sql使用的不当导致的。
代码字典式拼接
之前的查询写在了循环里,数据多的时候,Mysql需要进行反复的连接、查询、断开影响性能,这个地方也进行了优化。
$areaList = $this->area_model->get_info(['id' => $areaAllIds], '', '', '', 'id,name');
$areaNameDict = array_column($areaList, 'name', 'id');
foreach ($user_infos as $key => $val) {
//数组拼接
$user_infos[$key]['province_name'] = isset($areaNameDict[$val['native_province_id']]) ? $areaNameDict[$val['native_province_id']] : '';
}
大胆使用内存
因为内存的曲线较为平缓,说明内存不是导致问题的关键行为,PHP-FPM的特性在子进程执行结束也会进行释放,所以在进程执行时要保证内存的合理使用,可以一次性的加载数据。
ini_set('memory_limit', '1024M');
前段的定时器
Http的每一次请求,服务器都会对应开启一个进程,进行处理和响应,前段的小伙伴使用定时器每分钟进行一次请求,导致的直接结果就是服务器进入了多条等待导致的阻塞,直接到CPU打满。
和前端的小伙伴沟通和协商,30分钟请求一次服务,就变的平稳和丝滑了,至此这个问题告一段落了。
最后
我曾经一度认为不停的学习和钻研技术就能做到技术人中的天花板,就可以所向无敌,还是卖炭翁的一句【我亦无他 唯手熟尔】点醒了我,其实就分熟练和不熟练2种 有2点要纠正自己和分享给朋友们,技术人更高维度是要学会合作、沟通和理解,协商的解决问题,Tcp、Http、Udp都是协议,都是请求和响应的双方达成一致,进行的通信。
当然,要保持良好的学习习惯和修炼技能的纯度,也是必不可少的。