pfinder实现原理揭秘
在现代软件开发过程中,性能优化和故障排查是保证应用稳定运行的关键任务之一。Java作为一种广泛使用的编程语言,其生态中涌现出了许多优秀的监控和诊断工具,诸如:SkyWalking、Zipkin等,它们帮助开发者和运维人员深入了解应用的运行状态,快速定位和解决问题。在京东内部,则使用的是自研的pfinder。
本文旨在深入探讨pfinder的核心原理和架构设计,揭示它是如何实现应用全链路监控的。我们将从pfinder的基本概念和功能开始讲起,逐步深入到其具体实现机制。
2.1.pfinder简介
PFinder (problem finder) 是UMP团队打造的新一代APM(应用性能追踪)系统,集调用链追踪、应用拓扑、多维监控于一身,无需修改代码,只需要在启动文件增加 2 行脚本,便可实现接入。接入后便会对应用提供可观测能力,目前支持京东主流的中间件,包括:jimdb,jmq,jsf,以及一些常用的开源组件:tomcat、http client,mysql,es等。
2.2.pfinder功能
PFinder 除了具备 ump 现有功能的基础上,增加了以下重磅功能:
•多维监控: 支持按多个维度统计监控指标,按机房、按分组、按JSF别名、按调用方,各种维度随心组合查看
•自动埋点: 自动对 SpringMVC,JSF,MySQL,JMQ 等常用中间件进行性能埋点,无需改动代码,接入即可观测
•应用拓扑: 自动梳理服务的上下游和中间件的依赖拓扑
•调用链追踪: 基于请求的跨服务调用追踪,助你快速分析性能瓶颈
•自动故障分析: 通过AI算法自动分析调用拓扑上所有服务的监控数据,自动判断故障根因
•流量录制回放: 通过录制线上流量,回放至待特定环境(测试、预发),对比回放与录制时产生的差异,帮助用户补全业务场景、完善测试用例
•跨单元逃逸流量监控: 支持 JSF 跨单元流量、逃逸流量监控,单元化应用运行状态一目了然
2.3.APM类组件对比
Zipkin | Pinpoint | SkyWalking | CAT | pfinder | |
贡献者 | 韩国公司 | 华为 | 美团 | 京东 | |
实现方式 | 拦截请求,发送 http/mq 数据到 zipkin 服务 | 字节码注入 | 字节码注入 | 代理埋点(拦截器、注解、过滤器) | 字节码注入 |
接入方式 | 基于 linkerd/sleuth,引入配置即可 | javaagent 字节码 | javaagent 字节码 | 代码侵入 | javaagent 字节码 |
agent 到 collector 传输协议 | http、MQ | thrift | gRPC | http/tcp | JMTP |
OpenTracing | 支持 | 支持 | 支持 | ||
粒度 | 接口级 | 方法级 | 方法级 | 代码级 | 方法级 |
全局调用统计 | 支持 | 支持 | 支持 | 支持 | |
traceid 查询 | 支持 | 支持 | 支持 | ||
告警 | 支持 | 支持 | 支持 | 支持 | |
JVM 监控 | 支持 | 支持 | 支持 | 支持 |
更重要的一点是:pfinder对京东内部自研组件提供了支持,比如:jsf、jmq、jimdb
既然pfinder是基于字节码增强实现的,那么讲到pfinder,字节码增强技术自然也是无法避开的话题。这里我将字节码增强技术分两点来说,也是我认为实现字节码增强需要解决的两个关键点:
1.字节码是为了机器设计的,而非人类,字节码可读性极差、修改门槛极高,那么我们如何修改字节码呢?
2.修改后的字节码如何注入运行时JVM中呢?
欲攻善其事,必先利其器,所以下面我们围绕着这两个问题进行展开,当然,对这方面知识已经有所掌握的同学可忽略。
3.1.字节码修改
字节码修改成熟的框架已经很多了,诸如:ASM、javassist、bytebuddy、bytekit,下面我们用这几个字节码修改框架实现一个相同的功能,来对比下这几个框架使用上的区别。现在我们通过字节码修改来实现下面的功能:
![](https://s3.cn-north-1.jdcloud-oss.com/shendengbucket1/2024-05-14-18-40dNLCe0mG40SUXO6I..jpg)
@Override
public void visitCode() {
super.visitCode();
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitLdcInsn("start");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
}
@Override
public void visitInsn(int opcode) {
if ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN) || opcode == Opcodes.ATHROW) {
//方法在返回之前,打印"end"
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out"