八、性能危机——CPU、内存、磁盘,成本啊……

CPU危机

其实这个 CPU 危机,就是上一章里挖的坑。矿池经过几天的发展,四核的机器也几乎已经满了,特别到了周末,挖矿的人也开始变多。必须要想办法优化了。

优化的过程充满了戏剧性,H 神抱着试一试地心理,搜了一下,发现 Go 语言可以调用 C 语言写的 so 模块。然后 H 神又抱着试一试的心理,试了一下两个 Go 和 C 版本 share 检查程序的速度,发现居然 C 比 Go 快 10 倍!于是这个问题就这么解决了,此时距离矿池上线刚刚一周……(要是一开始就知道可以 Go 调用 C 就好了,也免去了 H 神花了好几天把 C 的代码翻译成 Go 的,现在又要亲手把它废掉……)

那些年 Go 语言刚出来,宣传里面有提到说它是编译语言,性能非常好,堪比 C。大家听后都觉得顺理成章,都没想着怀疑一下,结果在我们这个计算密集的程序里,居然有 10 倍的性能差距。

新版上线之后,CPU 使用率有了非常显著的降低,从此我们再也没担心过 CPU 的成本。有趣的是,就在我们更新新版之后的第二天,矿池的用户又开始猛增。要是没有 H 神的及时优化,CPU 占用应该已经到了红圈的位置了。


cpu
优化前后 CPU 使用率

内存危机

内存问题其实早在矿池上线第一天就出现了,一直在优化,从来没解决。CPU 的瓶颈解决之后,内存更是直接站在了瓶颈的位置上。DigitalOcean 上,CPU 和内存是搭配出售的,如果我选双核,那就只能搭配 2G 或者 4G 的内存;如果选四核,那就只能搭配 8G 的内存。现在 CPU 用四核绰绰有余,但是 8G 内存还是非常吃紧。还是直接看图吧。


memory
优化前内存使用率

可以看出内存的增长速度非常可怕,大概半天时间就会内存溢出,导致矿池挂掉。图里面的每次内存直线下降,都是因为重启了矿池,可能是因为挂掉重启或者升级策略主动重启。

照理来说,重启前后的内存消耗应该是相当的,可能少数用户会因为重启矿池而放弃重连,大部分用户还是会连回来的。但是图片里的这个直线下降的效果可不是这么说的,重启前后的内存差异非常的大。这只有一种可能,内存泄露了。连接矿池的矿机可能处于各种不同的网络环境下,断线是常有的事情,所以矿机一般都有断线自动重连的功能。再加上有些用户只在开机的时候顺便挖个矿,“翻台率”其实非常高。要是之前的内存不能及时释放,那最后肯定就是图中的效果了。

其实Go语言是自带内存回收机制的,当时的版本是 Go 1.2,传说这个版本的内存回收还不太成熟。我们自己体验到的就和传说中的一样。还有传说 Go 1.3 会改好 GC 问题(据R神介绍,实际上到了 1.5 版才算改好了),不过估计还得等大半年吧,可能等不到那一天矿池就倒闭了。那剩下的路子似乎只能是少申请内存:申请次数要少,能公用的内存就公用;申请的量也要少,这样至少可以减缓增速,多活几天。

优化的过程当然是非常的苦逼,自己挖的坑就得自己填,当初快糙猛写的代码,内存用的很随意,现在就一个个改吧。大多数还是非常好改的。除了直接改内存,我们还尽可能缩减“go”关键词的使用,每一次“go”都会有一些开销,积少成多。最早我们为了效率,把逻辑上可以并发的所有操作全都 go 了,经过这次精简,每个矿机只保留两个协程,一个等新块(与主程序通信),一个等 share(与矿机通信)。

经过两天的改进,内存终于稳定下来了,基本可以持续运行了。

硬盘危机

硬盘危机一开始还真的没想到,像我这种有数据完整性强迫症的人,特别不喜欢删数据,终于在矿池运行三周之后,硬盘容量告警了。实际上最占硬盘的只有两部分,一个是日志,一个是数据库。日志其实就是存储了每个用户的 IP,通信交互等情况,用于紧急的时候排查问题;数据库存的是所有的账单信息,每个人为什么这么发钱,根据什么算的。

随着时间的积累,这两部分数据都越来越大。当然我们也一拖再拖,一直拖到 1月12日,如果这天再不备份数据库,第二天连备份数据库的空间也没了……

其实硬盘危机相比前面两个还是很好解决的,日志就打包之后就传到另一台备份机上。数据库每天凌晨定期热备,然后只保留一周的流水,和当前的状态数据,剩下的数据全都直接删掉。备份文件同样也是压缩传到备份机上。这个备份机其实也是 VPS,只不过是内存少硬盘大而已。我们选了一台 512G 硬盘的备份机,可以用很久了。相比之下矿池的 Web 节点和矿池服务节点都只有 30G 硬盘。

当然,做这三个优化其实都是因为成本问题。我们在做优化的时候,土豪 F 神一边围观我们优化,一边幽幽地说“升级机器”。土豪的视角还是很先进的,我们其他人只关注了硬件成本,而没有关注人力或者时间成本。特别当我们在做内存优化的时候,我们就已经错过了内存币矿池的先手机会。