TT Lab
开始
学习 学习路径 课程

慢的不是一个请求,而是全部

留下什么,又该为什么叫醒人

在 TT Lab 中继续学习

一句话总结

事件循环延迟和堆使用,只有在进程内部才能看准。从外部看到的 CPU 使用率,分不清被堵住的进程和忙碌的进程。所以要从内部取出指标并保存下来,告警也不是针对平均值,而是要针对长尾和持续时间。

为什么需要它

事件循环被堵住期间,CPU 使用率接近 100%。可是在把活干得非常好的时候,也是 100%。只凭从外部看到的指标,无法区分二者,所以“CPU 很高”这样的告警,对这起事故什么也说明不了。反过来,也有事件循环被堵住、无法响应,CPU 却很清闲的情况——那是线程池被占满、正在等待的时候。

延迟日志同样不够。响应时间是已经发生的事的结果,而要看着这个结果去回溯原因,就得靠人用眼睛去对照“同一时刻别的请求是不是也慢”。如果在进程内部把事件循环停顿的时间本身测下来,就能跳过这一步。留下来的不是一长串变慢的请求列表,而是一行“12 点 04 分 20 秒,事件循环停顿了 410ms”。

工作原理

Node 为这个目的提供了两个工具。一个是前面见过的 perf_hooks.monitorEventLoopDelay(),另一个是 performance.eventLoopUtilization()。二者回答的是不同的问题。

monitorEventLoopDelay() 以分布的形式给出晚了多少。因为是直方图,可以直接取出 percentile(99) 和 max,值的单位是纳秒,采样间隔的默认值是 10ms(第 1 个模块里见过的底数就来自这里)。定期读取并 reset(),就成了按时间段的分布。

eventLoopUtilization() 给出有多忙。官方文档把这个值解释为“事件循环在事件提供者(例如 epoll_wait)之外度过的时间的比例”(perf_hooks)。它看上去与 CPU 使用率类似,其实是不同的值——它只看事件循环的统计,不看 CPU。如果把上一次调用的结果作为参数传入,它会返回这段时间内的变化量,所以要得到按时间段的利用率,不必自己做减法。这个 API 是在 Node 14.10.0 中引入的。

内存一侧由 process.memoryUsage() 负责。rss 是操作系统为这个进程分配的物理内存,heapUsed 是 V8 实际在使用的量。在背压事故中,二者会一起上升,尤其是堆积在流缓冲区里的 Buffer,在 external 一侧也能看到。在第 4 个模块的实测中,无视背压的一方 RSS 增加了 131MB,遵守背压的一方则没有——同样的代码,同样的输入,只差一行。

const h = monitorEventLoopDelay({ resolution: 20 });
h.enable();
setInterval(() => {
  const p99ms = h.percentile(99) / 1e6;      // 나노초로 나온다
  const maxMs = h.max / 1e6;
  const rssMB = process.memoryUsage().rss / 1048576;
  h.reset();                                  // 다음 구간을 위해 비운다
  // 여기서 남긴다 — 로그 한 줄이든 지표 노출이든
}, 10000);

在现场相遇的样子

告警该挂在哪里,才是真正困难的部分。守住三点,一般就能得到既安静又有用的告警。

第一,不要挂在平均值上。 正如第 1 个模块里看到的,一次 300ms 的停顿几乎不会让平均值动。要用 p99 和 max。

第二,不要因为一次尖峰就叫醒人。 刚启动之后的编译、偶尔运行的大 GC、部署之后填充缓存,都会偶尔出现一次大值。要像“p99 超过阈值的状态持续 5 分钟以上”这样,把持续时间放进条件里。一次尖峰留作记录,不叫醒人。

第三,阈值要测出来再定。 本课程的实验中每个人都测了基线,就是这个原因。如果不知道安静状态下的 p50 是多少,正常负载下 p99 在哪里,阈值就成了猜测。要有“基线的几倍”或“响应时间预算的几分之一”这样的依据,以后才有人能改这个数字。

内存告警也是同样的原则。比起绝对值,增长速度更好。背压事故是线性上升到上限的,所以用第 4 个模块里做出来的算术,预先算好“照现在的速度还有几分钟”,就能在碰到上限之前采取行动。碰到上限之后才来的告警,已经近乎是对一个被重启的进程的讣告。

最后,这些指标要按进程分别来看。如果启动多个 Worker 或用集群运行,哪怕只有一个进程被堵住,整体平均值也毫无异常。如果不在指标上加进程标识,就会产生与第 4 个模块里看到的同一类障眼法。

下一项测验要确认什么

确认事件循环延迟和利用率各自回答什么问题,为什么用从外部看到的 CPU 使用率无法辨别这起事故,以及为什么告警要挂在长尾和持续时间上,而不是平均值。前四个模块中亲自测出的数字,就是直接的依据。