在上篇中,我们讨论了性能优化的目标,以及性能分析常用的一些指标,那我们就来看看资源在性能中锁起到的作用。
核心资源使用
一般而言,复杂的应用程序必然需要将各项资源整合在一起,完成一系列复杂的业务逻辑。
单台机器是进程执行的载体,在单机上有着非常明确的资源边界,比如说一台24U96G的机器,搭载了12块SATA盘,并有两块千兆网卡,那么我们能看到的就是如下的数据:
- 24核CPU,对于CPU密集型的程序,可以容纳24个线程同时工作,对于IO密集型的程序,可以容纳48个线程工作;
- 96GB内存,内存上暂时不做讨论,笔者遇到的绝大部分情况,这个内存大小绝对足够;
- 12块机械盘,总IO大约1200MB/s,单磁盘IO为100MB/s;
- 两块千兆网卡,理论总IO大约为250MB/s,单网卡IO为125MB/s。实际上总IO一般为200MB/s,单网卡IO为100MB/s。
在做优化的时候,需要对机器的硬件特性有一个非常清晰的认识,对比实时机器监控,了解每一项资源目前的使用情况(百分比),才能根据它及时的调整设计和代码实现。
木桶原理
木桶原理,说的就是一个木桶的盛水量取决于最短的那块木板的高度。
这里说的资源问题,其实更应该是个套管原理,也就是说,若干根套在一起的管子,流量取决于最细的那根管子。
我们在分析性能的时候,会优先找出当前瓶颈所在,我们在这里虚构一个单机系统吧。
假设这是一个门禁系统,接收用户传来的身份信息,从DB中查询访问权限,然后判断是否有权限进入,无论是否有权限,都会写入当前访问的相关信息到DB中,如下图:

对于每一层的说明如下:
模块 |
分层 |
核心资源 |
职责 |
HTTP接口 |
数据接入层 |
网络IO、CPU |
处理HTTP层协议,反序列化用户请求 |
门禁逻辑判断 |
数据处理层 |
CPU |
结合权限数据和当前访问数据,综合判断是否有访问权限(由数据引擎来计算),以及进行访问记录的生成 |
权限数据处理 |
数据处理层 |
CPU |
根据访问数据查找权限规则(包含一定的权限规则扩散查询),调用数据库接口查询 |
访问数据格式化写入 |
数据处理层 |
CPU
|
处理访问记录,对数据格式进行一定处理,转换成数据库所需要的格式,调用数据库接口写入 |
数据库 |
数据存储层 |
CPU、磁盘IO |
存储权限数据和访问数据,提供标准数据库接口给上层模块 |
这样一个系统,我们考虑每一种核心资源成为瓶颈的可能性:
CPU,现实情况中CPU往往会成为系统短板,在发生这种情况的时候,一切行为都可能发生,比如说机器无法处理TCP连接的建立,或者门禁逻辑判断非常缓慢,原本1s可以完成的操作需要10s以上才能完成。
网络IO,假如外部请求量足够大(或者协议设计不合理,导致请求体过大),网络IO可能已经无法满足需要,最直接的表现就是网卡被打满,此时即使CPU和磁盘IO并未打满,系统也无法承载更高的吞吐量。
磁盘IO,如果数据处理层的程序存在不合理的设计,权限数据查询和访问数据写入都有大量的冗余数据,那么很可能系统大多数数据会等待在IO上面,此刻CPU即使很空闲,系统也无法支撑更高的吞吐。
参考我们前文提到的性能优化方法论,其实这里在检测到某项资源是短板的时候,就去优化它,直至下一项资源成为短板,循环往复不断提升短板的高度,直至性能达标。
实用命令
这里列举一些实用的命令来查看系统资源消耗(但不包含详细的说明,具体的可以谷歌或者百度)。
top命令,该命令可以看到系统中各个进程(线程)的CPU、内存使用情况,一般关注的是某pid对应的CPU使用率,100%代表该进程用满了一个核,在多核机器上,经常会出现进程占满多个核的情况,这往往都是可能存在优化空间的地方。用 top -p <pid> -H 还可以看到进程中每个线程的CPU使用情况。

htop命令,是top命令的升级版,除了可以看到top中的信息,还可以直观的看到每个CPU和内存使用百分比,降低了top命令使用的门槛。

dstat命令,用来查看磁盘IO、网络IO、CPU使用率、上下文切换等多种类型的资源使用。

Go性能分析常用工具
在Go语言应用程序中,最常用的是go pprof,它可以帮助我们获取Go程序内部的诸多运行状态。这里就不一一赘述了大家可以谷歌或百度查看细节,值得一提的就是火焰图(Flame graph),是一个非常好非常直观的性能分析工具。
火焰图如下,每一簇火焰就是一个函数的调用栈,通过它的宽度可以知道它所占的CPU比例,通过CPU比例对比代码复杂度,可以比较快速的找到性能问题所在。
