APM 杂谈

HTv8R.png

人人都需要的 Application Performance Monitoring 了解一下

需要收集什么数据

首先我们要做 APM 那我们,我们先明确我们的 APM 需要收集的数据有哪些?因为是针对 Application 而言,CPU DISK 就不算是在这个范围之内。一般认为有两类数据需要收集

  • 运行状态数据 [Metrics]: uptime,health,snaphost,avg_response_time
  • 链路数据 [Tracing]:单次调用的上下文和链路信息

第一类数据的作用是分析现在的系统状态,比如依赖的中间件是否有问题,系统的 GC 状态是否正常,对外的 Web 服务是否正常。

第二类数据的作用是分析在单次处理的过程中是否出现一些问题,以及出现问题时的上下文是如何的。

采集侧

探针体系

探针顾名思义,插在运行程序上面的一层。因此探针体系的 APM 都有着最好的一个卖点 无侵入,大多数都是利用了特定语言提供的无侵入能力做这件事。这里面的玩家有很多 Skywalking pinpoint 等。

例子: Java Agent

因为 Java 是一门虚拟机语言,支持一种名为 动态字节码 的功能,这个在功能提供了 2 种模式,C++ 版本的 agentlibJava 版本的 javaagent

HanCe.png

只需要在启动的时候

1
java -javaagent:agent1.jar -javaagent:agent2.jar -jar MyProgram.jar

这里的 agent.jar 就是我们的探针代码,比如有以下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public static void premain(String agentArgs, Instrumentation inst) {
final ElementMatcher.Junction<NamedElement> springApplicationType = ElementMatchers.nameEndsWith("ApplicationObjectSupport");

//捕获到 `ApplicationObjectSupport.setApplicationContext()` 函数,并且创建一个代理函数,逻辑如下个class
final AgentBuilder.Transformer transformer = new AgentBuilder.Transformer() {
@Override
public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder, TypeDescription typeDescription, ClassLoader classLoader, JavaModule javaModule) {
return builder.method(ElementMatchers.named("setApplicationContext"))
.intercept(Advice.to(ContextAdvice.class));
}
};
}

public class ContextAdvice {
// 捕获 ApplicationContext 对象用于处理后面的逻辑
@Advice.OnMethodEnter
static void enter(@Advice.AllArguments Object[] args) {
ApplicationContextHolder.applicationContextObj = args[0];
}

@Advice.OnMethodExit
static void exit() {
VersionLoader.getVersion();
}
}

小结

探针体系依赖于语言提供的能力,优点也是不辩自明的,无需客户端做任何修改就可以兼容大部分的框架。主要说说这个体系的缺点

  • 适用性低:受限于语言本身的能力
  • 适配繁琐:这可以参考 Skywalking 的适配,对于开源的还好,如果是内部框架呢?

主要选手:

skywalking pinpiont

SDK 体系

SDK 体系最好理解,提供了一套 SDK API 供使用者调用,这样就可以让使用者无感知监控的内部实现。对于大部分的 编译型 语言来说,这是唯一可选的路径。

例子: go-agent

比如 Newrelic Tingyun 都是采用这种模式,支持大部分的常见的 Middware,然后也支持手动的创建一些监控数据。

启动一个外部通讯服务
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
func main() {
// Create an Application:
app, err := newrelic.NewApplication(
// Name your application
newrelic.ConfigAppName("Your Application Name"),
// Fill in your New Relic license key
newrelic.ConfigLicense("__YOUR_NEW_RELIC_LICENSE_KEY__"),
// Add logging:
newrelic.ConfigDebugLogger(os.Stdout),
// Optional: add additional changes to your configuration via a config function:
func(cfg *newrelic.Config) {
cfg.CustomInsightsEvents.Enabled = false
},
)
// If an application could not be created then err will reveal why.
if err != nil {
fmt.Println("unable to create New Relic Application", err)
}
// Now use the app to instrument everything!
}

然后再我们想要的地方进行埋点

1
2
3
4
5
// 原逻辑
http.HandleFunc("/users", usersHandler)

// 现逻辑
http.HandleFunc(newrelic.WrapHandleFunc(app, "/users", usersHandler))

