性能:使用有限的资源在有限的时间内完成工作。 最主要的衡量因素就是时间,所以很多衡量指标,都可以把时间作为横轴。
加载缓慢的网站,会受到搜索排名算法的惩罚,从而导致网站排名下降。 因此加载的快慢是性能优化是否合理的一个非常直观的判断因素,但性能指标不仅仅包括单次请求的速度,它还包含更多因素。
1、性能指标:吞吐量QPS/TPS/HPS、响应速度 2、响应时间:平均响应时间AVG、百分位数 3、并发量 4、秒开率 5、正确性
现在市面上大多数都是分布式的高并发应用,而现在最常用的衡量指标就是吞吐量和响应速度。
这里给定几个确定的开发常用词:
QPS 代表每秒查询的数量, TPS 代表每秒事务的数量, HPS 代表每秒的 HTTP 请求数量等, 这都是常用的与吞吐量相关的量化指标。
需要注意的是:在性能优化的时候,我们要搞清楚优化的目标,到底是吞吐量还是响应速度。
有些时候,虽然响应速度比较慢,但整个吞吐量却非常高,比如一些数据库的批量操作、一些缓冲区的合并等。虽然信息的延迟增加了,但如果我们的目标就是吞吐量,那么这显然也可以算是比较大的性能提升。
一般对于普通的业务来说,优化主要侧重于响应速度,如果响应速度提升了,吞吐量也就提升了。
而现在的环境是追求高并发、高可用、能够抵御住双十一特别大人流的场景、甚至可能还会夹带有云原生的概念,而这些业务场景,两者是都需要的。
如何计算响应时间呢?
长尾危害:假设,一个接口提供服务B,有1%的可能性响应时间大于1s,如果此刻一个上游服务A需要完成一次查询,需要同时查询100次的话,那么服务A响应时间超过1s的概率是63%。
0.99的概率是小于1s,100次的概率是0.99^100 = 0.37,小于1s响应的时间是37%的概率。
那么请求大于1s的概率就是63%。
即使服务处理时间超过1秒的比例仅为 0.01% ,当需要同时查询的实例数(Numbers of Servers)达到2000时,服务延时大于1秒的请求数将超过18%。
传统解决接口超时问题可能通过重试,在一次请求发送之后等待指定的超时时间,如果没有返回则再请求一次,最差情况下要消耗 2 倍的超时时间。
而双发机制则不然,在发送一次请求后等待 P90(在 T1 时间内有 90% 的请求都能返回则称 P90=T1,通常系统的 P90 和程序设置的超时时间相比小很多)时间。
如果请求没有返回则在此刻再次发送一次请求,在超时时间内,这两个请求中取最快返回的那个。
当然,这里有个防雪崩机制,假如,超过一定数量的请求(比如 15%)都在进行双发,则认为服务整体有问题,会自动停止双发。实践证明,双发机制的去长尾效果非常明显
话说回来,为了解决平均响应时间的缺点,同时还要衡量响应时间,我们引入了百分位数概念。
它的意义是,超过 N% 的请求都在 X 时间内返回。比如 TP90 = 50ms,意思是超过 90th 的请求,都在 50ms 内返回。
这个指标也是非常重要的,它能够反映出应用接口的整体响应情况。比如,某段时间若发生了长时间的 GC,那它的某个时间段之上的指标就会产生严重的抖动,但一些低百分位的数值却很少有变化。
我们一般分为 TP50、TP90、TP95、TP99、TP99.9 等多个段,对高百分位的值要求越高,对系统响应能力的稳定性要求越高。
在这些高稳定性系统中,目标就是要干掉严重影响系统的长尾请求。这部分接口性能数据的收集,会采用更加详细的日志记录方式,而不仅仅靠指标。比如,将某个接口,耗时超过 1s 的入参及执行步骤,详细地输出在日志系统中。
并发量是指系统同时能处理的请求数量,这个指标反映了系统的负载能力。
在高并发应用中,仅仅高吞吐是不够的,它还必须同时能为多个用户提供服务。并发高时,会导致很严重的共享资源争用问题,我们需要减少资源冲突,以及长时间占用资源的行为。
针对响应时间进行设计,一般来说是万能的。因为响应时间减少,同一时间能够处理的请求必然会增加。值得注意的是,即使是一个秒杀系统,经过层层过滤处理,最终到达某个节点的并发数,大概也就五六十左右。我们在平常的设计中,除非并发量特别低,否则都不需要太过度关注这个指标。
现在的用户,如果上网,试想一下,打开一个页面如果要5-6秒那这个网页或者说APP估计评分肯定非常低,秒开是一种特别需要重视的用户体验。
在进行测试的时候,发现接口响应非常流畅,把并发数增加到 20 以后,应用接口响应依旧非常迅速。
但等应用真正上线时,却发生了重大事故,这是因为接口返回的都是无法使用的数据。
其问题原因也比较好定位,就是项目中使用了熔断。在压测的时候,接口直接超出服务能力,触发熔断了,但是压测并没有对接口响应的正确性做判断,造成了非常低级的错误。
理论方法有很多,如木桶理论、基础测试、Amdahal定律等等。
一只木桶若想要装最多的水,则需要每块木板都一样长而且没有破损才行。如果有一块木板不满足条件,那么这只桶就无法装最多的水。
能够装多少水,取决于最短的那块木板,而不是最长的那一块。
木桶效应在解释系统性能上,也非常适合。组成系统的组件,在速度上是良莠不齐的。系统的整体性能,就取决于系统中最慢的组件。
比如,在数据库应用中,制约性能最严重的是硬盘的 I/O 问题,也就是说,硬盘是这个场景下的短板,我们首要的任务就是补齐这个短板。
基准测试(Benchmark)并不是简单的性能测试,是用来测试某个程序的最佳性能。
应用接口往往在刚启动后都有短暂的超时。在测试之前,需要对应用进行预热,消除 JIT 编译器等因素的影响。而在 Java 里就有一个组件,即 JMH,就可以消除这些差异。
在优化的时候不能凭借对代码的熟悉来猜测系统的问题所在,一般来说,复杂的业务系统往往有多个影响因素,我们应该将性能分析放在第一位,而不是把性能优化放在第一位。
进行性能优化时,我们一般会把分析后的结果排一个优先级(根据难度和影响程度),从大处着手,首先击破影响最大的点,然后将其他影响因素逐一击破。
有些优化会引入新的性能问题,有时候这些新问题会引起更严重的性能下降,你需要评估这个连锁反应,确保这种优化确实需要,同时需要使用数字去衡量这个过程,而不是靠感觉猜想。
个体请求的小批量数据,可参考价值并不是非常大。响应时间可能因用户的数据而异,也可能取决于设备和网络条件。
合理的做法,是从统计数据中找到一些规律,比如上面所提到的平均响应时间、TP 值等,甚至是响应时间分布的直方图,这些都能够帮我们评估性能质量。
虽然性能优化有这么多好处,但并不代表我们要把每个地方都做到极致,性能优化也是要有限度的。程序要运行地正确,要比程序运行得更快还要困难。
计算机科学的鼻祖"Donald Knuth" 曾说:“过早的优化是万恶之源”,就是这个道理。
正确的做法是,项目开发和性能优化,应该作为两个独立的步骤进行,要做性能优化,要等到整个项目的架构和功能大体进入稳定状态时再进行。