1.9 案例研究
案例研究会讲述什么时候该做什么事和为什么要做这些事,如果你刚接触系统性能,把这些关联到自己当前的环境会对你有所帮助。接下来是两个虚构的示例;一个是与磁盘I/O 相关的性能问题,另一个是软件新版本的性能测试。
这些案例研究中所做的事情在本书的其他章节能找到相关解释。此处并不是为了表现方法的正确性或是唯一性,而是为了展示一种执行性能研究的方式,对此你可以好好思考。
1.9.1 缓慢的磁盘
Scott 是一家中型公司里的系统管理员。数据库团队报告了一个支持ticket(工单),抱怨他们有一台数据库服务器“磁盘缓慢”。
Scott 首要的任务是多了解问题的情况,收集信息形成完整的问题陈述。ticket 中抱怨磁盘慢,但是并没解释这是否是由数据库引发的。Scott 的回复问了以下这些问题:
● 当前是否存在数据库性能问题?如何度量它?
● 问题出现至今多长时间了?
● 最近数据库有任何变动吗?
● 为什么怀疑是磁盘?
数据库团队回复:“我们的日志显示有查询的延时超过了1000ms,这并不常见,就在过去的一周这类查询的数目达到了每小时几十个。AcmeMon 显示磁盘在那段时间很繁忙。”
可以肯定确实存在数据库的问题,但是也可以看出关于磁盘的问题更多的是一种猜测。Scott需要检查磁盘,同时他也要快速地检查一下其他资源,以免这个猜测是错误的。
AcmeMon 是公司服务器的基础监控系统,基于mpstat(1)、iostat(1)等其他的系统工具,提供性能的历史图表。Scott 登录到AcmeMon 上自己查看问题。
一开始,Scott 使用了一种叫做USE 的方法来快速检查系统瓶颈。正如数据库团队所报告的一样,磁盘的使用率很高,在80%左右,同时其他资源(CPU、网络)的使用率却低得多。历史数据显示磁盘的使用率在过去的一周内稳步上升,而CPU 的使用率则持平。AcmeMon 不提供磁盘饱和(或错误)的统计数据,所以为了使用USE 方法,Scott 必须登录到服务器上并运行几条命令。
他在/proc 目录里检查磁盘错误数,显示是零。他以一秒钟作为间隔运行iostat,对使用率和饱和率观察了一段时间。AcmeMon 报告80%的使用率是以一分钟作为间隔的。在一秒钟的粒度下,Scott 看到磁盘使用率在波动,并且常常达到100%,造成了饱和,加大了磁盘I/O 的延时。
为了进一步确定这是阻塞数据库的原因——延时相对于数据库的查询不是异步的——他利用动态跟踪脚本来捕捉时间戳和每次数据库被内核取消调度时数据库的栈跟踪。他发现数据库在查询过程中常常被一个文件系统读操作阻塞,一阻塞就是好几毫秒。对于Scott 来说,这些证据已经足够了。
接下来的问题是为什么。磁盘性能统计显示负载持续很高。Scott 对负载进行了特征归纳以便做更多了解,使用iostat(1)来测量IOPS、吞吐量、平均磁盘I/O 延时和读写比。从这些结果,他计算出了平均I/O 的大小并对访问模式做了估计:随机或者连续。Scott 可以通过I/O级别的跟踪来获得更多的信息,然而,他觉得这些已经足够表明这个问题是一个磁盘高负载的情况,而非磁盘本身的问题。
Scott 在ticket 中添加了更多的信息,陈述了自己检查的内容并上传了检查磁盘所用到的命令截屏。他目前总结的结果是由于磁盘处于高负载状态,从而使得I/O 延时增加,进而延缓了查询。但是,这些磁盘看起来对于这些负载工作得很正常。因此他问道,难道有一个更简单的解释:数据库的负载增加了?
数据库团队的回答是没有,并且数据库查询率(AcmeMon 并没有显示这个)始终是持平的。这看起来和最初的发现是一致的,CPU 的使用率也是持平的。
Scott 思考着还会有什么因素会导致磁盘的高I/O 负载而又不引起CPU 可见的使用率提升,他和同事简单讨论了一下这个问题。一个同事推测可能是文件系统碎片,碎片预计会在文件系统空间使用接近100%时出现。Scott 查了一下发现,磁盘空间使用率仅仅为30%。
Scott 知道他可以进行更为深入的分析来了解磁盘I/O 问题的根源,但这样做太耗时。基于自己对内核I/O 栈的了解,他试图想出其他简单的分析,以此来做快速的检查。他想到这次的磁盘I/O 是由文件系统缓存(页缓存)未命中导致的。
Scott 进而检查了文件系统缓存的命中率,发现当前是91%。这看起来还是很高的(很好),但是他没有历史数据可与之比较。他登录到其他有相似工作负载的数据库服务器上,发现它们的缓存命中率超过了97%。他同时发现问题服务器上的文件系统缓存大小要比其他服务器大得多。
于是他把注意力转移到了文件系统缓存大小和服务器内存使用情况上,发现了一些之前忽视的事情:一个开发项目的原型应用程序不断地消耗内存,虽然它并不处于生产负载之下。这些被占用的内存原本可以用作文件系统缓存,这使得缓存命中率降低,让磁盘I/O 负载升高,损害了生产数据库服务器的性能。
Scott 联系了应用程序开发团队,让他们关闭该应用程序,并将其放到另一台服务器上,作为数据库问题的参照。随后在AcmeMon 上Scott 看到了磁盘使用率的缓慢下降,同时文件系统缓存恢复到了它原先的水平。被拖慢的数据库查询数目变成了零,他关闭了ticket 并将它置为“已解决”。
1.9.2 软件变更
Pamela 在一家小公司做性能扩展工程师,负责所有与性能相关的事务。应用程序开发人员开发了一个新的核心功能,但是他们不确定引入这个功能会不会影响性能。在部署到生产环境之前,Pamela 决定对这个应用程序的新版本执行一次非回归性测试。(非回归性测试是用来确认软件或硬件的变更并没有让性能倒退的。)
为了这个测试,Pamela 需要一台空闲的服务器和一台客户负载的模拟器。应用程序团队之前写过一个模拟器,虽然这个模拟器还有诸多限制和一些已知的bug,她还是决定一试,但要确定它能够充分地模拟生产环境的工作负载。
她依照生产环境的配置设置好服务器,从另一个系统向目标开启客户端工作负载模拟器。客户工作负载可以通过研究访问日志来进行分析,不过公司里已经有一个工具在做这件事情,她直接就用了。她还用这个工具来分析同一天不同时间段的生产环境日志,这样来对两个工作负载进行比较。她发现客户负载模拟器虽然可以提供一般性的生产环境工作负载,但是对负载的多样性无能为力。Pamela 记下了这一点后继续她的分析。
这时,Pamela 知道有很多方法可以用。她选择了最简单的那个:增加客户模拟器的负载直至达到一个极限。客户负载模拟器可以设定每秒钟执行的客户数目,其默认值是1000,她之前使用的就是这个值。Pamela 决定从100 客户请求开始,以每次100 为增量逐步增加负载,直至达到极限,每一个测试级别都测试一分钟。她写了一个shell 脚本来执行这个测试,将结果收集到一个文件里供其他工具绘图。
随着负载不断增加,她通过执行动态基准测试来判定限制因素。服务器资源和服务器的线程看起来有大量空闲。客户模拟器显示完成的请求数稳定在大约每秒700 客户请求。
她切换到了新的软件版本并重复相同的测试。这次也是到了700 客户请求就稳定不动了。她分析了服务器,试图寻找限制的原因,但是一无所获。
她把结果绘成图,画出了请求完成率相对于负载的变化情况,以此来观察不同软件版本的扩展特性。新旧两个软件版本都有一个很突兀的上限。
虽然看起来两个软件版本所拥有的性能特性是相似的,但Pamela 还是很失望,因为她找不到是什么因素制约客户数的扩展。她知道她检查的只有服务器资源,限制的原因可能出在应用程序的逻辑上,也可能是其他地方:网络或者客户模拟器上。
Pamela 想知道是不是需要采取一种不同的方法,例如,跑一个固定量的操作,然后记录资源使用的汇总情况(CPU、磁盘I/O、网络I/O),这样就可以表示出单一客户请求的资源使用量。她针对当前的和新的软件版本,按照每秒700 客户的请求量来运行客户模拟器,并测量了资源的消耗情况。当前的软件版本对于给定负载,跑在32 个CPU 上的使用率达到了20%。新的软件版本对于同样的负载,在相同CPU 数目上则是30%的使用率。看得出这确实是一个性能倒退,占用了更多的CPU 资源。
为了理解700 的上限,Pamela 运行了一个更高的负载并研究了在数据路径上的所有组件,包括网络、客户系统和客户工作负载产生器。她还对服务端和客户端软件做了向下挖掘分析。
她把所做的检查都做了记录,包括屏幕截图,以作为参考。
为了研究客户端软件,她执行了线程状态分析,发现这是一个单线程的软件。单线程100%的执行时间都花在了一个CPU 上。这使得她确认这就是测试的限制因素所在。
作为验证实验,她在不同客户系统上并行运行客户端软件。用这种方式,无论当前版本软件还是新版本的软件,她都让服务器达到了100%的CPU 使用率。这样,当前版本达到了每秒3500 请求,新版本则是每秒2300 请求,这与之前资源消耗的发现是一致的。
Pamela 通知应用软件开发人员新的软件版本有性能倒退,她打算对CPU 的使用做剖析来查找原因:看看是哪条代码路径导致的。她强调指出一般性的生产工作负载已被测试过了,但多样性的工作负载还未曾测过。她还发了一个bug,说明客户工作负载生成器是单线程的,这是会成为瓶颈的。
1.9.3 更多阅读
第13章提供了一个更为详尽的案例研究,记录了一个我如何解决特定云计算性能问题的故事。下一章将介绍性能分析方法,其余各章会讲述必要的知识背景和细节。