为 Dubbo 服务接入 CAT 监控:cross 报表、依赖分析与全链路 Trace 实战
【免费下载链接】catCAT 作为服务端项目基础组件,提供了 Java, C/C++, Node.js, Python, Go 等多语言客户端,已经在美团点评的基础架构中间件框架(MVC框架,RPC框架,数据库框架,缓存框架等,消息队列,配置系统等)深度集成,为美团点评各业务线提供系统丰富的性能指标、健康状况、实时告警等。项目地址: https://gitcode.com/gh_mirrors/ca/cat
导读
本文围绕本仓库integration/apache-dubbo目录下的 Dubbo 与 CAT 整合插件展开,讲解如何以最小的接入成本(仅引入一个 Maven 依赖)为基于 Dubbo 的微服务系统开启调用监控,使每一次 RPC 调用自动产生跨服务(cross)报表、依赖分析(dependency)、服务端 matrix 以及调用链路 trace 数据。读完本文,你将掌握该插件的接入方式、手动启停 API、底层 Filter 拦截与消息树上下文透传原理,以及两类错误打点的分类口径,可直接用于排查 Dubbo 调用的耗时与异常问题。
一、插件定位:一行依赖带来的监控能力
该插件名为cat-monitor,其设计目标非常聚焦:监控当前系统 Dubbo 调用的执行情况,包括耗时以及异常统计。与手工埋点不同,它基于 Dubbo 的 Filter 扩展机制自动生效,业务代码零侵入。
按官方接入说明,只需在 Maven 中引入插件包:
<dependency> <groupId>net.dubboclub</groupId> <artifactId>cat-monitor</artifactId> <version>0.0.6</version> </dependency>That's All!
添加以上依赖之后,CAT 服务端即会出现以下报表(插件自身说明,trace 信息在 CAT 1.0.8 及以后版本提供):
- cross 报表:跨服务调用统计,展示调用方与被调用方之间的调用量、耗时、成功率;
- dependency 报表:服务依赖关系图,用于梳理服务间调用拓扑;
- 服务端 matrix 报表:服务端处理矩阵,观察各接口的响应时间分布;
- 调用链路 trace 信息:从消费端到提供端的完整消息树串联,可用于单次调用的全链路追踪。
需要说明的是,仓库中实际上维护了两套同名插件实现,分别面向不同 Dubbo 版本线:
- integration/apache-dubbo:基于
org.apache.dubbo:dubbo:2.7.15的新版实现,其 pom.xml 将 Dubbo 声明为provided作用域,依赖运行时环境自带; - integration/dubbo:基于
com.alibaba.dubbo(2.x 时代)的旧版实现,源码结构与新版本基本一致,仅包名不同(如com.alibaba.dubbo.common.Constants对应新版的org.apache.dubbo.common.constants.CommonConstants)。
因此接入时需按自身 Dubbo 版本选择对应实现:使用 Apache Dubbo 2.7.x 请参考integration/apache-dubbo,使用阿里 Dubbo 2.x 请参考integration/dubbo。
二、手动开启 / 关闭监控
插件默认在引入依赖后即自动生效,同时提供全局开关 API,便于在特殊场景(如灰度验证、故障期间临时关闭监控)下动态控制:
DubboCat.disable(); // 关闭 dubbo cat 监控 DubboCat.enable(); // 开启 dubbo cat 监控该开关的实现位于 DubboCat.java,核心逻辑如下:
public class DubboCat { private static boolean isEnable = true; public static void disable() { isEnable = false; } public static void enable() { isEnable = true; } public static boolean isEnable() { boolean isCatEnabled = false; try { isCatEnabled = Cat.getManager().isCatEnabled(); } catch (Throwable e) { CatLogger.getInstance().error("[DUBBO] Cat init error.", e); } return isCatEnabled && isEnable; } }从源码可以看到两个关键细节:
isEnable()是双重校验:既要插件自身开关为开启状态,又要求 CAT 客户端全局可用(Cat.getManager().isCatEnabled())。若 CAT 客户端初始化失败或未配置,监控会自动失效而不会影响 Dubbo 调用本身;- 开关是静态变量,进程内全局生效;当 CAT 客户端初始化抛出异常时,插件会记录
[DUBBO] Cat init error.日志后降级为不采集,体现了"监控失败不影响业务调用"的设计原则。
三、核心原理:基于 Dubbo Filter 的自动埋点
插件的自动埋点能力完全依赖 Dubbo 的 SPI 扩展机制。核心过滤器 CatTransaction.java 通过@Activate注解同时激活在 PROVIDER 和 CONSUMER 两侧,并设置order = -9000抢占过滤链的最前端:
@Activate(group = {CommonConstants.PROVIDER, CommonConstants.CONSUMER}, order = -9000) public class CatTransaction implements Filter {order = -9000意味着该 Filter 在调用链中尽可能早执行,从而能捕获最完整的调用耗时(包含后续所有过滤器与真实调用的时间)。整个invoke流程的核心步骤为:
开关校验:若
DubboCat.isEnable()为 false,直接放行原调用,不产生任何额外开销;确定调用方向:根据
invoker.getUrl()的side参数判断当前是消费端还是提供端,并据此决定 CAT Transaction 的类型:- 消费端:
CatConstants.CROSS_CONSUMER = "PigeonCall" - 提供端:
CatConstants.CROSS_SERVER = "PigeonService"
两个类型常量定义在 CatConstants.java 中。这里复用了 CAT 原有 RPC 框架(Pigeon)的消息命名,目的是让 cross 报表在服务端天然兼容、直接展示;
- 消费端:
创建 Transaction 并记录 cross 明细:
loggerName由接口简单名.方法名拼接而成(如UserService.getUserById),作为 cross 报表中"调用名称"的维度;调用真实业务逻辑:执行
invoker.invoke(invocation),根据结果决定 Transaction 的成功或失败状态;finally 收尾:
transaction.complete()完成埋点并清理 ThreadLocal 上下文。
3.1 消费端 cross 明细(PigeonCall)
消费端侧在发起调用前,通过createConsumerCross打点三个 Event,用于 cross 报表的调用方视角统计:
| Event 类型常量 | 值 | 含义 |
|---|---|---|
CONSUMER_CALL_APP | PigeonCall.app | 被调用的提供方应用名 |
CONSUMER_CALL_SERVER | PigeonCall.server | 提供方主机(url.getHost()) |
CONSUMER_CALL_PORT | PigeonCall.port | 提供方端口(url.getPort()) |
对应源码见 CatTransaction.java。这三个 Event 会被transaction.addChild挂到当前 Transaction 下,随消息树一起上报。
3.2 提供端 cross 明细(PigeonService)
提供端侧在收到请求后,通过createProviderCross打点两个 Event(见 CatTransaction.java):
| Event 类型常量 | 值 | 含义 |
|---|---|---|
PROVIDER_CALL_APP | PigeonService.app | 调用方(消费端)应用名 |
PROVIDER_CALL_SERVER | PigeonService.client | 调用方主机(RpcContext.getContext().getRemoteHost()) |
其中消费端应用名的获取依赖 RPC 上下文中透传的application参数(见下文 AppNameAppendFilter);若拿不到,则降级为远程主机:远程端口形式,保证 cross 报表仍有数据可看。
四、异常分类:三类 Error 打点
插件对 Dubbo 调用的异常做了分类统计,共定义了三个 Event 类型(见 CatTransaction.java):
| Event 类型常量 | 值 | 触发场景 |
|---|---|---|
DUBBO_BIZ_ERROR | 业务异常 | 服务端业务代码抛出的非 RPC/Remoting 异常 |
DUBBO_TIMEOUT_ERROR | 调用超时 | RpcException且根因(cause)为TimeoutException |
DUBBO_REMOTING_ERROR | 网络/远程通信异常 | RpcException但非超时,或RemotingException及其子类 |
判定逻辑位于 CatTransaction.java:调用返回后若result.hasException(),则根据异常类型创建对应 Event,event.setStatus(result.getException())记录异常对象,同时将 Transaction 状态置为异常类的简单类名;若无异常则transaction.setStatus(Message.SUCCESS)。
另外值得注意的两点实现细节:
- 异步调用的特殊处理:若
RpcUtils.isAsync判定为异步调用,插件不会判断返回结果中的异常(源码注释明确说明这会阻塞接口,因为AsyncRpcResult.hasException会触发 future.get),直接标记为成功; - 异常兜底:在
catch (RuntimeException e)分支中,若init已成功,会调用Cat.logError(e)记录错误日志并同样打点分类 Event;若发生异常时连初始化都未完成,则仅透传异常,保证监控自身故障不影响调用方。
五、链路串联:消息树上下文在 RPC 间透传
要形成完整的调用链路 trace,必须把"消费端发起的 Transaction"与"提供端接收的 Transaction"串到同一棵消息树上。插件通过 Dubbo 的RpcContextattachment 机制完成上下文透传,核心是 CAT 客户端Cat.Context的三个字段(见 Cat.java 中定义的ROOT、CHILD、PARENT常量):
_catRootMessageId —— 根消息 ID _catChildMessageId —— 子消息 ID _catParentMessageId —— 父消息 ID具体流程(见 CatTransaction.java):
- 消费端通过
Cat.logRemoteCallClient(context)生成新的消息树上下文; setAttachment把 ROOT / CHILD / PARENT 三个 ID 写入RpcContext的 attachments,随 RPC 请求发送到提供端;- 提供端在
initContext时,从RpcContext.getContext().getAttachments()中取出这三个 ID 还原Cat.Context,再调用Cat.logRemoteCallServer(context)将当前消息树挂接到上一跳,从而完成跨进程的链路串联。
上下文对象DubboCatContext使用ThreadLocal<Cat.Context>保存(静态字段CAT_CONTEXT),并在 finally 中CAT_CONTEXT.remove()清理,避免线程池复用导致的上下文串扰。
六、应用名透传:让 cross 报表"指名道姓"
cross 报表的价值在于展示"谁调了谁",因此调用双方的应用名至关重要。插件通过两个组件共同保证应用名的正确性:
6.1 消费端:AppNameAppendFilter
AppNameAppendFilter.java 仅激活在 CONSUMER 侧,在发起调用前把当前提供方 URL 中的application参数写入 RpcContext attachment,使下游能识别调用方应用:
@Activate(group = {CommonConstants.CONSUMER}) public class AppNameAppendFilter implements Filter { @Override public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException { RpcContext.getContext().setAttachment( CommonConstants.APPLICATION_KEY, invoker.getUrl().getParameter(CommonConstants.APPLICATION_KEY)); return invoker.invoke(invocation); } }6.2 提供端:Registry 包装获取服务端应用名
CatRegistryFactoryWrapper.java 通过包装 Dubbo 的RegistryFactory,在服务提供方注册 URL 时追加一个自定义参数serverApplicationName(常量定义见 CatConstants.java),值为提供方自身的application:
private URL appendProviderAppName(URL url) { String side = url.getParameter(CommonConstants.SIDE_KEY); if (CommonConstants.PROVIDER_SIDE.equals(side)) { url = url.addParameter(CatConstants.PROVIDER_APPLICATION_NAME, url.getParameter(CommonConstants.APPLICATION_KEY)); } return url; }RegistryWrapper在register/unregister/lookup等关键操作上统一调用appendProviderAppName做 URL 增强。这样消费端拿到提供方地址后,即可通过url.getParameter(CatConstants.PROVIDER_APPLICATION_NAME)直接读出被调服务应用名,无需额外配置;若该参数缺失,getProviderAppName会降级为从接口全限定名中截取包名前缀作为应用名(见 CatTransaction.java),保证数据不中断。
说明:
CatRegistryFactoryWrapper的使用需要额外配置注册中心工厂的包装扩展(通过 Dubbo SPI 的qos-registry-factory或 XML 中显式指定),接入时请结合自身 Dubbo 配置方式确认该环节是否生效;核心的 Filter 埋点(第三、四、五节)则无需任何额外配置,随依赖自动激活。
七、从入门到排查:接入路径小结
- 确认 Dubbo 版本线:Apache Dubbo 2.7.x 使用 integration/apache-dubbo,阿里 Dubbo 2.x 使用 integration/dubbo;
- 引入依赖:在服务提供方和消费方应用(尤其是 RPC 入口应用)的 pom.xml 中加入
net.dubboclub:cat-monitor:0.0.6; - 确认 CAT 客户端配置:插件依赖
com.dianping.cat:cat-client(pom.xml 中声明 2.0.0),需保证各应用已正确接入 CAT 客户端(域名、服务端地址等),否则Cat.getManager().isCatEnabled()为 false,监控不会生效; - 观察报表:部署后即可在 CAT 服务端查看 cross、dependency、matrix 与 trace 数据,验证耗时与异常统计是否符合预期;
- 动态控制:特殊场景下可通过
DubboCat.disable()/DubboCat.enable()在不重启应用的情况下启停监控。
通过上述步骤,Dubbo 服务的调用耗时、超时、业务异常、网络异常即可自动沉淀为可检索、可告警的监控数据,为微服务架构下的故障定位与服务治理提供第一手依据。
【免费下载链接】catCAT 作为服务端项目基础组件,提供了 Java, C/C++, Node.js, Python, Go 等多语言客户端,已经在美团点评的基础架构中间件框架(MVC框架,RPC框架,数据库框架,缓存框架等,消息队列,配置系统等)深度集成,为美团点评各业务线提供系统丰富的性能指标、健康状况、实时告警等。项目地址: https://gitcode.com/gh_mirrors/ca/cat
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考