这一讲我将带领你学习点评CAT(Central Application Tracking),为什么会以“老牌”和“经久不衰”来形容点评 CAT 呢?

首先,从时间上看,CAT 首个开源版本是在 2012 年,此时期与 APM 相关重大事件有:

Google 公司发布《Dapper 论文》;

紧随其后,Naver 公司发布开源无侵入 APM 系统Pinpoint;

Twitter 公司发布开源 APM 系统Zipkin,Zipkin 是 Spring Cloud 分布式追踪系统Spring-Cloud-Sleuth的解决方案。

听到 Dapper、Pinpoint 和 Zipkin 的时候,你会觉得 CAT 已经是上个年代的 APM 产品了。但其实 CAT 的时间渊源更早,它的核心设计思路源自 2000 年初 eBay 的内源 APM 产品 CAL(Centralized Application Logging),所以在时间轴上看 CAT 是足够老牌的。

其次,从已登记的落地案例上来看,CAT 从开源初期至今,在国内已经经历了非常多公司的复杂场景和海量流量的考验了。目前 CAT 在国内有数百家企业的部署案例,代码的变动被上千名开发人员关注,社区收到了上万人点赞,在 APM 的 Java 系统监控领域位居 TOP 1,算是一个经久不衰的 APM 产品。

这一课时,我将结合 CAT 的产品设计与落地经验,来详细讲述 CAT 是如何指导线程优化方法论落地的,又是如何解决互联网企业普遍存在的线程资源过度浪费问题的。

CAT 凭什么称为“老牌监控”?

点评 CAT 是使用 Java 开发实现的实时应用监控平台,起初专为大规模 Java 微服务集群提供实时监控报警场景打造。时至今日的 CAT 3.0 版本,客户端除了支持 Java 语言外,还支持 C/C++、Python、Go、Node.js 等语言。

Java 客户端通过使用框架暴露出的拦截器或过滤器,实现框架的性能监控(如实现 Apache Dubbo 的过滤器)。然后应用服务在代码中引入 CAT 客户端和相关埋点集成方案即可完成接入;而服务端又由两个模块组成,负责收集分析客户端数据的收集器模块和负责给用户提供报表展示的控制端模块。

所以如下图所示,总的来说 CAT 产品架构是由三个模块组成,架构设计非常清晰:

Cat-client 提供给应用以及中间层埋点的底层 SDK

Cat-consumer 用于实时分析从客户端提供的数据(收集器模块)

Cat-home 作为给用户提供展示的控制端(控制端模块)

基于以上原理,所以 CAT 有两大特色优势,那就是代码段监控报表非常实时,并且客户端性能消耗低。接下来,我就这两大优势展开讲解。

1.实时的代码段监控报表

CAT 提供了非常丰富的报表,应用服务简单接入 CAT 客户端后,我们就可以通过 CAT 服务端全方位地监控应用集群了。

CAT 报表功能如下:

2. 客户端性能损耗低

根据我在贝壳找房的实战经验,以及参考网上的实践文章,总结下来 CAT 客户端对 Java 进程的性能损耗,相对于其他 APM 产品是最低的,且被监控应用服务性能消耗可以做到忽略不计。

在没有自定义埋点的接入方式下,在线上 IDC 机房环境,性能消耗指标参考如下:

落地实践要点

由于 CAT 的生命周期非常长,所以部分 APM 功能已相对过时。接下来的落地实践,我会从 CAT 擅长和不擅长这两方面来阐述落地实践。

1.建议所有应用服务接入 CAT

由于 CAT 的性能消耗极低,我建议客户端所有应用服务全部接入 CAT。

但在接入前,需要先梳理企业应用服务集群目前使用的框架是否都被 CAT 官方埋点支持列表支持。如果没有,我建议企业内部偏向基础架构的团队,统一完成自建框架的埋点,服务端至少采用两台独立的物理机实现灾备部署。

在接入后,要持续迭代应用服务在 CAT 控制端对相关报警阈值配置。

2.不建议使用 CAT 分布式链路追踪功能

