每年双十一,有多少人盼着阿里的系统崩溃?
文末有彩蛋
刚刚的双十一,天猫的成交额突破 4982 亿元,大家都知道,在这背后是阿里巴巴的程序员熬了无数个通宵,不停的对系统做各种测试和优化才有的成果。
肯定有不少人心里暗暗的想:「哪年双十一阿里系统可以崩溃一次?看看有什么影响?」(如果你也这么想,请在评论处写 “1”)
就像11月5日那天的大断电一样,如果再遭遇人为的高等级破坏,是否还能用非常短的时间让灾备系统自主启动,让一切服务快速重启。会不会出现系统瘫痪,商家无法补货,用户不能下单等状况?
一个成熟的系统应该有两个阶段,从 0 到 1 是「攻城」阶段,在市场或业务上完成突破,抢得一席之地;从 0 到 100 则是「守城」阶段,如何规划,如何迭代,如何优化,如何保护,都是影响成败的大问题。
这里我们可以脱离具体技术和代码,讲讲性能测试和不该出现的「反模式」。
在有些程序员眼里,优化程序性能经常被视作一种「暗黑艺术」。
代码的性能分析往往也存在神秘感,人们常将其看作孤独的「专家们」在绞尽脑汁、深思熟虑之后练就的手艺。
事实上,对代码的性能分析是经验主义和心理学的巧妙融合。其重点在于,一方面是可观测指标的绝对数字,另一方面是最终使用者如何看待这些数字。
所以,如何测试代码的性能是个常见且多变的问题了。
性能测试的类型
比较常见的错误之一是笼统地谈论“性能测试”而不涉及具体内容。事实上,在一个系统上可以执行多种不同类型的大规模性能测试。
延迟测试:终端到终端的交易时间是多少?
吞吐量测试:目前的系统容量能处理多少个并发交易?
负载测试:系统是否能处理某个特定的负载?
压力测试:系统的临界点是什么?
耐久性测试:系统长期运行时会发现哪些性能异常现象?
容量规划测试:当增加额外的资源时,系统的规模是否能按预期扩展?
退化测试:当系统发生部分故障时会出现什么情况呢?
PS:好的性能测试是量化的。它们提出的问题可以得到一个数字化的答案,用于作为实验输出来处理并进行统计分析。
1. 延迟测试
这是最常见的性能测试类型之一,因为它通常与管理层直接感兴趣的系统观测对象密切相关:我们的客户等待交易或页面加载的时间有多长?
这是一把双刃剑,因为延迟测试所要回答的量化问题似乎非常明显,以致它可能会掩盖识别其他类型的性能测试所需要的量化问题的必要性。
PS:对延迟进行调优,其目标通常是直接改善用户体验,或者满足服务等级协议 (SLA)。
然而,即使在最简单的情况下,延迟测试也有一些必须谨慎对待的微妙之处,其中最值得注意的是,对于衡量应用程序对请求的响应程度而言,简单的平均数并不是很有用。
2. 吞吐量测试
吞吐量可能是性能测试时第二个常见的观测量。在某些意义上,甚至可以将其看作等同于延迟。
例如,当进行延迟测试时,有一点非常重要,即在生成延迟结果的分布时,需要说明和控制正在进行的并发交易数。
PS:所观测到的系统延迟应该在已知和受控的吞吐量水平下给出。
同样,我们通常在监控延迟的同时进行吞吐量测试,通过留意延迟分布何时会突然发生变化 —— 实际上是系统的一个“临界点”(也称为“拐点”)来判断“最大吞吐量”。正如我们将看到的,压力测试的目的就是确定这样的点以及它们出现时的负载水平。
然而,吞吐量测试是测量系统开始降级之前观测到的最大吞吐量。
3. 负载测试
负载测试与吞吐量测试(或压力测试)的不同之处在于,它通常被定义为二进制测试,即系统能不能处理这个预设的负载。
在预期的业务事件之前,有时要实施负载测试,比如新的客户或市场有可能使应用程序的流量激增。
还有一些情况下也需要进行此类测试,比如广告活动、社交媒体事件和“病毒式营销”。
4. 压力测试
我们可以把压力测试看作一种确定系统还有多少余量空间的方法。该测试通常是通过将系 统置于稳定的交易状态来进行的,也就是在某个指定的吞吐量水平(通常是当前的峰值) 之下。
压力测试会缓慢地增加并发交易数,直到我们观测的系统数据开始下降。
通过观测数据刚开始下降时的值,就可以确定系统在吞吐量测试中可以达到的最大吞吐量。
5. 耐久性测试
有些问题只有在更长的时间内(通常以天为单位)才会出现。这些问题包括缓慢的内存泄漏、高速缓存污染和内存碎片化,尤其是对于使用并发标记和扫描垃圾收集器(CMS)的应用程序来说,它们最终可能会出现并发模式失败。
为了检测这些问题,通常的方法是进行耐久性测试(也称为浸泡测试)。这些测试在平均 (或高)利用率下运行,但是系统的负载要处于观测之下。测试期间要密切监控资源利用水平,以发现任何故障或资源耗尽的情况。
这种类型的测试在快速响应或低延迟的系统中非常常见,因为这些系统通常无法承受一次 Full GC 周期所造成的 STW 事件的时间。
6. 容量规划测试
容量规划测试与压力测试有许多相似之处,但它们是两种不同类型的测试。压力测试的作用是找出当前系统能够承受的负载,而容量规划测试更具前瞻性,旨在确定系统在升级后能够承受的负载。
因此,容量规划测试通常作为常规计划的一部分进行,而非应对特定事件或威胁。
7. 退化测试
退化测试也称为部分失效测试。虽然关于弹性和失败恢复测试的一般性讨论超出了本书的范围,但是可以这样说,在监管和审查最严密的环境(包括银行和金融机构)中,失败恢复测试不仅会极为严肃地进行,而且通常都会有细致深入的规划。
就目的而言,这里唯一要考虑的弹性测试类型是退化测试。该测试的基本方法是,当系统在相当于通常生产量级的模拟负载下运行时,一个组件或整个子系统突然失去了能力, 此时观察系统会有何表现。
例如,应用程序服务器集群突然丢失机器、数据库突然丢失 RAID 磁盘,或网络带宽突然下降等。
在进行退化测试时,主要观测对象包括交易延迟分布和吞吐量。
部分故障测试有一个特别有意思的子类型,叫作混沌猴(chaos monkey)。这是以 Netflix 公司的一个项目命名的,该项目是为了验证其基础设施的健壮性。
混沌猴背后的理念是,在一个真正的弹性架构中,单个组件的故障不应该导致级联故障,也不应该对整个系统产生实际影响。
混沌猴试图通过随机终止生产环境中实际使用的活跃进程来演示这一点。
为了成功地实现混沌猴类型的系统,公司必须具有最高水平的系统卫生、服务设计和运维优势。然而,这也是越来越多的公司和团队所关注和向往的领域。
将性能测试当作软件开发生命周期的一部分
有些公司和团队喜欢把性能测试看作偶尔的、一次性的活动。然而,有经验的团队往往会持续进行性能测试,特别是会把性能回归测试当作软件开发生命周期(SDLC)中不可或缺的一部分。
这需要开发人员和基础架构团队之间的协作,以控制在任何特定时间内性能测试环境中要出现的代码版本。如果没有专门的测试环境,那么这几乎是不可能实现的。
在讨论了一些最常见的性能测试的最佳实践之后,现在让我们把注意力转到团队可能陷入的陷阱和反模式上。
性能反模式
反模式的频繁出现,使得人们有了这样的结论或怀疑:某些潜在因素要为产生了我们不希望出现的行为负责。
有些反模式乍一看似乎是合理的,其不好的方面并不明显。有些则是不良的项目实践随着时间的推移慢慢累积的结果。
在某些情况下,这些行为可能是由于社会或团队的限制、常见管理技术的滥用或者只是人类(开发人员)的本性导致的。通过对这些不希望出现的特性进行分类和归类,我们就可以开发出一种“模式语言”来讨论它们,并希望将其从项目中消除。
应该始终将性能调优视为一个非常客观的过程,并在计划阶段的早期就设定精确的目标。但是这说起来容易做起来难,因为当一个团队面临压力时,或者在合理的情况下都不能正常运作时,很容易就半途而废了。
很多读者遇到过这样的情况:在一个新的客户端将要上线,或者一个新的特性就要发布时意外出现了服务中断。
如果幸运的话,中断会出现在用户验收测试(user acceptance testing,UAT)阶段;但不幸的是,它经常出现在生产环境中。
这时团队就要手忙脚乱地寻找引发瓶颈的问题并加以修复。导致这样的结果通常是因为没有进行性能测试,或者团队的“专家”假设了某个条件,但是这个条件消失了。
与遵循了良好的性能测试实践并进行了开放、理性的对话的团队相比,以这种方式工作的团队更容易成为反模式的牺牲品。就像许多开发问题一样,往往是人为因素,比如沟通问题,而不是任何技术方面的原因导致应用程序出现问题。
Carey Flichel 在一篇名为“Why Developers Keep Making Bad Technology Choices”的博客文章中提供了一种有趣的分类,文中特别指出了导致开发人员做出错误选择的 5 大原因,接下来我们依次看一下。
# 厌倦
大多数开发人员有过在某个角色中感到厌倦的经历,对一些人来说,这种情况并不会持续很长时间,因为他们会在公司或其他地方寻求新的挑战或角色。但是,公司里可能没有其他的机会,换个地方可能也没有。
大家也许遇到过这样的优秀程序员,他们能够克服困难,甚至可能积极寻求更轻松的生活。然而,感到厌倦的开发人员可能会以多种方式给项目带来损害。
例如,他们可能会引入不必要的代码复杂性,比如直接在代码中写一个排序算法,而这本来使用简单的 Collections.sort() 就够了。他们也可能会使用未知的或不适合当前场景的技术来构建组件,只是希望有个机会用这些技术,以此来解决他们的厌倦感。这又会导致出现下面的问题。
# 填充简历
偶尔,技术的过度使用并不是因为厌倦,而是因为开发人员想找到一个机会,在简历上增加使用某项技术的经验。在这种情况下,开发人员会积极尝试提高自己的潜在薪资和市场竞争力,因为他们即将重新进入就业市场。在一个运作良好的团队内部,很多人不太可能靠这个侥幸成功,不过项目确实可能会因为这个原因而进入不必要的路径。
由于开发人员的厌倦,或为了填充简历而增加了一项不必要的技术,其影响可能是深远而持久的,在原来的开发人员另谋高就后还会持续数年。
# 同侪压力
侪(chái)
在做出选择时,如果关注点没有得到表达或讨论,那所做出的技术决策往往是最糟糕的。问题有可能通过几种方式表现出来。
比如,也许某个初级开发人员不想在团队中的高级成员面前犯错(“负担症候群”),或者某个开发人员担心遇到对某个特定主题不了解的情况。
另一种特别有害的同侪压力,这是来自存在竞争关系的团队,大家都希望表现出更高的开发速度,因此会在没有充分探讨所有后果的情况下就仓促做出关键决定。
# 缺乏理解
因为没有了解当前工具的全部功能,所以开发人员可能会寻求引入新的工具来帮助解决问题。使用适合执行某个具体任务的新的、令人兴奋的技术组件往往很有诱惑力。然而,引入更多技术复杂性也必须考虑与当前工具的实际功能相平衡。
例如,Hibernate 有时被看作简化领域对象和数据库之间转换的解决方案。如果团队对 Hibernate 的理解相当有限,那么开发人员可能会基于在另一个项目中看过的 Hibernate 的 使用情形而对其适用性做出假设。
缺乏理解会导致 Hibernate 的用法过度复杂,甚至导致无法恢复的生产环境中断。
相比之下,使用简单的 JDBC 调用来重写整个数据层,可以让开发人员停留在熟悉的领域中。本书的一位作者曾经讲授过一门 Hibernate 课程,课上有位代表就遇到了完全一样的情况。
他试图学习足够多的 Hibernate 知识,看看是否能让应用程序从中断中恢复过来,但最终不得不用一个周末的时间把 Hibernate 去掉了,这可不是值得羡慕的事。
# 被错误理解的问题 or 不存在的问题
开发人员可能经常会使用某种技术来解决某个特定的问题,但是对问题空间本身并没有进行充分研究。如果没有测量获得的性能值,那就几乎不可能理解特定解决方案是否成功。通常,整理这些性能指标有助于更好地理解问题。
为了避免出现反模式,要确保关于技术问题的交流对团队的所有参与者公开,并鼓励大家踊跃参与,这非常重要。
在事情不清楚的地方,收集事实证据和研究原型可以帮助指导团队做出决策。
一项技术可能看起来很有吸引力,但是如果原型不符合要求,那么团队可也以做出更明智的决策。
小 结
一个系统的性能测试必须根据不同的原因而进行不同类型的测试,但性能测试并不是那么畅通无阻,一些可能会困扰性能测试的问题或反模式问题,只有那些有足够经验的资深程序员才会真正了解。
性能优化是资深程序员必不可少的技能,也是很多大厂招高级技术岗位必备的条件之一,如果你在这方面还没有突破的话,这里推荐这本《Java性能优化实践》。
— 【 THE END 】— 本公众号全部博文已整理成一个目录,请在公众号里回复「m」获取! 3T技术资源大放送!包括但不限于:Java、C/C++,Linux,Python,大数据,人工智能等等。在公众号内回复「1024」,即可免费获取!!