本文是 Java 低延迟编程系列文章的第一篇。读完本文,您将掌握以下概念:


  • 什么是延迟,作为开发者为什么要关心延迟?

  • 如何描述延迟,数据中的百分比意味着什么?

  • 什么因素会造成延迟?


闲话少说,让我们开始吧。


1. 什么是延迟,为什么延迟很重要?


延迟可以简单定义为执行一个操作耗费的时间。


“操作(operation)”是一种宽泛的说法,在这里我指的是软件系统中值得测量的行为,以及某个时间点该行为的一次执行过程。


例如,在典型 Web 应用中,操作可以是从浏览器提交查询并查看搜索结果;在交易应用程序中,可以是金融交易工具收到价格变动后自动向交易所发送买入卖出指令。通常,操作花费的时间越短,对用户的好处越大。用户更喜欢那些不需要等待的网络应用。回想当初,谷歌比同时代其他搜索引擎最大的优势就是快速搜索体验。交易系统对市场变化的反应越快,交易成功的概率就越高。数以百计的交易公司沉迷于让他们的交易引擎成为华尔街上延迟最低的系统,并因此从中获得竞争优势。


不夸张地说,在高风险的地方,降低延迟可以决定一家企业的成败!


2. 如何描述延迟?


每个操作都有延迟,一百种操作有一百种延迟。因此,不能使用像“操作数/秒”或“秒数/操作”这种单一的指标描述系统延迟,因为这只能用于描述某种操作的单次运行。


乍一看,可以把延迟定义为所有同类操作的平均值。这可不是什么好主意。


取平均值有什么问题吗?请考虑下面这张图:


图片


有几个操作度超过了60毫秒的 SLA 目标(实际上是7个),但平均响应时间却位于 SLA 范围内。一旦采用平均响应时间,红色区域内的所有异常值都会被忽略。然而,忽略的这部分异常值恰恰是对软件工程师来说最重要的数据,即需要关注和梳理的系统性能问题。更糟糕的是,隐藏在这些数据背后的问题在实际生产环境中往往会发生。


另外值得注意的是,实际上很多延迟测量结果可能会像上图看到的那样,偶尔会看到一些随机的严重异常值。延迟从来不遵循正常分布、高斯分布或者泊松分布,你看到更多的可能是多重模态分布延迟。实际上,这就是为什么用平均值或标准差来讨论延迟是无效的。


延迟最好用百分比描述。


什么是百分位数?考虑一组数字,第 n 个百分位数(其中 `0 < n < 100`)将其分为两个部分,较低的部分包含 n% 的数据,较高的部分包含 (100 - n)% 的数据。因此,这两部分的数据总和为 100%。


例如,50%是指一半低于50%,另一半高于50%。百分比更广为人知的说法是中位数。


让我们举几个测量延迟的例子。90%的延迟为75毫秒,意味着100个操作中有90个操作延迟最多为75毫秒,而其余的操作,即100 - 90 = 10,延迟至少为75毫秒。


如果进一步补充,98%延迟170毫秒,这意味着100个操作中有2个操作的延迟达到或超过170毫秒。


如果再进一步补充,99%延迟313毫秒,这意味着每100个操作中有1个操作与其他操作相比有更大的延迟。


事实上,许多系统表现出这样的特征,即使百分比增加延迟也会显著增长。


图片


为什么要担心长尾延迟呢?我的意思是,如果每100个操作中只有1个操作有较高延迟,那么系统性能还不够好吗?


好吧,为了有一个直观的印象,请想象下面这个场景。一个热门网站,90%延迟1秒、95%延迟2秒、99%延迟25秒。假设网站所有页面日浏览量超过100万,也就是说某个页面会有超过10000次加载超过25秒。这时候用户可能会打着哈欠关闭浏览器转而去干别的事情,更糟糕的情况他们会向亲朋好友吐槽这次糟糕的体验。任何在线业务都负担不起这样的长尾延迟。


3. 导致延迟的原因是什么?


最简短的回答是:一切皆有可能!


延迟“抖动”会产生特有形状、出现随机离群值,可归因于下面这些事情:


  • 硬件中断

  • 网络/IO延迟

  • 管理程序暂停

  • 操作系统活动,例如内部结构重建、刷新缓冲等

  • 上下文切换

  • 垃圾收集暂停


这些事件通常是随机的,并不遵循正态分布。


另外,从更高的层次来看 Java 程序运行:


图片


(管理程序、容器对于裸机硬件是可选的,但在虚拟化或云环境中与延迟密切相关)


减少延迟与以下因素密切相关:


  • CPU/缓存/内存架构

  • JVM 架构和设计

  • 应用程式设计:并发、数据结构算法和缓存

  • 网络协议等


上面的图中每一层都很复杂,大大增加了性能优化所需的知识和专业技能,也是时刻需要考虑成本、时间合理性的原因。


但这就是性能工程如此有趣的原因!


我们的挑战是,让应用对所需的每一次操作执行延迟总是保持在合理的较低水平。


说起来容易,做起来难!