小站迁移到Spartan Host上也有半年多了,尽管到中国的线路在晚高峰时期可以说是很不好,不过总还是可堪一用。但是最近在后台操作的时候总有卡顿,有时候甚至会直接无法打开,JetPack也时常发邮件提醒站点掉线。不得已,只好尝试排查一下。
首先检查一下系统占用,Spartan给的Control Panel给出的占用率高到爆炸,着实吓我一跳。
Spartan监测的CPU占用
接下来用top命令检查一下具体的资源占用情况:

top

top命令可以显示当前运行的进程,及其PID、CPU和内存占用等信息,类似图中的结果。
top命令的结果

在这一步就发现了问题:有一个叫containerd的进程占用了不少CPU。查了一下发现,containerd是docker的组成部分。这个属于遗留问题,是我以前尝试用docker搭建Typecho博客还有几个测速页面遗留下的残迹。这些空壳页面和docker占用的CPU不算很少,但是也很好排查和解决。我检查了一下docker里正在运行的容器:

docker ps -a

发现typecho博客的对应容器还在运行,而且也只有这一个容器。那么把docker从容器、镜像到包都删除就可以解决。
先停止运行的容器,再删除所有容器和镜像,最后结束docker服务:

sudo docker kill $(docker ps -a -q)
sudo docker rm $(docker ps -a -q)
sudo docker rmi $(docker images -q)
systemctl stop docker

然后检查docker的相关包:

yum list installed | grep docker

搜索到三个结果:

containerd.io.x86_64 1.3.9-3.1.el7 @docker-ce-stable
docker-ce.x86_64 3:19.03.14-3.el7 @docker-ce-stable
docker-ce-cli.x86_64 1:19.03.14-3.el7 @docker-ce-stable

卸载掉即可。

yum remove docker-ce.x86_64 docker-ce-cli.x86_64 containerd.io.x86_64 -y

本来以为卸载掉docker就解决问题了,但显然事情没有这么简单。没多久,CPU的负载又上去了。这一次再检查,发现两个惊喜:
一个是MySQL,另外一个是PHP,两个进程的CPU占用率都异常地高。这两个是WordPress的重要组件,当然不能一删了事。既然如此,只好检查一下记录,看看是什么导致了这么高的占用率。
我先从MySQL下手,它有一个很好的功能叫做慢日志。慢日志会记录超过一个时间阈值的查询操作,可以有效地反映出是什么拖慢了运行速度,并导致了高占用和卡顿。当然,首先它得处于开启状态。这里可以用SSH登录MySQL或者使用网页端的phpMyAdmin,先来检查一下:

SHOW VARIABLES LIKE 'slow_query%'

很走运,我恰好开了慢日志:

Variable_name         Value
slow_query_log        ON
slow_query_log_file   /*******/mysql-slow.log

而且查出了慢日志的路径。打开一看,记录竟然有1.8M之大。这恐怕没多少人有耐心细看,当然我也没有。所以MySQL也提供了简单的分析工具mysqldumpslow,据说只要安装了MySQL一般都会带有此工具。不巧的是,我恰巧没有,所以改用了pt-query-digest。有三种安装方式可选:

wget http://www.percona.com/get/pt-query-digest
chmod +x pt-query-digest

wget percona.com/get/percona-toolkit.rpm
rpm -ivh percona-toolkit-2.2.13-1.noarch.rpm

wget percona.com/get/percona-toolkit.tar.gz
tar -zxvf percona-toolkit-2.2.13.tar.gz
cd percona-toolkit-2.2.13
perl Makefile.PL
make && make install

安装完后,可以用如下命令对mysql-slow.log进行分析。

pt-query-digest  /*******/mysql-slow.log > slow_report.log

参考详解 慢查询 之 mysqldumpslow
分析的结果很有趣,占据前几名的慢查询记录是这样的:

# Rank Query ID       Response time  Calls  R/Call  V
# ==== ============ =============== =====  =======  =
#    1 0x665212F**  2337.5847 28.5%   101  23.1444  21.22 SELECT wp_options
#    2 0x9D781DC**   749.3945  9.1%    57  13.1473  12.29 SELECT wp_options
#    3 0xDB78C57**   600.6963  7.3%    40  15.0174  12.70 SELECT wp_terms wp_term_taxonomy wp_term_relationships
#    4 0xE0ED4FC**   515.7171  6.3%    41  12.5785  20.61 SELECT wp_postmeta
#    5 0xC0BA5A2**   491.8532  6.0%    24  20.4939  15.03 SELECT wp_wfconfig
#    6 0x6837082**   390.9320  4.8%    27  14.4790  11.07 SELECT wp_wfblocks?
#    7 0x37DD036**   229.8844  2.8%    17  13.5226  14.36 SELECT wp_usermeta
#    8 0xBC8F025**   192.8420  2.4%    13  14.8340  13.05 SELECT wp_yoast_indexable
#    9 0x6ADC8F4**   192.4062  2.3%    19  10.1266   2.84 SELECT wp_terms wp_term_taxonomy
#   10 0xAC0C338**   146.5066  1.8%    16   9.1567   2.65 SHOW COLUMNS

wp_wf*是wordfence的数据库,看起来插件对MySQL的运行影响不小。为此我清理了一些效果并不很大的插件,比如说Wordfence、JetPack等等。然后回到排第一、二的两个wp_options记录,分析结果的EXPLAIN分别显示:

# EXPLAIN /*!50100 PARTITIONS*/
SELECT option_name, option_value FROM wp_options WHERE  autoload = 'yes'

# EXPLAIN /*!50100 PARTITIONS*/
SELECT option_value FROM wp_options WHERE option_name = 'siteurl' LIMIT 1

本来我以为第一条是WordPress数据库的一处性能优化中提到的那样,是没有建立索引导致的速度缓慢。但实际上已经建立了索引,因此按照文中操作会报错”Duplicate key name ‘autoload’.”好在经过一番网上冲浪得到提示,有可能是安装插件过多而未清理导致的。因此我进入wp_options表检查长的项目,

SELECT LENGTH(option_value),option_name FROM  wp_options WHERE autoload='yes' ORDER BY length(option_value) DESC LIMIT 20;

也确实发现了一些已经卸载的插件所遗留的项目,删除即可。至于第二条,我并没有什么头绪,猜想是当初迁移域名时留下的错误。
在调整优化了MySQL和WordPress后,本站的运行速度快了不少,目前也没有再出现未响应或离线的情况。因此,php的问题暂不做排查和处理,以观后效,如果仍然出现问题或需要调整,我会在下一次优化完后再进行更新。特作此记录,或许对读者亦可资参考。