CAT 分布式链路跟踪功能,强烈不建议使用,主要问题出现在以下两方面:

“树形”模型无法追踪批处理框架的链路CAT 使用 ROOT、PARENT 和 CHILD 三个参数实现了分布式链路跟踪的“树形”模型,而“树形”模型是无法绘制分布式集群中存在批处理框架的链路的。即使不需要追踪批处理框架的链路,CAT 分布式链路跟踪代码也需要让每个 RD 理解跟踪模型,然后分布式跟踪的上下游都需要去进行手动埋点才可以实现。

报表展示效果不理想,且过时CAT 分布式链路追踪展示,可通过 Transaction 报表的 logview 视图查看。个人觉得其展示效果相较 SkyWalking、Zipkin 的分布式链路追踪视图存在明显差距。

综上所述,CAT 的分布式链路跟踪并没有给 CAT 产品整体上锦上添花;而且分布式链路跟踪的“树形”模型会让 RD 陷入思维定式,其学习成本较高,且手动埋点的落地方案也会非常难以实施。

分布式链路跟踪的解决方案,我会在《14 | 互通有无:如何设计跨语言的 APM 交互协议》和《21 | 高维思考:通过监控 Case,彻底悟透 OpenTracing》中详细讲述。

线程优化

刚刚我们学习了 CAT 的 Heartbeat 报表,此报表展示了应用服务的JVM 相关指标,可以看到官方 JVM Thread 相关指标很笼统,缺少对框架线程池的监控。

接下来我将根据 Java 线程模型,通过定制化改造 Heartbeat 报表的 JVM Thread 相关指标,快速实现应用服务线程优化方法论的落地,最终达到优化进程占用资源、提高资源利用率、避免 OOM的效果。

1.为什么要进行线程优化?

从软件资源角度:Java 进程内是通过多线程技术处理完成消费者请求,多线程需要调用 JVM 资源,而 JVM 资源又需要 GC 来持续维护可用。从应用视角看,线程的优化可以优化 JVM 资源,从而减少 GC。

从硬件资源角度:每个节点有线程上限,过多的线程会影响 CPU 的并行度,而节省进程就是节省资源,这样可以实现一个节点资源运行多个进程,降低硬件成本。

从可用性角度:非预期的 GC 导致应用节点不能按照预期响应消费者,造成应用服务不可用。

所以我们说,线程的优化是最关键的一环。说到线程优化,这可是 Java 程序员面试老生常谈,工作中却无人问津的问题了。

2.使用线程的正确姿势

如果你对这个问题还是有些懵,那我们先回顾下应用服务有哪些使用线程不正确的方式。

如果你还想了解更多不正确使用线程的方式,可以参考《阿里巴巴 Java 开发手册》。

对应这三个不正确的线程使用方式,我们总结下正确使用方式是:

使用线程池去管理线程;

线程的命名需要有意义;

任务线程的数量需要与工作线程挂钩,且需要考虑极端情况,如故障重启。

除了以上三种使用方式,线程优化还面临着其他难题,需要更细致的工具去帮助我们监控,这时便需要对 CAT 进行改造了。

3.CAT 改造

为了让 Heartbeat 报表支持更细的框架线程粒度监控,我们需要掌握 CAT 的监控线程实现原理。Heartbeat 功能采集线程通过 Java ThreadMXBean 获取进程中的线程信息,然后以一分钟为周期,持续上报到服务端。

原理很简单,比如我们要监控 Apache Dubbo 的核心线程和活跃线程数,只需要在此处代码增加前缀“DubboServerHandler-”,即可监控 Apache Dubbo 的核心线程,然后遍历所有的线程,通过获取运行状态或者非等待状态的线程,就可以统计出活跃线程数。

其他框架也是如此,只要线程名称有意义,即可快速通过 CAT 完成监控。

4.优化实战

如果你的团队没有对负责应用服务进行过专门的线程优化,那我可以肯定地告诉你,此应用服务存在大量的内置框架(Apache Dubbo、Netty)的过度线程使用,这些框架滥用线程的形式通常会分为以下两种情况。

