做微服务排查的人,应该都有过这种经历:接到一个“订单提交特别慢”的工单,打开监控平台想顺着链路找瓶颈,结果发现调用链在某个环节突然断了——前面还能看到 HTTP 入口、Feign 调用、Redis 查询,可一到消息发送或者分库分表的数据访问,就只剩孤零零的一截。这时候十有八九不是业务代码的问题,而是 APM 对那个中间件“不认识”。SkyWalking 作为目前使用率很高的开源应用性能监控系统,解决这个问题的核心,就是它那张“支持的中间件清单”。这篇文章我就围绕 Spring Cloud、Dubbo、RocketMQ、ShardingSphere 这几个高频组件,把 SkyWalking 的中间件支持范围、背后原理、接入验证和排坑经验一次性讲清楚。
选择 SkyWalking 的团队,绝大多数不是冲着 UI 有多炫,而是看中它的 Agent 能在一大堆中间件上自动埋点。这也就意味着,搞清楚“支持清单”比搞清楚告警规则更优先——清单决定了一条调用链能画多完整,也决定了你后续做拓扑分析、慢 SQL 定位、异常追踪时会不会出现盲区。下面我按“为什么需要重视清单—清单到底长什么样—重点中间件逐个拆解—原理实现—实操验证—问题排查”的顺序来写,新手可以照着跑一遍验证流程,老手也可以直接跳到第三、第六节看重点。
1. 为什么“支持哪些中间件”是选型的第一道门槛
1.1 链路中断是分布式监控里最常发生的事故
我见过不止一个团队在引入 APM 时踩同一个坑:光看官网首页截图觉得功能很全,装完 Agent 后才发现,自己项目里最关键的调用链“断”了。比如服务之间的调用走的是自研 RPC,或者消息队列用的是某个不太主流的客户端版本,这些场景往往没有对应插件。链路一旦断在半路,调用链图上就会出现断层:前半段的时间瀑布图是完整的,后半段只有一个黑盒 Span,耗时压在里面根本定位不到具体方法。
这里的关键点在于:一个 APM 能不能用,不是由 UI 功能决定的,而是由插件生态决定的。SkyWalking 的 Java Agent 之所以在这个问题上口碑不错,正是因为它把支持的中间件以“插件”的方式组织起来,形成了覆盖 HTTP、RPC、MQ、数据库、缓存、注册中心、网关等几大类别的长清单。链路追踪其实很像拼积木,每个插件负责在特定框架或客户端上“搭”一个 Span 出来,而你不希望调用链拼到一半突然缺了一块积木。
1.2 SkyWalking 的插件机制让支持清单具备可扩展性
讲支持清单之前,先有必要知道 SkyWalking 的插件是怎么工作的。Java Agent 本身不直接写死“每种中间件怎么埋点”,而是在启动时扫描 plugins 和 optional-plugins 目录下的所有插件 jar。每个插件通过字节码增强技术,在目标中间件客户端的关键方法(比如发请求、收响应、执行 SQL、发送消息)前后插入探针代码,自动创建和结束 Span,再透传 Trace ID。
这种机制带来两个直接影响:第一,业务代码可以保持零侵入,不用手动在方法上加注解或者埋点;第二,供给清单是动态的,你可以通过加插件 jar 的方式扩展支持范围,不一定要升级整个 Agent。哪怕某个中间件没有官方插件,理论上也能通过自定义插件、手动埋点、OpenTelemetry 接入等方式补上。所以,网络上经常有人截图贴“SkyWalking 支持中间件清单表”来讨论,其实那张表代表的只是一个“快照”,真正重要的是你理解这张表为什么长这样、每个格子背后对应哪个插件。
2. 最新支持清单总览:哪些中间件可以直接“插上就用”
2.1 一份可对照的中间件支持总表
先放一份按使用场景分类的总表。这张表不是从某个版本说明书里完整抄出来的,而是我把平时部署中验证过的组件归纳在一起,标注了插件归属(默认 plugins 直接生效,还是 optional-plugins 需要手动启用),方便你对照自己项目的技术栈。
| 分类 | 常见中间件 / 框架 | 插件归属 | 使用备注 |
|---|---|---|---|
| Web 与 HTTP | Tomcat、Jetty、Undertow、Servlet、Spring MVC、Spring Boot | 默认启用的插件 | 绝大多数 Java Web 服务开箱即用 |
| 服务间调用 | Spring Cloud(Feign、RestTemplate)、Spring Cloud Gateway、Zuul | 默认启用 / Optional | Gateway 的插件在某些版本需要单独拷贝 |
| RPC | Dubbo、gRPC、Motan、SOFARPC | 默认启用 | Dubbo 3.x 与 2.x 的插件包不同 |
| 消息队列 | RocketMQ、Kafka、RabbitMQ、ActiveMQ | 默认启用 / Optional | RocketMQ 支持 4.x 与 5.x,注意版本匹配 |
| 数据库访问 | MySQL、PostgreSQL、Oracle、SQLServer、H2、SQLite、MariaDB | 默认启用 | 通过 JDBC 层拦截 SQL 执行 |
| 分库分表 | ShardingSphere 以及 ShardingSphere-JDBC / Proxy | 默认启用 / Optional | JDBC 模式和 Proxy 模式的接入方式不一样 |
| 缓存 | Redis(Jedis、Lettuce、Redisson)、Memcached | 默认启用 | 关注命令耗时与缓存穿透场景 |
| 搜索引擎 | Elasticsearch(TransportClient / RestClient)、OpenSearch | 默认启用 / Optional | 版本跨度大,插件需按客户端版本选 |
| 注册与配置 | Nacos、Zookeeper、Consul、Eureka、Apollo | 默认启用 / Optional | 主要用于服务发现阶段的 Span 显示 |
| 异步与并发 | CompletableFuture、线程池、Java 并发工具、TimerTask、Spring @Async | Optional 居多 | 跨线程链路传递建议重点检查 |
| 响应式 | Spring WebFlux、WebClient、Reactor 相关 | Optional / 较晚加入 | 需要复制插件并关注响应式上下文传播 |
这张表的价值在于,你一眼就能发现自己技术栈里哪些组件是“不需要动手的”,哪些是“需要去 optional-plugins 里翻一翻的”。我自己的习惯是,每次新项目定技术选型时,先把表里列出的组件和版本对照一遍,再决定要不要为“灰区”组件准备替代方案。
2.2 如何快速判断手头的中间件是否被覆盖
遇到表里没写的组件,不用急着焦虑,可以按下面三步走。
第一步,看 Agent 解压后的 plugins 目录。目录下有大量命名规整的 jar,比如 apm-dubbo-xx.jar、apm-rocketmq-xx.jar、apm-shardingsphere-xx.jar,直接搜索关键词最快。第二步,看 optional-plugins 目录。有些插件因为可能影响性能或者依赖特殊版本,不会默认激活,而是放在 optional 下,需要手动复制到 plugins 目录。第三步,看官方文档。SkyWalking 的官方文档会按插件列出支持的版本范围,版本匹配度比“有没有插件”更重要。
这里有一个特别值得说的细节:判断“支持”不能只看插件包存在,还要看 Agent 版本和后端版本。插件一般跟着 Agent 大版本走,比如你用 9.x 的 Agent,就需要匹配同大版本的后端服务,否则上报数据可能因为协议版本不同而解析异常。实际项目里我见过不少“插件明明加载成功,UI 里却看不到链路”的案例,最后发现都是 Agent 与后端版本错配。
2.3 关于 WebFlux 与响应式场景的特别提醒
Spring Cloud 技术栈现在已经普遍迈向 Spring Boot 3.x 和 Spring WebFlux,监控在响应式场景下的支持相对敏感。SkyWalking 对 WebFlux、WebClient、Reactor 的支持是有的,但插件通常放在 optional-plugins 目录,需要根据版本手动启用。而且在响应式链路里,上下文传播方式与传统 ThreadLocal 模型差异很大,很多链路断层发生在订阅线程切换时,并不是插件不支持,而是 Agent 默认的传播机制在响应式调用链上需要配合额外配置。
这个点我建议使用 WebFlux 的团队做接入验证时单独测一条分钟级的全链路请求:从前端网关打到 WebFlux 服务,再通过 WebClient 调用下游,最后看链路图上是否完整。如果中途断掉,先确认 optional 插件是否已复制,再去官方文档核对 reactor 相关支持的版本要求。
3. 重点中间件逐个拆解:Spring Cloud、Dubbo、RocketMQ、ShardingSphere
3.1 Spring Cloud:从 Feign 到 Gateway 的完整调用链
Spring Cloud 之所以排在标题第一位,因为它是微服务落地时最主流的技术栈,也是 SkyWalking 插件支持最“饱满”的一块。在 Spring Cloud 体系里,调用链上的每一段几乎都有对应插件:服务接收方有 Spring MVC / Servlet 插件,服务调用方有 OpenFeign、RestTemplate、OkHttp、Apache HttpClient 插件,网关层有 Spring Cloud Gateway 和 Zuul 插件。这意味着从一个入口请求进来,经过网关、Feign 调用、下游服务处理,再到数据库访问,Span 都能被自动串联起来。
实际接入时要注意两点。第一,Feign 调用是否真的能形成跨服务链路,不仅取决于 Feign 插件,还取决于下游服务是否也挂了 SkyWalking Agent。只要有一端没挂,链路就会断在下游入口,现象是“上游能看到调用出口,下游看不到调用入口”。第二,Spring Cloud Gateway 的插件往往不在默认加载列表里。我遇到过网关链路在图谱上只有孤零零一个节点的情况,后来查日志发现是 gateway 插件没有被激活。把 optional-plugins 里的对应 jar 复制到 plugins 目录并重启服务后,拓扑图立刻恢复了完整的网关上下游关系。
3.2 Dubbo:跨进程与跨线程的链路上下文传递
Dubbo 是 RPC 场景里很典型的代表。SkyWalking 对 Dubbo 的支持分两个方向:消费端发出调用时,会把 TraceId 和 ParentSpanId 放进 Dubbo 的隐式传参里;提供端收到请求后,从隐式传参中取回上下文并创建新的 Span。所以只要两端都挂了 Agent,一次 Dubbo 调用的上下游关系是自动关联的,不需要改任何业务代码。
需要留神的细节是 Dubbo 版本差异。老项目常用 Dubbo 2.6/2.7,新项目可能直接用 3.x,不同版本在上下文传递机制上有变化,SkyWalking 针对不同大版本的插件命名不一样,使用前最好核对插件支持的版本范围。另外一个常见坑是异步泛化调用:很多团队用 Dubbo 的泛化调用做网关路由,但异步线程里如果不显式绑定上下文,Trace 会在异步边界断掉。解决思路有两个,一是确认线程池相关的可选插件已经被启用,二是在业务侧通过 ContextManager 手动把上下文传递到异步任务中。
3.3 RocketMQ:异步消息场景怎么保住一条链路
很多微服务到了中后期,都会把部分同步调用改成 RocketMQ 异步消息。这是业务上常见的改造,但对 APM 来说却是一次真正的考验:消息发送方给完 MQ 就返回了,消费者在另一个服务、另一个线程池里拉取消息继续处理,两段业务之间没有直接的 HTTP 或 RPC 调用,链路怎么连起来?
SkyWalking 的做法是在生产者发送消息时,把链路上下文写入消息的 properties;消费者消费消息时,从消息属性里取出上下文并重建 Span。所以只要生产者、消费者两端都接入 Agent,并且 RocketMQ 插件正常加载,“发送消息”和“消费处理”会出现在同一条 Trace 里。我在一个订单项目里实测过:用户下单后,订单服务发一条“创建订单成功”的消息给积分服务,积分服务消费后更新积分,整条链路在拓扑图上能从用户请求一路画到 Redis 写入,消息中间的那一段也能清楚看到耗时在哪。
操作上要注意两点:第一,如果消费者端的消费线程是自定义线程池,需要检查线程池相关插件是否启用,否则上下文在 submit 到 worker 线程时容易丢;第二,RocketMQ 客户端版本跨度较大,4.x 和 5.x 的 API 有差异,插件版本要与客户端版本适配,升级客户端时最好同步检查 Agent 插件版本。
3.4 ShardingSphere:分库分表下的 SQL 追踪
ShardingSphere 在标题里的出现,说明很多人已经开始关心“分库分表之后,监控能不能看到真实执行 SQL”这个问题。分库分表中间件会改写 SQL,比如一张订单表被拆到 8 个分片里,直接看数据库的慢查询日志只能看到物理库上的分片 SQL,很难对上业务侧的原始逻辑 SQL。SkyWalking 对 ShardingSphere 的插件正好解决了这个痛点:它能在 ShardingSphere 的路由改写层抓取逻辑 SQL 和实际路由后的物理 SQL,帮助你在监控图上看到 SQL 是从哪个服务发起、改写成哪些分片语句、每个分片的执行耗时。
ShardingSphere 有 JDBC 模式和 Proxy 模式,接入方式不太一样。JDBC 模式下,应用直接依赖 ShardingSphere-JDBC,SkyWalking 的 JDBC 相关插件可以通过拦截数据源操作拿到对 ShardingSphere 的执行链路;Proxy 模式下,应用先连到一个独立部署的数据库代理层,再转发到底层数据库,链路追踪要看这一跳在 SkyWalking 网关/数据库之间的呈现是否清晰。实际使用中,我建议同时看服务和数据库两端的 Span,才能判断“慢”到底是分片路由耗时,还是底层数据库耗时。
3.5 其他高频组件:MySQL、Redis、Kafka、ES、MongoDB
除了标题点名的四个组件,日常技术栈里还有几个出镜率极高的中间件,值得顺带说。
MySQL 属于最基础、也最让人放心的支持项。SkyWalking 通过 JDBC 层统一拦截,无论是 Spring Data、MyBatis 还是原生 JDBC,SQL 执行过程和耗时都能上链路。Redis 的 Jedis、Lettuce、Redisson 都有插件,连 Lua 脚本执行也能被追踪到。Kafka 的插件模式和 RocketMQ 类似,生产消费两端都传递上下文。Elasticsearch 的情况稍微特殊一点:它同时是数据库和搜索引擎,插件要跟客户端版本(TransportClient、RestClient 还是新客户端)匹配,用错了版本会出现“ES 调用不在链路里”的现象。MongoDB 的插件相对中规中矩,如果你用的驱动版本比较老,建议在测试环境先用一条真实查询验证一次。
4. “支持”背后是怎么实现的:从 Agent 到字节码增强
4.1 一个请求进来,插件到底做了什么
理解了清单,再往里走一层,我们要搞清楚“支持”这件事到底是谁在干活。以一次 MySQL 查询为例:应用代码调用 MyBatis 的 mapper 方法,最终会走 JDBC 驱动。SkyWalking 的 mysql 插件通过字节码增强,在 JDBC 的 Connection 创建、Statement 执行、ResultSet 获取等关键方法上做了拦截,把这些位置变成链路上的 Span。Span 记录的信息包括 SQL 语句、数据库实例、执行耗时、异常堆栈,UI 里看到的就是一条慢 SQL 明细。
这套机制的优点很明显:业务代码完全没有侵入,不需要在每个 Mapper 上面加工具方法,也不需要改 SQL。缺点是需要做字节码增强,对 Java 版本和框架版本比较敏感。字节码增强本身也是动态的,Agent 在应用启动时把增强逻辑注入到目标类的字节码中,如果你的框架升级后方法签名变了,旧插件就会失效。很多“升级完 Spring Boot 之后链路突然少了”的诡异现象,根因其实就在这。
4.2 上下文传播机制:ThreadLocal 与跨线程传递
链路上下文在单线程里传递比较简单,本质上是一个 ThreadLocal 变量,Span 开始、结束都围绕同一个线程。但现实业务里异步无处不在:线程池、消息队列、CompletableFuture、Spring @Async。每当执行线程从 A 切到 B,ThreadLocal 里的上下文就断了。SkyWalking 针对这个问题专门做了跨线程传递插件,原理是拦截线程池的提交方法,在创建新任务时把上下文快照传进去,在新线程中恢复。
这就是为什么前面反复强调 Optional 插件要检查:线程池插件、异步工具插件都属于可选加载项。如果你在自己的异步代码里发现 Trace 断成两截,先不要怀疑中间件没被支持,重点检查“异步插件是否已启用”和“是否用官方插件支持的线程池实现方式”。从实际操作角度讲,接入了 Agent 之后跑一个包含异步调用的测试用例,是验证这块最直接的手段。
4.3 关键配置项与目录结构
Java Agent 的目录结构虽然不算复杂,但有些配置项容易忽略。解压 Agent 压缩包后会看到 skywalking-agent.jar、config 目录、plugins 目录、optional-plugins 目录。启动应用时用 -javaagent 指定 skywalking-agent.jar 的路径即可,常见配置一般集中在 config/agent.config 文件或者启动参数里的 -Dskywalking.xxx=xxx。我个人最常用的几个配置项是 agent.service_name(服务名)、collector.backend_service(后端地址)、agent.namespace(用来区分多环境命名空间,比如测试环境与生产环境共用一套后端时避免串数据)。
另一个容易被忽略的是插件开关:并不是 plugins 目录里有多余 jar 就必须全部启用。对于不需要监控的某些中间件,保留插件反而会带来额外的增强开销和日志噪音。如果项目里没有用到某个组件,可以放心把对应插件 jar 移走,或者通过插件相关配置控制加载范围。这是一项非常实用的优化手段,尤其在高并发、对启动速度敏感的生产应用中,精简插件列表比无脑全量开启更合理。
5. 实操验证:让中间件清单在你的环境里真正生效
5.1 环境准备与 Agent 接入
接入 SkyWalking 的第一步是拿到 Java Agent 压缩包,把它放到统一目录,比如 /opt/skywalking,方便多应用复用。接着在应用的启动参数里加上 -javaagent,典型命令长这样:
java \ -javaagent:/opt/skywalking/skywalking-agent.jar \ -Dskywalking.agent.service_name=order-service \ -Dskywalking.collector.backend_service=10.0.0.8:11800 \ -jar order-service.jar注意 -javaagent 必须放在主类或 jar 参数之前,如果顺序不对,应用可能不会加载 Agent。这里我用了一个示例内网地址 10.0.0.8:11800,实际部署时替换成你的后端 OAP 地址即可。端口 11800 是后端 gRPC 上报端口,如果你用了 HTTP 上报或者其他自定义端口,相应调整即可。
5.2 验证插件加载:用日志确认
Agent 接入成功后,应用启动日志里会出现 SkyWalking 相关的 banner 和插件加载信息。想快速确认某个中间件插件是否生效,可以直接在启动日志里搜索插件名。我自己通常会用类似下面的命令把日志过滤一下:
nohup java -javaagent:/opt/skywalking/skywalking-agent.jar \ -Dskywalking.agent.service_name=order-service \ -jar order-service.jar > app.log 2>&1 & grep -i "plugin" app.log | head -n 30正常情况下你能看到 Dubbo、RocketMQ、ShardingSphere、MySQL 等插件被加载的日志条目。如果发现某个中间件插件不在加载列表里,先检查它是否在 optional-plugins 目录而没有复制到 plugins 目录。这是一个很常见、也很容易在代码 review 时被忽略的动作。
5.3 用一条 Spring Cloud + RocketMQ 链路做端到端验证
工具装好以后,最有说服力的验证是跑一条完整链路。我以一个简化版微服务场景举例:入口服务通过 Feign 调用订单服务,订单服务发一条 RocketMQ 消息,消费者服务(比如积分服务)收到消息后查询一次 MySQL。所有服务都接同一个 Agent 和同一个后端,然后在后端 UI 里查看拓扑图和 Trace 明细。
验证时有几个可观察的点:拓扑图应该显示入口服务、订单服务、RocketMQ、积分服务、MySQL 几个节点,节点之间连线清晰;点进任意一条 Trace,应该能看到 HTTP 入口、Feign 调用、消息发送、消息消费、SQL 执行等完整 Span 序列;如果一个节点缺失,基本可以断定是对应服务的插件没生效,或者服务没有正常上报数据。这套验证流程花费时间不长,但能把“支持清单”从纸面变成实打实的可用能力。
6. 常见问题与排查技巧实录
6.1 高频问题速查表
把我在实际使用中遇到的典型问题整理成一张速查表,方便你在出问题时按图索骥:
| 问题现象 | 可能原因 | 排查方法 |
|---|---|---|
| 服务在 UI 上只有一个节点,没有任何依赖边 | 服务间调用关系没识别到,多半是某个中间件插件没生效 | 检查该服务启动日志里的插件加载情况 |
| Trace 在消息消费端断掉 | RocketMQ / Kafka 插件未加载,或消费端线程池插件未启用 | 确认 MQ 插件在 plugins 目录,检查消费线程模型 |
| Feign 调用的下游服务没有入口 Span | 下游服务没有接入 Agent,或者 Agent 与服务端版本不匹配 | 确认下游服务启动参数和后端版本 |
| 网关链路缺失 | Spring Cloud Gateway / Zuul 插件在 optional-plugins 中未启用 | 复制对应插件到 plugins 目录后重启 |
| SQL 执行耗时看不到明细 | JDBC 插件未加载,或数据库用了非 JDBC 连接池 | 检查插件与连接池版本,确认使用的访问方式 |
| 异步调用链路断裂 | 线程池、CompletableFuture 等异步插件未启用 | 启用 Optional 插件,重新验证异步场景 |
| 升级框架版本后链路消失 | 插件版本与客户端版本不兼容 | 回归测试,并核对官方文档中的版本兼容区间 |
这张表不一定覆盖所有怪问题,但能覆盖绝大多数“刚接入 APM 时最容易遇到”的情况。排查的大方向其实是一致的:先确认插件加载,再确认版本匹配,最后才怀疑后端解析问题。
6.2 真没有插件时怎么办:兼容手段清单
如果经过所有检查,某个自研框架、内部客户端或小众中间件确实没有现成插件,也别慌,有几条可行的路线可以尝试。
第一条路线是用 SkyWalking 提供的自定义增强插件(apm-customize-enhance-plugin),通过配置描述目标类和方法,实现轻量级埋点。这种方式不需要写 Java 代码,适合快速补点。第二条路线是在业务代码里调用 SkyWalking 的 OpenTracing 兼容 API 手动创建 Span,代码稍有侵入,但能在关键业务方法上保留精确的耗时和上下文。第三条路线是接入 OpenTelemetry 数据,较新版本的 SkyWalking 后端支持通过 OTLP 协议接收 OpenTelemetry 产生的 Trace 数据,这样可以借助 OTel 生态的丰富组件覆盖官方插件未覆盖的中间件。第四条路线是自己基于官方插件框架写一个自定义插件,适合有技术能力、愿意长期维护的团队。
这几条路线没有绝对的好坏,取决于你对“零侵入”的要求有多高,以及这个中间件在你的系统里有多关键。如果只是为了“图上不断链”,OpenTelemetry 接入是最省事的路径之一。
6.3 版本兼容性:最容易被忽略的坑
最后专门再说说版本兼容性。这是我在实际项目里踩过最多坑的地方,也是最容易在最终交付时被忽略的地方。SkyWalking 的 Java Agent、后端 OAP、前端 UI 之间通常保持同大版本组合才最稳。混用大版本可能表现为“Agent 显示上报成功,但 UI 没有数据”或“采样数据周期性地丢失”。
中间件客户端版本与插件版本的匹配同样敏感。比如 RocketMQ 客户端如果从 4.x 升到 5.x,你依然要验证消息消费端是否还能正确传递上下文;ShardingSphere 如果从旧版本往新版本升级,路由 SQL 的结构可能变化,插件拦截到的字段也可能变化。我的建议很简单:给每个中间件的“客户端版本—Agent 插件版本”组合做一次回归测试,并把验证结果记录在项目维护文档里,避免每次升级都靠猜。顺手提一个不算技巧的技巧:升级 Agent 或中间件客户端时,把升级前的链路截图留一份,升级后对比同一场景的 Trace 结构,哪里断了立刻就能看出来。
我在实际项目里最大的体会是,SkyWalking 的“支持清单”不是一份躺在官网上的静态表格,而是一套工程能力:选型前对照清单决定监控盲区,接入后通过日志和 UI 验证清单真实生效,升级前回归验证清单覆盖的中间件可用性。你把这三个环节都用起来,中间件支持这个问题才不会反复成为告警排查时的暗坑。希望这篇内容能帮你少走一些弯路。