摘要: I/O 线程阻塞是Java 程序经常出现的问题之一,此类故障发生时 Java 程序的请求、响应在 I/O 线程向操作系统 Socket Buffer 读/写过程中发生阻塞,由于在业务代码插桩无法观测到 I/O 线程的工作情况和性能表现,因而导致故障非常隐蔽和难以诊断定位。通过本篇案例您将了解到,某银行的开发工程师如何使用 eBPF 技术带来的零侵扰追踪能力,在某次分布式核心交易系统上线信创平台的非功能测试(性能压测)故障诊断中,用 3 分钟时间锁定 Java 程序 I/O 线程阻塞。

0x0: 故障背景

近期,某银行分布式核心交易系统 XX 中心业务按计划即将上线华为信创云,但在上线前的非功能测试(性能压测)过程中,业务响应时延不定时劣化,原因不明。

XX 中心的业务程序使用 Java 语言开发(SOFARPC 框架),采用 Netty 作为网络框架,该业务流程涉及 ClientGatewayApp-1App-2,端到端业务访问路径如下:

  • 1)Client 使用 SOFARPC 协议访问 Gateway 的接口;
  • 2)Gateway 经过七层负载均衡将 SOFARPC 协议的 Request 转发至某个 App-1
  • 3)App-1 访问 App-2

eBPF 零侵扰分布式追踪 3 分钟锁定 Java 程序 I/O 线程阻塞_App

DeepFlow 通过 eBPF 以零侵扰的方式对每一次请求经过每一个位置时( 观测点 1~8 )的性能进行全链路采集和观测,整个过程对应用程序无需搬运或注入代码,也无需任何重启操作

0x1: 故障诊断

在 DeepFlow 可观测性平台使用 “5W 方法” 对故障快速诊断。

eBPF 零侵扰分布式追踪 3 分钟锁定 Java 程序 I/O 线程阻塞_可观测性_02

什么是 “5W 方法”?

从宏观到微观有序调阅可观测性数据,逐步回答如下 5 个问题,可以快速、有效诊断出问题根因,称之为“5W 方法”:

  • Who is in trouble?
  • When is it in trouble?
  • Which request is in trouble?
  • Where is the root position?
  • What is the root cause?

1. 确定首个观测对象

故障诊断的第一步,首先要回答 “Who is in trouble” 的问题,从而确定首个观测的对象。

此次性能压测的故障诊断场景中,已明确知道 Gateway ——> App-1 路径的响应时延存在异常,因此将其作为首个观测对象,在 DeepFlow 的「追踪」-「路径分析」功能入口检索该路径。

eBPF 零侵扰分布式追踪 3 分钟锁定 Java 程序 I/O 线程阻塞_DeepFlow_03

2. RED 指标监控,找出时间点

故障诊断的第二步,要回答 “When is it in trouble” 的问题。

打开 Gateway ——> App-1 路径的「右滑窗」,在其中观测 RED 指标(请求量、错误率、响应时延)。

由于分布式核心系统的业务等级非常重要,因此选择观测响应时延指标中的“最大时延”,并找出问题发生时间点

通过分析该路径的最大时延,我们发现在压测阶段周期性出现慢响应(大于 2 秒),于是我们可以将右滑窗的观测时间范围缩小到问题时段(20:20~21:00)。

eBPF 零侵扰分布式追踪 3 分钟锁定 Java 程序 I/O 线程阻塞_应用程序_04

3. 调用日志检索,找出慢响应

当确定问题发生时间段之后,第三步便要回答 “Which request is in trouble” 的问题。

在「右滑窗」的「调用日志」中过滤或排序,在问题时段找到十余次慢响应请求。

eBPF 零侵扰分布式追踪 3 分钟锁定 Java 程序 I/O 线程阻塞_DeepFlow_05

4. 调用链追踪,找出根因位置

找到慢响应的请求列表后,第四步便可以通过调用链追踪,回答 “Where is the root position” 的问题。

选取其中一次约 2s 的慢响应请求做「调用链追踪」,经过追踪发现在观测点 8 的左侧存在约 2s 的空缺,说明:

  • 观测点 7(App-2 的 Pod 网卡)已及时收到 Request;
  • 观测点 8(App-2 的应用进程)等待约 2s 才发起 read() 读取该次 Request。

因此,根因位置是 App-2 的应用进程。

eBPF 零侵扰分布式追踪 3 分钟锁定 Java 程序 I/O 线程阻塞_故障诊断_06

5. 根因分析

第五步,对根因位置进行分析,回答 “What is the root cause” 的问题。

为什么 App-2 在网卡已收到 Request 后约 2s 才发起 read() 的 Syscall?

答案很简单:Java 程序 Netty 的 I/O 线程繁忙

eBPF 零侵扰分布式追踪 3 分钟锁定 Java 程序 I/O 线程阻塞_应用程序_07

