起源:javadoop.com/post/apm
前言
本文来说说什么是 APM 零碎,也就是大家平时说的监控零碎,以及怎么实现一个 APM 零碎。因为一些非凡的起因,我在文中会应用 Dog 作为咱们的零碎名称进行介绍。
咱们为 Dog 布局的指标是接入公司的大部分利用,预计每秒解决 500MB-1000MB 的数据,单机每秒 100MB 左右,应用多台一般的 AWS EC2。
因为本文的很多读者供职的公司不肯定有比拟全面的 APM 零碎,所以我尽量关照更多读者的浏览感触,会在有些内容上啰嗦一些,心愿大家能够了解。我会在文中提到 prometheus、grafana、cat、pinpoint、skywalking、zipkin 等一系列工具,如果你没有用过也不要紧,我会充分考虑到这一点。
本文预设的一些背景:Java 语言、web 服务、每个利用有多个实例、以微服务形式部署。另外,从文章的可浏览性上思考,我假如每个利用的不同实例散布在不同的 IP 上,可能你的利用场景不肯定是这样的。
APM 简介
APM 通常认为是 Application Performance Management 的简写,它次要有三个方面的内容,别离是 Logs(日志)、Traces(链路追踪) 和 Metrics(报表统计)。当前大家接触任何一个 APM 零碎的时候,都能够从这三个方面去剖析它到底是什么样的一个零碎。
有些场景中,APM 特指下面三个中的 Metrics,咱们这里不去探讨这个概念
这节咱们先对这 3 个方面进行介绍,同时介绍一下这 3 个畛域外面一些罕用的工具。
1、首先 Logs 最好了解,就是对各个利用中打印的 log 进行收集和提供查问能力。
Logs 零碎的重要性显而易见,通常咱们在排查特定的申请的时候,是十分依赖于上下文的日志的。
以前咱们都是通过 terminal 登录到机器外面去查 log(我好几年都是这样过去的),然而因为集群化和微服务化的起因,持续应用这种形式工作效率会比拟低,因为你可能须要登录好几台机器搜寻日志能力找到须要的信息,所以须要有一个中央中心化存储日志,并且提供日志查问。
Logs 的典型实现是 ELK (ElasticSearch、Logstash、Kibana),三个我的项目都是由 Elastic 开源,其中最外围的就是 ES 的贮存和查问的性能失去了大家的认可,禁受了十分多公司的业务考验。
Logstash 负责收集日志,而后解析并存储到 ES。通常有两种比拟支流的日志采集形式,一种是通过一个客户端程序 FileBeat,收集每个利用打印到本地磁盘的日志,发送给 Logstash;另一种则是每个利用不须要将日志存储到磁盘,而是间接发送到 Kafka 集群中,由 Logstash 来生产。
Kibana 是一个十分好用的工具,用于对 ES 的数据进行可视化,简略来说,它就是 ES 的客户端。
咱们回过头来剖析 Logs 零碎,Logs 零碎的数据来自于利用中打印的日志,它的特点是数据量可能很大,取决于利用开发者怎么打日志,Logs 零碎须要存储全量数据,通常都要反对至多 1 周的贮存。
每条日志蕴含 ip、thread、class、timestamp、traceId、message 等信息,它波及到的技术点非常容易了解,就是日志的存储和查问。
应用也非常简单,排查问题时,通常先通过关键字搜到一条日志,而后通过它的 traceId 来搜寻整个链路的日志。
题外话,Elastic 其实除了 Logs 以外,也提供了 Metrics 和 Traces 的解决方案,不过目前国内用户次要是应用它的 Logs 性能。
2、咱们再来看看 Traces 零碎,它用于记录整个调用链路。
后面介绍的 Logs 零碎应用的是开发者打印的日志,所以它是最贴近业务的。而 Traces 零碎就离业务更远一些了,它关注的是一个申请进来当前,通过了哪些利用、哪些办法,别离在各个节点消耗了多少工夫,在哪个中央抛出的异样等,用来疾速定位问题。
通过多年的倒退,Traces 零碎尽管在服务端的设计很多样,然而客户端的设计缓缓地趋于对立,所以有了 OpenTracing 我的项目,咱们能够简略了解为它是一个标准,它定义了一套 API,把客户端的模型固化下来。以后比拟支流的 Traces 零碎中,Jaeger、SkyWalking 是应用这个标准的,而 Zipkin、Pinpoint 没有应用该标准。限于篇幅,本文不对 OpenTracing 开展介绍。
上面这张图是我画的一个申请的时序图:
从下面这个图中,能够十分不便地看出,这个申请通过了 3 个利用,通过线的长短能够非常容易看出各个节点的耗时状况。通常点击某个节点,咱们能够有更多的信息展现,比方点击 HttpClient 节点咱们可能有 request 和 response 的数据。
上面这张图是 Skywalking 的图,它的 UI 也是蛮好的:
SkyWalking 在国内应该比拟多公司应用,是一个比拟优良的由国人发动的开源我的项目,已进入 Apache 基金会。
另一个比拟好的开源 Traces 零碎是由韩国人开源的 Pinpoint,它的打点数据十分丰盛,这里有官网提供的 Live Demo,大家能够去玩一玩。
最近比拟火的是由 CNCF(Cloud Native Computing Foundation) 基金会治理的 Jeager:
当然也有很多人应用的是 Zipkin,算是 Traces 零碎中开源我的项目的老前辈了:
下面介绍的是目前比拟支流的 Traces 零碎,在排查具体问题的时候它们十分有用,通过链路剖析,很容易就可以看进去这个申请通过了哪些节点、在每个节点的耗时、是否在某个节点执行异样等。
尽管这里介绍的几个 Traces 零碎的 UI 不一样,大家可能有所偏好,然而具体说起来,表白的都是一个货色,那就是一颗调用树,所以咱们要来说说每个我的项目除了 UI 以外不一样的中央。
首先必定是数据的丰盛度,你往上拉看 Pinpoint 的树,你会发现它的埋点十分丰盛,真的实现了一个申请通过哪些办法高深莫测。
然而这真的是一个坏事吗?值得大家去思考一下。两个方面,一个是对客户端的性能影响,另一个是服务端的压力。
其次,Traces 零碎因为有零碎间调用的数据,所以很多 Traces 零碎会应用这个数据做零碎间的调用统计,比方上面这个图其实也蛮有用的:
另外,后面说的是某个申请的残缺链路剖析,那么就引出另一个问题,咱们怎么获取这个“某个申请”,这也是每个 Traces 零碎的不同之处。
比方上图,它是 Pinpoint 的图,咱们看到后面两个节点的圆圈是不完满的,点击后面这个圆圈,就可以看进去起因了:
图中左边的两个红圈是我加的。咱们能够看到在 Shopping-api 调用 Shopping-order 的申请中,有 1 个失败的申请,咱们用鼠标在散点图中把这个红点框出来,就能够进入到 trace 视图,查看具体的调用链路了。限于篇幅,我这里就不去演示其余 Traces 零碎的入口了。
还是看下面这个图,咱们看右下角的两个统计图,咱们能够看进去在最近 5 分钟内 Shopping-api 调用 Shopping-order 的所有申请的耗时状况,以及工夫散布。在产生异样的状况,比方流量突发,这些图的作用就进去了。
对于 Traces 零碎来说,最有用的就是这些货色了,当然大家在应用过程中,可能也发现了 Traces 零碎有很多的统计性能或者机器衰弱状况的监控,这些是每个 Traces 零碎的差异化性能,咱们就不去具体分析了。
3、最初,咱们再来探讨 Metrics,它侧重于各种报表数据的收集和展现。
在 Metrics 方面做得比拟好的开源零碎,是公众点评开源的 Cat,上面这个图是 Cat 中的 transaction 视图,它展现了很多的咱们常常须要关怀的统计数据:
下图是 Cat 的 problem 视图,对咱们开发者来说就太有用了,利用开发者的指标就是让这个视图中的数据越少越好。
本文之后的内容次要都是围绕着 Metrics 开展的,所以这里就不再开展更多的内容了。
另外,说到 APM 或系统监控,就不得不提 Prometheus+Grafana 这对组合,它们对机器衰弱状况、URL 拜访统计、QPS、P90、P99 等等这些需要,反对得十分好,它们用来做监控大屏是十分适合的,然而通常不能帮忙咱们排查问题,它看到的是零碎压力高了、零碎不行了,但不能一下子看进去为啥高了、为啥不行了。
科普:Prometheus 是一个应用内存进行存储和计算的服务,每个机器 / 利用通过 Prometheus 的接口上报数据,它的特点是快,然而机器宕机或重启会失落所有数据。
Grafana 是一个好玩的货色,它通过各种插件来可视化各种零碎数据,比方查问 Prometheus、ElasticSearch、ClickHouse、MySQL 等等,它的特点就是酷炫,用来做监控大屏再好不过了。
Metrics 和 Traces
因为本文之后要介绍的咱们开发的 Dog 零碎从分类来说,侧重于 Metrics,同时咱们也提供 tracing 性能,所以这里独自写一大节,剖析一下 Metrics 和 Traces 零碎之间的分割和区别。
应用上的区别很好了解,Metrics 做的是数据统计,比方某个 URL 或 DB 拜访被申请多少次,P90 是多少毫秒,谬误数是多少等这种问题。而 Traces 是用来剖析某次申请,它通过了哪些链路,比方进入 A 利用后,调用了哪些办法,之后可能又申请了 B 利用,在 B 利用外面又调用了哪些办法,或者整个链路在哪个中央出错等这些问题。
不过在后面介绍 Traces 的时候,咱们也发现这类零碎也会做很多的统计工作,它也笼罩了很多的 Metrics 的内容。
所以大家先要有个概念,Metrics 和 Traces 之间的分割是十分严密的,它们的数据结构都是一颗调用树,区别在于这颗树的枝干和叶子多不多。在 Traces 零碎中,一个申请所通过的链路数据是十分全的,这样对排查问题的时候十分有用,然而如果要对 Traces 中的所有节点的数据做报表统计,将会十分地消耗资源,性价比太低。而 Metrics 零碎就是面向数据统计而生的,所以树上的每个节点咱们都会进行统计,所以这棵树不能太“繁茂”。
咱们关怀的其实是,哪些数据值得统计?首先是入口,其次是耗时比拟大的中央,比方 db 拜访、http 申请、redis 申请、跨服务调用等。当咱们有了这些要害节点的统计数据当前,对于零碎的衰弱监控就非常容易了。
我这里不再具体去介绍他们的区别,大家看完本文介绍的 Metrics 零碎实现当前,再回来思考这个问题会比拟好。
Dog 在设计上,次要是做一个 Metrics 零碎,统计要害节点的数据,另外也提供 trace 的能力,不过因为咱们的树不是很”繁茂“,所以链路上可能是断断续续的,两头会有很多缺失的地带,当然利用开发者也能够退出手动埋点来补救。
Dog 因为是公司外部的监控零碎,所以对于公司外部大家会应用到的中间件绝对是比拟确定的,不须要像开源的 APM 一样须要打很多点,咱们次要实现了以下节点的主动打点:
- http 入口:通过实现一个 Filter 来拦挡所有的申请
- MySQL: 通过 Mybatis Interceptor 的形式
- Redis: 通过 javassist 加强 RedisTemplate 的形式
- 跨利用调用: 通过代理 feign client 的形式,dubbo、grpc 等形式可能须要通过拦截器
- http 调用: 通过 javassist 为 HttpClient 和 OkHttp 减少 interceptor 的形式
- Log 打点: 通过 plugin 的形式,将 log 中打印的 error 上报上来
打点的技术细节,就不在这里开展了,次要还是用了各个框架提供的一些接口,另外就是用到了 javassist 做字节码加强。
这些打点数据就是咱们须要做统计的,当然因为打点无限,咱们的 tracing 性能绝对于业余的 Traces 零碎来说薄弱了很多。
Dog 简介
上面是 DOG 的架构图,客户端将音讯投递给 Kafka,由 dog-server 来生产音讯,存储用到了 Cassandra 和 ClickHouse,前面再介绍具体存哪些数据。
1、也有 APM 零碎是不通过消息中间件的,比方 Cat 就是客户端通过 Netty 连贯到服务端来发送音讯的。
2、Server 端应用了 Lambda 架构模式,Dog UI 上查问的数据,由每一个 Dog-server 的内存数据和上游贮存的数据聚合而来。
上面,咱们简略介绍下 Dog UI 上一些比拟重要的性能,咱们之后再去剖析怎么实现相应的性能。
留神:上面的图都是我本人画的,不是真的页面截图,数值上可能不太精确
下图示例 transaction 报表:
点击上图中 type 中的某一项,咱们有这个 type 上面每个 name 的报表。比方点击 URL,咱们能够失去每个接口的数据统计:
当然,上图中点击具体的 name,还有下一个层级 status 的统计数据,这里就不再贴图了。Dog 总共设计了 type、name、status 三级属性。下面两个图中的最初一列是 sample,它能够指引到 sample 视图:
Sample 就是取样的意思,当咱们看到有个接口失败率很高,或者 P90 很高的时候,你晓得出了问题,但因为它只有统计数据,所以你不晓得到底哪里出了问题,这个时候,就须要有一些样本数据了。咱们每分钟对 type、name、status 的不同组合别离保留最多 5 个胜利、5 个失败、5 个慢解决的样本数据。
点击下面的 sample 表中的某个 T、F、L 其实就会进入到咱们的 trace 视图,展现出这个申请的整个链路:
通过下面这个 trace 视图,能够十分疾速地晓得是哪个环节出了问题。当然,咱们之前也说过,咱们的 trace 依赖于咱们的埋点丰盛度,然而 Dog 是一个 Metrics 为主的零碎,所以它的 Traces 能力是不够的,不过大部分状况下,对于排查问题应该是足够用的。
对于利用开发者来说,上面这个 Problem 视图应该是十分有用的:
它展现了各种谬误的数据统计,并且提供了 sample 让开发者去排查问题。
最初,咱们再简略介绍下 Heartbeat 视图,它和后面的性能没什么关系,就是大量的图,咱们有 gc、heap、os、thread 等各种数据,让咱们能够察看到零碎的衰弱状况。
这节次要介绍了一个 APM 零碎通常蕴含哪些性能,其实也很简略对不对,接下来咱们从开发者的角度,来聊聊具体的实现细节问题。
客户端数据模型
大家都是开发者,我就间接一些了,下图介绍了客户端的数据模型:
对于一条 Message 来说,用于统计的字段是 type, name, status,所以咱们能基于 type、type+name、type+name+status 三种维度的数据进行统计。
Message 中其余的字段:timestamp 示意事件产生的工夫;success 如果是 false,那么该事件会在 problem 报表中进行统计;data 不具备统计意义,它只在链路追踪排查问题的时候有用;businessData 用来给业务零碎上报 业务数据,须要手动打点,之后用来做业务数据分析。
Message 有两个子类 Event 和 Transaction,区别在于 Transaction 带有 duration 属性,用来标识该 transaction 耗时多久,能够用来做 max time, min time, avg time, p90, p95 等,而 event 指的是产生了某件事,只能用来统计产生了多少次,并没有工夫长短的概念。
Transaction 有个属性 children,能够嵌套 Transaction 或者 Event,最初造成一颗树状构造,用来做 trace,咱们稍后再介绍。
上面表格示例一下打点数据,这样比拟直观一些:
简略介绍几点内容:
- type 为 URL、SQL、Redis、FeignClient、HttpClient 等这些数据,属于主动埋点的领域。通常做 APM 零碎的,都要实现一些主动埋点的工作,这样利用开发者不须要做任何的埋点工作,就能看到很多有用的数据。像最初两行的 type=Order 属于手动埋点的数据。
- 打点须要特地留神 type、name、status 的维度“爆炸”,它们的组合太多会十分耗费资源,它可能会间接拖垮咱们的 Dog 零碎。type 的维度可能不会太多,然而咱们可能须要留神开发者可能会滥用 name 和 status,所以咱们肯定要做 normalize(如 url 可能是带动静参数的,须要格式化解决一下)。
- 表格中的最初两条是开发者 手动埋点 的数据,通常用来统计特定的场景,比方我想晓得某个办法被调用的状况,调用次数、耗时、是否抛异样、入参、返回值等。因为主动埋点是业务不想关的,凉飕飕的数据,开发者可能想要埋一些本人想要统计的数据。
- 开发者在手动埋点的时候,还能够上报更多的业务相干的数据上来,参考表格最初一列,这些数据能够做业务剖析来用。比方我是做领取零碎的,通常一笔领取订单会波及到十分多的步骤(国外的领取和大家平时应用的微信、支付宝略微有点不一样),通过上报每一个节点的数据,最初我就能够在 Dog 上应用 bizId 来将整个链路串起来,在排查问题的时候是十分有用的(咱们在做领取业务的时候,领取的成功率并没有大家设想的那么高,很多节点可能出问题)。
客户端设计
上一节咱们介绍了单条 message 的数据,这节咱们笼罩一下其余内容。
首先,咱们介绍客户端的 API 应用:
public void test() {Transaction transaction = Dog.newTransaction("URL", "/test/user");
try {Dog.logEvent("User", "name-xxx", "status-yyy");
// do something
Transaction sql = Dog.newTransaction("SQL", "UserMapper.insert");
// try-catch-finally
transaction.setStatus("xxxx");
transaction.setSuccess(true/false);
} catch (Throwable throwable) {transaction.setSuccess(false);
transaction.setData(Throwables.getStackTraceAsString(throwable));
throw throwable;
} finally {transaction.finish();
}
}
下面的代码示例了如何嵌套应用 Transaction 和 Event,当最外层的 Transaction 在 finally 代码块调用 finish() 的时候,实现了一棵树的创立,进行音讯投递。
咱们往 Kafka 中投递的并不是一个 Message 实例,因为一次申请会产生很多的 Message 实例,而是应该组织成 一个 Tree 实例当前进行投递。下图形容 Tree 的各个属性:
Tree 的属性很好了解,它持有 root transaction 的援用,用来遍历整颗树。另外就是须要携带机器信息 messageEnv。
treeId 应该有个算法能保障全局惟一,简略介绍下 Dog 的实现:{encode(ip)}- 以后分钟{自增 id}。
上面简略介绍几个 tree id 相干的内容,假如一个申请从 A->B->C->D 通过 4 个利用,A 是入口利用,那么会有:
1、总共会有 4 个 Tree 对象实例从 4 个利用投递到 Kafka,跨利用调用的时候须要传递 treeId, parentTreeId, rootTreeId 三个参数;
2、A 利用的 treeId 是所有节点的 rootTreeId;
3、B 利用的 parentTreeId 是 A 的 treeId,同理 C 的 parentTreeId 是 B 利用的 treeId;
4、在跨利用调用的时候,比方从 A 调用 B 的时候,为了晓得 A 的下一个节点是什么,所以在 A 中提前为 B 生成 treeId,B 收到申请后,如果发现 A 曾经为它生成了 treeId,间接应用该 treeId。
大家应该也很容易晓得,通过这几个 tree id,咱们是想要实现 trace 的性能。
介绍完了 tree 的内容,咱们再简略探讨下利用集成计划。
集成无外乎两种技术,一种是通过 javaagent 的形式,在启动脚本中,加上相应的 agent,这种形式的长处是开发人员无感知,运维层面就能够做掉,当然开发者如果想要手动做一些埋点,可能须要再提供一个简略的 client jar 包给开发者,用来桥接到 agent 里。另一种就是提供一个 jar 包,由开发者来引入这个依赖。
两种计划各有优缺点,Pinpoint 和 Skywalking 应用的是 javaagent 计划,Zipkin、Jaeger、Cat 应用的是第二种计划,Dog 也应用第二种手动增加依赖的计划。
通常来说,做 Traces 的零碎抉择应用 javaagent 计划比拟省心,因为这类零碎 agent 做完了所有须要的埋点,无需利用开发者感知。
最初,我再简略介绍一下 Heartbeat 的内容,这部分内容其实最简略,然而能做出很多花花绿绿的图表进去,能够实现面向老板编程。
后面咱们介绍了 Message 有两个子类 Event 和 Transaction,这里咱们再加一个子类 Heartbeat,用来上报心跳数据。
咱们次要收集了 thread、os、gc、heap、client 运行状况(产生多少个 tree,数据大小,发送失败数)等,同时也提供了 api 让开发者自定义数据进行上报。Dog client 会开启一个后盾线程,每分钟运行一次 Heartbeat 收集程序,上报数据。
再介绍细一些。外围构造是一个 Map<String, Double>,key 相似于“os.systemLoadAverage”,“thread.count”等,前缀 os,thread,gc 等其实是用来在页面上的分类,后缀是显示的折线图的名称。
对于客户端,这里就介绍这么多了,其实理论编码过程中,还有一些细节须要解决,比方如果一棵树太大了要怎么解决,比方没有 rootTransaction 的状况怎么解决(开发者只调用了 Dog.logEvent(…)),比方内层嵌套的 transaction 没有调用 finish 怎么解决等等。
Dog server 设计
下图示例了 server 的整体设计,值得注意的是,咱们这里对线程的应用十分地克服,图中只有 3 个工作线程。
首先是 Kafka Consumer 线程,它负责批量生产音讯,从 kafka 集群中生产到的是一个个 Tree 的实例,接下来思考怎么解决它。
在这里,咱们须要将树状构造的 message 铺平,咱们把这一步叫做 deflate,并且做一些预处理,造成上面的构造:
接下来,咱们就将 DeflateTree 别离投递到两个 Disruptor 实例中,咱们把 Disruptor 设计成单线程生产和单线程生产,次要是性能上的思考。生产线程依据 DeflateTree 的属性应用绑定好的 Processor 进行解决,比方 DeflateTree 中 List<Message> problmes
不为空,同时本人绑定了 ProblemProcessor,那么就须要调用 ProblemProcessor 来解决。
科普工夫:Disruptor 是一个高性能的队列,性能比 JDK 中的 BlockingQueue 要好
这里咱们应用了 2 个 Disruptor 实例,当然也能够思考应用更多的实例,这样每个生产线程绑定的 processor 就更少。咱们这里把 Processor 绑定到了 Disruptor 实例上,其实起因也很简略,为了性能思考,咱们想让每个 processor 只有单线程应用它,单线程操作能够缩小线程切换带来的开销,能够充分利用到零碎缓存,以及在设计 processor 的时候,不必思考并发读写的问题。
这里要思考负载平衡的状况,有些 processor 是比拟消耗 CPU 和内存资源的,肯定要正当调配,不能把压力最大的几个工作分到同一个线程中去了。
外围的解决逻辑都在各个 processor 中,它们负责数据计算。接下来,我把各个 processor 须要做的次要内容介绍一下,毕竟能看到这里的开发者,应该真的是对 APM 的数据处理比拟感兴趣的。
Transaction processor
transaction processor 是零碎压力最大的中央,它负责报表统计,尽管 Message 有 Transaction 和 Event 两个次要的子类,然而在理论的一颗树中,绝大部分的节点都是 transaction 类型的数据。
下图是 transaction processor 外部的一个次要的数据结构,最外层是一个工夫,咱们用分钟工夫来组织,咱们最初在长久化的时候,也是依照分钟来存的。第二层的 HostKey 代表哪个利用以及哪个 ip 来的数据,第三层是 type、name、status 的组合。最内层的 Statistics 是咱们的数据统计模块。
另外咱们也能够看到,这个构造到底会耗费多少内存,其实次要取决于咱们的 type、name、status 的组合也就是 ReportKey 会不会很多,也就是咱们后面在说客户端打点的时候,要防止维度爆炸。
最外层构造代表的是工夫的分钟示意,咱们的报表是基于每分钟来进行统计的,之后长久化到 ClickHouse 中,然而咱们的使用者在看数据的时候,可不是一分钟一分钟看的,所以须要做数据聚合,上面展现两条数据是如何做聚合的,在很多数据的时候,都是依照同样的办法进行合并。
你认真想想就会发现,后面几个数据的计算都没故障,然而 P90, P95 和 P99 的计算是不是有点坑骗人啊?其实这个问题是真的无解的,咱们只能想一个适合的数据计算规定,而后咱们再想想这种计算规定,可能算进去的值也是差不多可用的就好了。
另外有一个细节问题,咱们须要让内存中的数据提供最近 30 分钟的统计信息,30 分钟以上的才从 DB 读取。而后做下面介绍的 merge 操作。
探讨:咱们是否能够抛弃一部分实时性,咱们每分钟长久化一次,咱们读取的数据都是从 DB 来的,这样可行吗?
不行,因为咱们的数据是从 kafka 生产来的,自身就有肯定的滞后性,咱们如果在开始一分钟的时候就长久化上一分钟的数据,可能之后还会收到后面工夫的音讯,这种状况解决不了。
比方咱们要统计最近一小时的状况,那么就会有 30 分钟的数据从各个机器中取得,有 30 分钟的数据从 DB 取得,而后做合并。
这里值得一提的是,在 transaction 报表中,count、failCount、min、max、avg 是比拟好算的,然而 P90、P95、P99 其实不太好算,咱们须要一个数组构造,来记录这一分钟内所有的事件的工夫,而后进行计算,咱们这里讨巧应用了 Apache DataSketches,它十分好用,这里我就不开展了,感兴趣的同学能够本人去看一下。
到这里,大家能够去想一想贮存到 ClickHouse 的数据量的问题。app_name、ip、type、name、status 的不同组合,每分钟一条数据。
Sample Processor
sample processor 生产 deflate tree 中的 List<Transaction> transactions
和 List<Event> events
的数据。
咱们也是依照分钟来采样,最终每分钟,对每个 type、name、status 的不同组合,采集最多 5 个胜利、5 个失败、5 个慢解决。
相对来说,这个还是非常简单的,它的外围构造如下图:
联合 Sample 的性能来看比拟容易了解:
Problem Processor
在做 deflate 的时候,所有 success=false 的 Message,都会被放入 List<Message> problmes
中,用来做谬误统计。
Problem 外部的数据结构如下图:
大家看下这个图,其实也就晓得要做什么了,我就不啰嗦了。其中 samples 咱们每分钟保留 5 个 treeId。
顺便也再展现下 Problem 的视图:
对于长久化,咱们是存到了 ClickHouse 中,其中 sample 用逗号连接成一个字符串,problem_data 的列如下:
event_date, event_time, app_name, ip, type, name, status, count, sample
Heartbeat processor
Heartbeat 解决 List<Heartbeat> heartbeats
的数据,题外话,失常状况下,一颗树外面只有一个 Heartbeat 实例。
后面我也简略提到了一下,咱们 Heartbeat 中用来展现图表的外围数据结构是一个 Map<String, Double>
。
收集到的 key-value 数据如下所示:
{
"os.systemLoadAverage": 1.5,
"os.committedVirtualMemory": 1234562342,
"os.openFileDescriptorCount": 800,
"thread.count": 600,
"thread.httpThreadsCount": 250,
"gc.ZGC Count": 234,
"gc.ZGC Time(ms)": 123435,
"heap.ZHeap": 4051233219,
"heap.Metaspace": 280123212
}
前缀是分类,后缀是图的名称。客户端每分钟收集一次数据进行上报,而后就能够做很多的图了,比方下图展现了在 heap 分类下的各种图:
Heartbeat processor 要做的事件很简略,就是数据存储,Dog UI 上的数据是间接从 ClickHouse 中读取的。
heartbeat_data 的列如下:
event_date, event_time, timestamp, app_name, ip, name, value
MessageTree Processor
后面咱们屡次提到了 Sample 的性能,这些采样的数据帮忙咱们复原现场,这样咱们能够通过 trace 视图来跟踪调用链。
要做下面的这个 trace 视图,咱们须要上下游的所有的 tree 的数据,比方上图是 3 个 tree 实例的数据。
之前咱们在客户端介绍的时候说过,这几个 tree 通过 parent treeId 和 root treeId 来组织。
要做这个视图,给咱们提出的挑战就是,咱们须要保留全量的数据。
大家能够想一想这个问题,为啥要保留全量数据,咱们间接保留被 sample 到的数据不就好了吗?
这里咱们用到了 Cassandra 的能力,Cassandra 在这种 kv 的场景中,有十分不错的性能,而且它的运维老本很低。
咱们以 treeId 作为主键,另外再加 data 一个列即可,它是整个 tree 的实例数据,数据类型是 blob,咱们会先做一次 gzip 压缩,而后再扔给 Cassandra。
Business Processor
咱们在介绍客户端的时候说过,每个 Message 都能够携带 Business Data,不过只有利用开发者本人手动埋点的时候才会有,当咱们发现有业务数据的时候,咱们会做另一个事件,就是把这个数据存储到 ClickHouse 中,用来做业务剖析。
咱们其实不晓得利用开发者到底会把它用在什么场景中,因为每个人负责的我的项目都不一样,所以咱们只能做一个通用的数据模型。
回过头来看这个图,BusinessData 中咱们定义了比拟通用的 userId 和 bizId,咱们认为它们可能是每个业务场景会用到的货色。userId 就不用说了,bizId 大家能够做来记录订单 id,领取单 id 等。
而后咱们提供了 3 个 String 类型的列 ext1、ext2、ext3 和两个数值类型的列 extVal1 和 extVal2,它们能够用来表白你的业务相干的参数。
咱们的解决当然也非常简单,将这些数据存到 ClickHouse 中就能够了,表中次要有这些列:
event_data, event_time, user, biz_id, timestamp, type, name, status, app_name、ip、success、ext1、ext2、ext3、ext_val1、ext_val2
这些数据对咱们 Dog 零碎来说必定不意识,因为咱们也不晓得你表白的是什么业务,type、name、status 是开发者本人定义的,ext1, ext2, ext3 别离代表什么意思,咱们都不晓得,咱们只负责存储和查问。
这些业务数据十分有用,基于这些数据,咱们能够做很多的数据报表进去。因为本文是探讨 APM 的,所以该局部内容就不再赘述了。
其余
ClickHouse 须要批量写入,不然必定是撑不住的,个别一个 batch 至多 10000 行数据。
咱们在 Kafka 这层管制了,一个 app_name + ip 的数据,只会被同一个 dog-server 生产,当然也不是说被多个 dog-server 生产会有问题,然而这样写入 ClickHouse 的数据就会更多。
还有个要害的点,后面咱们说了每个 processor 是由单线程进行拜访的,然而有一个问题,那就是来自 Dog UI 上的申请可怎么办?这里我想了个方法,那就是将申请放到一个 Queue 中,由 Kafka Consumer 那个线程来生产,它会将工作扔到两个 Disruptor 中。比方这个申请是 transaction 报表申请,其中一个 Disruptor 的消费者会发现这个是本人要干的,就会去执行这个工作。
小结
如果你理解 Cat 的话,能够看到 Dog 在很多中央和 Cat 有相似之处,或者间接说”抄“也行,之前咱们也思考过间接应用 Cat 或者在 Cat 的根底上做二次开发。然而我看完 Cat 的源码后,就放弃了这个想法,认真想想,只是借鉴 Cat 的数据模型,而后咱们本人写一套 APM 其实不是很难,所以有了咱们这个我的项目。
行文须要,很多中央我都避重就轻,因为这不是什么源码剖析的文章,没必要处处谈细节,次要是给读者一个全貌,读者能通过我的形容大抵想到须要解决哪些事件,须要写哪些代码,那就当我表述分明了。
近期热文举荐:
1.1,000+ 道 Java 面试题及答案整顿(2022 最新版)
2. 劲爆!Java 协程要来了。。。
3.Spring Boot 2.x 教程,太全了!
4. 别再写满屏的爆爆爆炸类了,试试装璜器模式,这才是优雅的形式!!
5.《Java 开发手册(嵩山版)》最新公布,速速下载!
感觉不错,别忘了顺手点赞 + 转发哦!