原理不用猜也知道了,对于大部分的框架来说,我们在处理上下文的时候都会传递一个 Context 对象,我们希望能够捕获一些状态数据的话,将其放置于 Context 中,然后将其在最终 End 的地方通过异步的队列上报即可。

例子:Cat

单纯的埋点,无需支持任何框架,本身就是一个全局对象。用起来也非常的粗暴简单

HTOBf.png

CAT手动埋点
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Transaction t = Cat.newTransaction("URL", "pageName");

try {
Cat.logEvent("URL.Server", "serverIp", Event.SUCCESS, "ip=${serverIp}");
Cat.logMetricForCount("metric.key");
Cat.logMetricForDuration("metric.key", 5);

yourBusiness();

t.setStatus(Transaction.SUCCESS);
} catch (Exception e) {
t.setStatus(e);
Cat.logError(e);
} finally {
t.complete();
}

自报体系

还有一类以 Web API 提供远端的消费者来提供服务的,这样的需要自行在客户端侧进行逻辑适配,然后按照 API 的要求将数据上报来,因此在我们上面所说的两种监控数据类型上,社区有相对标准的数据格式。

指标类型:prometheus 的 metric types,不过这块还没有什么标准的文件出现,不过有一个 micrometer spring 社区在推进。

链路类型:opentracing 一统天下

小结

SDK 体系就实现起来很简单,并且在使用的过程中如果有什么不兼容的在使用侧进行修改都可以,但是缺点就是一旦引入了 SDK,就是涉及到代码的更新,这就是传统 SDK 同样导致的问题。

主要选手:
cat

处理侧

对于收集上来的数据总是需要进行处理的,因此这块设计也不尽相同。

聚合处理型

SkywalkingPinpoint 都是这个模式,以 Skywalking 为例子,有一个单独的部署的模块为 Observability Analysis Platform 下称 OAP

HaJZ6.png

OAP 承担三件事情

  1. 数据计算:对于 Tracing 数据需要将多个 Span 聚合在一起
  2. 数据的收集对接储存: OAP 支持 H2 Mysql Elasticsearch
  3. 数据的查询: 因为不同的数据库,对应不同的储存模型,但是对外要提供统一的接口

这个模式看起来还不错,不过从开源的版本看起来,都会有几个问题。

  • OAP 的性能堪忧,随着接入节点的变多,OAP 既是接入端,又是处理端,还是查询端。
  • OAP 的逻辑复杂,且难以扩展。

简单透传型

jaeger 就属于这一类型,只有最终的一份工作,将 Tracing 的数据落到不同的数据库中,而且几乎不进行什么处理,因此在数据内存放的都是散落的 Span 信息,在后面继续分析的时候,聚合数据的工作需要留到查询侧再进行,这样的设计也不是不无道理,因为对于大部分的时间来说,我们不会去看 Tracing 的信息,等我们真的点击的时候再进行数据分析即可。

HaNAT.png

理想型

  • 数据接入:所有的数据应该可以直接连接 OAP 或者 Kafka,因为对于 POC 或者小客户场景,加入 Kafka 是不适合的,增加了运维复杂度,直接在 OAP 进行处理即可。
  • 数据处理:1. 对于大规模的场景,数据的消费者应该是 Spark/Flink ,基于流处理的能力减少开发复杂度, 2. 对于简单场景, OAP 进行逻辑处理即可

储存侧

储存方案笔者也不是很精通,随便看看

对于数据的存储一般都是监控体系最头疼的地方,不过还算是比较好的现在已经有很多可以参考的案例。现在常见的储存有 Hbase Elasticsearch TSDB 等。
对于大规模的长时间储存有需要检索分析的话,Hbase 也许符合需求,对于每日场景分析的数据我认为使用 Elasticsearch 更加适合,对于 Metris 指标的话,采用时序数据库又更更适合。因此我认为这几个应该搭配起来使用,大部分时候链路数据的热点也就是在一天左右,因为这些数据可以一开始储存在 Elasticsearch 方便分析,之后可以将数据归档到 Hbase 进行冷处理。

参考