Java 程序使用 Netty 网络框架时,当多路 Client 发起连接(TCP Connection)注册,Netty 的 BossGroup 会在 I/O 线程池(即 Woker Group)中选择一个 I/O 线程(Worker EventLoop)负责 TCP 建连处理(三次握手),接收应用请求,发送应用响应,因此 I/O 线程池中的一个 I/O 线程会被多个 TCP 连接所复用。

因此,虽然 Netty 的 NIO 通信模型很好的解决了高并发场景下的业务处理性能,并通过 I/O 线程池很好的控制了高并发量对线程资源的消耗,但 Netty 需要显式配置明确限制 I/O 线程数量(默认取值:2 * CPU 核数),在瞬时请求量大的场景下 I/O 线程仍然存在拥塞风险,当某个 I/O 线程负责的 TCP 连接出现大并发时,有可能发生 Request 到达 Socket 的 recv buffer 后不能被 I/O 线程及时 read 的情况。

6. 修复方案及验证

锁定故障根因是“Netty 的 I/O 线程阻塞”后,开发人员在 JVM 中修改应用程序启动参数:java -XX:ActiveProcessorCount,将取值调整为 实际 CPU 核数 的 2 倍,达到 I/O 线程超分的效果(Netty 中 I/O 线程数量默认取值 2 * ActiveProcessorCount,此时等于 4 * 实际 CPU 核数)。

通过调整 ActiveProcessorCount 参数,将应用程序 I/O 线程数量提升到原来的 2 倍之后,经过持续压测验证,问题解决。

实际生产中,可以根据应用程序的特性(I/O 密集型、CPU 密集型)来自定义线程数量,以达到更好的应用程序性能。对高度 I/O 密集型应用程序,可以增加 I/O 线程数量;对高度计算密集型应用程序,可减少 I/O 线程数量。

0x2: 总结

eBPF 零侵扰分布式追踪 3 分钟锁定 Java 程序 I/O 线程阻塞_DeepFlow_08

从该案例中我们不难发现,在传统 NPM 监控和 APM 监控体系下,操作系统内核、应用程序均存在大范围的盲区,导致故障定位极其困难,经常发生 APM 、NPM 呈现的服务性能不一致的情况,从 NPM 监控明显看到服务端应用程序响应慢,但从 APM 监控却认为所有请求的响应性能指标均正常,因而故障诊断中的边界不清来回拉锯甚至互相指责非常普遍。

DeepFlow 通过 eBPF 技术实现的零侵扰(Zero-Code)的分布式追踪,实现了任意一次应用请求(Any Request)从物理网络到容器网络、操作系统、应用进程的全技术栈(Full-Stack)、端到端(Real End-End)追踪能力,打通了应用运维、系统运维、网络运维的技术栈边界,构建统一的运维观测数据湖,从而将故障诊断效率提升到 3 分钟以内。

0x3: 技术 Tip——Netty 网络框架数据包读取过程

Netty 网络框架使用 OS Kernel 的 epoll 机制在内核空间、用户空间之间读/写数据。

那么 Netty I/O 线程如何与 OS Kernel 交互并获取通信对端发来的 Request 数据包呢?

eBPF 零侵扰分布式追踪 3 分钟锁定 Java 程序 I/O 线程阻塞_可观测性_09

可以通俗的描述为如下过程:

  1. 首先,Netty 的 Worker EventLoop(I/O 线程)调用 Kernel 中 epollepoll_wait() 方法,并进入阻塞状态,等待 epoll 的通知;
  2. 当网卡接收到 Request 数据包后,通过硬中断通知 Kernel,Kernel 处理数据包并放入 Socket 的 recv buffer;
  3. 然后,epoll 监测到 Socket 的 recv buffer 有数据包到达后,将可读的 Socket 列入就绪的事件列表;
  4. 再然后,Worker EventLoopepoll_wait 上被唤醒,并返回就绪的事件列表;
  5. 最后,Worker EventLoop 调用 read() 方法从操作系统的 recv buffer 中读取 Request 数据包。

至此,Netty 完成一次从 Kernel 的 Socket 读取数据的完整过程,如果 Worker EventLoop 没有其他需要读/写的数据,则再次回到第 1 步,调用 epollepoll_wait() 方法,进入阻塞状态,等待下一次数据包到达后的 epoll 通知,循环往复。

0x4: 什么是 DeepFlow

DeepFlow 是云杉网络开发的一款可观测性产品,旨在为复杂的云原生AI 应用提供深度可观测性。DeepFlow 基于 eBPF 实现了应用性能指标、分布式追踪、持续性能剖析等观测信号的零侵扰(Zero Code)采集,并结合智能标签(SmartEncoding)技术实现了所有观测信号的全栈(Full Stack)关联和高效存取。使用 DeepFlow,可以让云原生及 AI 应用自动具有深度可观测性,从而消除开发者不断插桩的沉重负担,并为 DevOps/SRE 团队提供从代码到基础设施的监控及诊断能力。

GitHub 地址: https://github.com/deepflowio/deepflow

访问  DeepFlow Demo,体验零侵扰、全栈的可观测性。