第一种:线程池使用一个较大的固定值。例如 Apache Dubbo 为例,服务提供者的默认线程数为 200 的 Fixed 的线程池。

第二种:和 CPU 核心数挂钩的线程池。非常典型的代码Runtime.getRuntime().availableProcessors()*2 获取硬件环境可用的 CPU 核心数 2 倍作为线程池个数,例如 Redisson。

1)数量优化

第一种,框架使用线程池的方式显然就不合理。通过 CAT 改造,可以监控 Apache Dubbo 的线程数,你会发现一般的应用服务只用了十几个线程。

我建议,可以根据高峰和一定的未来预估来适当缩小线程池,并且考虑将线程池类型由 Fixed 改为 Cache,实现减少项目的启动时间和不必要的线程池数量。

关于 Apache Dubbo 的线程池改造,你可详见Apache Dubbo 线程模型。

第二种,主要会在公司使用 IDC 机房,采取物理机多进程混合部署的方式,会存在线程滥用过多的情况。

以 Redis 客户端 Redisson 为例,Redisson 客户端使用 Netty 与 Redis 集群通讯,默认的 Netty threads 为 0,这种配置会形成线程数与 CPU 核心数挂钩。

以常规的 IDC 机房节点 CPU 的核心数为 40 为例,那 Redisson 使用 Netty 的线程数就是 80 个。通过改造后的 CAT 查看线程数会发现,Redisson 的使用 Netty 的活跃线程数不会超过 10(10 已是非常高了,常规业务的应用服务,不会超过 5)。通过改造,一个使用 Java 进程单从 Redisson 框架就可以少用 70 个线程。

对于常规的微服务进程,基本都会用到的框架有 HTTP Server、HTTP Client、JDBC、RPC Framework、MQ 和 NoSQL,我们可以自底向上,将每个应用服务的各个框架优化任务分配到各个成员。然后自顶向下,通过 CAT 的持续跟进优化,看是否达到了预期。

综上,通过对上面两种情况的优化,我们的节点资源会得到显著释放。上面的优化显然是对线程的数量进行优化,但线程的优化远不仅此。

2)体积优化

从另一个角度看,我们还可以对单个线程的体积进行优化,根据国内 Java 进程线上运行的现状,基本都是在使用 Java 6 以上版本和在 64 位的操作系统上运行。

所以线程栈默认大小为 1M,原因参见Oracle VM Options,首先 1M 对于线程栈来说本身就非常充裕,其次随着项目微服务化带来的应用服务拆分,和框架更多的使用异步线程的设计,线程栈相对于早年的垂直单体应用的线程栈也缩小了很多。

所以我们可以适当缩小线程栈到 512k 以下,在应用服务使用较多线程时,优化的效果会非常明显。

小结与思考

今天的课程,我首先带你从产品视角学习了 CAT 擅长的 APM 领域及其优点,包括代码段监控报表实时、客户端性能消耗低这两大优点;也带你学习了 CAT 不擅长的 APM 领域,如分布式链路跟踪、业务监控等。

然后通过 CAT 针对框架更细粒度线程监控改造,快速实现线程优化方法论的落地。希望你通过今天的学习,可以掌握 CAT 的学习路径,并能在实际的工作中做到学以致用。

下面给你留一个思考题:你是否有过垂直单体应用拆分为微服务的经历?那拆分前后,有哪些原因造成了微服务需要更多的硬件资源呢?

请参考本文分享的线程优化实践案例,检查应用服务是否存在其他资源滥用的情况?是否可以通过 CAT 监控出这些问题?

你可以在留言区写下你思考的场景和优化思路,期待与你讨论。

-– ### 精选评论 ##### **聪: > 选型时一看到需要埋点的apm工具就放弃了,开发都忙着做需求,还是无侵入式的利于落地和推广 ######     讲师回复: >     是的,对比探针实现的APM工具,落地效率存在天生劣势,不过CAT除分布式追踪外,报警和性能损害CAT还是有一战之力的