昨天登录 SSH 去看服务器状态的时候,磁盘占用就已经100%了(实际上算了一下,97%差不多),不过基本操作还是没表现出问题,心里就合计着等到某天真出了问题再修吧。结果一觉起来,就有人报告网站又崩溃了。没办法,要做到快速响应,赶快修吧。
这篇文章记录了今日维护碰到的两个问题:
- 磁盘占用100%,而且找不到可以删除的文件;
- Discuz X3.2 的
forum_threaddisablepos
表“损坏”。
一、磁盘占用100%
很久之前,就已经出现了匪夷所思的现象:
$ df
$ du / -sh
我们配的是 50G 的硬盘,实际文件占用不到 10G,磁盘占用却每天都在往上涨。
开始(9月初)的时候利用命令找了一下大文件删除:
$ find / -type f -print0 | xargs -0 du -h | sort -hr | head -20
看到是 php-fpm
和 nginx
的日志最大,删。到去年十一月的时候,占用率已经到80%,需要每几天就删除日志(对,没弄 cron
脚本)。占用率到100%的时候页面载入就会出错,数据库读写异常,所以每次一看100%了赶快删日志。
和lan姐讨论了一下,他(嗯,没打错)认为是 inode 的问题,小文件占用了太多空间。我想了一下,很可能,毕竟我们的 info
节数据被分开来按照单个文件的方式存了,而这不是小数量。我还想着在闲时改代码,放数据库里。
科普:小文件也能占用大空间?
在 Windows 上,磁盘分配的空间单元是簇(cluster),文件占用大小(在未设置压缩属性时)是簇大小的整数倍,与文件的对应关系由分区表(partition table)记录。例如,NTFS 的默认格式化选项上,簇大小是 4K(4 KiB),也就是说即使你的文件内容只有 1.5 KiB,资源管理器显示大小为 1.5 KB,也会占用 4 KiB 的磁盘空间。在 Linux 上,对应的概念是块(block)和 inode。
平均一个文件会浪费 0.5 个存储单元的空间,文件一多就体现出来了。文件越小,浪费空间所占比重越大,就出现了许多小文件浪费了大空间的情况。
不过即使是勤劳地删除日志,最后也到了即使删除所有能删的东西磁盘占用率也上了93%的尴尬境地。就到了现在。
今天我找了一下维护的资料,查了一下 inode 的实际占用率:
$ df -i /home
实际才用了6%,说明小文件浪费的空间没达到阈值。那么是什么问题呢?于是我谷歌了一下:“linux df 100% usage, du shows less”。
谷歌同志实在是太强大了,理解了我想找什么。结果的第一条就是我所需要的。
三楼的回答中提到:
This was caused by apache (httpd) keeping large log files in memory which had been deleted from disk.
我不熟悉 Linux 的磁盘分配策略。Windows 下,打开一个文件会自动锁定句柄,在不关闭之前文件是无法删除的。而根据lan姐(解决后问的)所说,Linux 的文件可以随意删除,但是空间释放要到关闭时才进行。(这也就是最大化利用内存映射的体现吧!)
于是按照所提示的运行命令(我们的服务器日志保存在 /var/log
):
$ lsof | grep "/var" | grep deleted
然后就可以看出,是 php-fpm
和 nginx
的日志占用着:
于是重新启动服务来关闭文件并释放(因为已经删除,所以停止服务时会自动释放):
$ service nginx restart
$ service php-fpm restart
于是磁盘占用就恢复正常了:
至此这个问题告一段落。
二、Discuz X3.2 forum_threaddisablepos
表损坏
(注:pre_
是 Discuz X 中的待替换表前缀,在安装的时候可以配置。例如配置为 aaa
,则会创建名为 aaa_forum_threaddisablepos
的表。)
打开任何一个版块,都会报错:
Table ‘database.pre_forum_threaddisablepos’ does not exist.
由于之前也出现过类似帖子表的损坏,我就尝试了一下以前的修复命令。保持 MySQL 运行,使用 mysqlcheck
:
$ ./mysqlcheck --all-databases --auto-repair -u root -p
但是却报告这张表并不存在:
使用 REPAIR TABLE
命令也失败了(REPAIR TABLE
是 MySQL 的方言):
于是我到了表的存储目录下去找了一下这张表:
只存在 .frm
保存的架构数据,不存在表的内容。这更蛋疼了。只好求助于百度(毕竟 Discuz 主要在国内用),搜索表名“forum_threaddisablepos”。还好,前几个回答就有靠谱的。这些问题发表在 Discuz 的官方站点上,但是不知道为什么我访问不了,说是域名解析失败,明明可以 ping
的。幸好可以借助百度快照查看文本内容。
首先找到的线索是,有人说(“有用户说‘有人说’”)是这是一张内存表。
在另一篇帖子中,20楼提供的方案解决了我的问题。
执行语句(注意替换 pre_
):
CREATE TABLE IF NOT EXISTS `pre_forum_threaddisablepos` (
`tid` mediumint(8) unsigned NOT NULL default '0',
PRIMARY KEY (`tid`)
) ENGINE=MEMORY DEFAULT CHARSET=gbk;
最后能用了。