前十五篇,我们从全景架构一路拆到了客户端 SDK 的每一根神经末梢——HTTP/WebSocket 双通道、加解密体系、心跳机制、JVM 采集、线程池监控、Spring Boot Starter 自动配置、SpringMVC Integrator 适配,以及业务告警 API。客户端的故事讲完了,但监控数据的旅程才刚过半。从这一篇开始,我们进入 Phoenix 的第三大模块——代理端(phoenix-agent)。如果说客户端是散落在各个应用进程里的「感知神经」,那代理端就是部署在服务器上的「区域大脑」——它既能自己采集基础设施信息,又能充当客户端与服务端之间的「中转枢纽」。
一、为什么需要代理端?
在第一篇全景概览中,我们知道 Phoenix 的四端分离架构包含客户端、代理端、服务端和 UI 端。客户端负责采集 Java 应用内的运行时数据(心跳、JVM、线程池),服务端负责数据持久化和告警触发。乍看之下,客户端直连服务端就能完成整个闭环——那代理端的存在价值是什么?
答案藏在三个现实场景中:
场景一:操作系统级的信息没人采集。 客户端作为一个嵌入到业务应用中的 SDK,它能拿到 JVM 内部的数据,但 CPU 利用率、内存使用、磁盘 IO、网卡流量这些操作系统级的指标,并不是每个 Java 应用都应该关心的——尤其当同一台服务器上跑了多个应用时,让每个应用都去采集一遍服务器信息既浪费资源又产生冗余。代理端作为「驻守在服务器上的哨兵」,只需要部署一个实例,就能统一采集该服务器的所有硬件信息。
场景二:Docker 和网络设备需要专门的采集能力。 Docker 容器的状态、镜像列表、资源统计、实时事件,需要通过 Docker API 交互;交换机和路由器的信息,需要通过 SNMP 协议采集。这些能力显然不应该塞进轻量级的客户端 SDK 里——它们需要一个独立的、拥有更丰富技术栈的运行载体。
场景三:网络壁垒需要突破。 在很多企业环境中,内网应用无法直接访问监控服务端——中间隔着防火墙、跳板机、网络分区。代理端可以部署在跳板机上,充当「数据中继站」:内网客户端将监控数据发送给代理端,代理端再转发给服务端。一道网络壁垒,就这样被优雅地绕过了。
用一句话概括代理端的核心价值:它是基础设施的信息采集者,也是网络拓扑中的数据中转站。
二、双重身份:采集者 + 中转站
理解代理端的关键,在于认清它的「双重身份」。
2.1 身份一:基础设施的信息采集者
代理端部署在目标服务器上,利用各类技术栈采集基础设施信息:
| 采集对象 | 技术手段 | 采集内容 |
|---|---|---|
| 服务器硬件 | oshi / Sigar | CPU、内存、磁盘、网卡、进程、负载、GPU、电池、传感器 |
| Docker 服务 | docker-java | 系统概况、容器列表、镜像列表、实时事件、资源统计(CPU/内存/网络IO/磁盘IO) |
| 网络设备 | snmp4j | 设备在线状态、系统信息、接口信息 |
这些数据会被定时采集、封装成数据包,经过加密后发送到服务端持久化。
2.2 身份二:客户端的数据中转站
代理端对外暴露了一组 REST 接口,接收来自客户端的各类监控数据包,然后「原封不动」地转发给服务端。这组接口包括:
| 接口类别 | 端点 | 作用 |
|---|---|---|
| 心跳包 | /heartbeat/accept-heartbeat-package |
接收客户端心跳并转发 |
| 告警包 | /alarm/accept-alarm-package |
接收客户端告警并转发 |
| 异常包 | /exception/accept-exception-package |
接收客户端异常信息并转发 |
| JVM 信息包 | /jvm/accept-jvm-package |
接收客户端 JVM 信息并转发 |
| 下线包 | /offline/accept-offline-package |
接收客户端下线通知并转发 |
| 命令包 | /command/accept-command-package |
接收并执行命令(如 Docker 操作) |
从客户端的视角看,代理端和服务端是「无差别」的——客户端配置文件中的 monitoring.comm.http.url 既可以指向服务端,也可以指向代理端,接口规范完全一致。这种透明中转的设计,让部署拓扑的调整不需要修改任何客户端代码。
2.3 一个不容忽视的事实:代理端自己也是客户端
打开 phoenix-agent 的 pom.xml,你会发现一个有趣的依赖:
<!-- springboot项目集成监控依赖,使之拥有监控功能 -->
<dependency>
<groupId>com.gitee.pifeng</groupId>
<artifactId>phoenix-client-spring-boot-starter</artifactId>
</dependency>
代理端引入了 phoenix-client-spring-boot-starter——这意味着代理端自身也是一个被监控的应用实例。它会向服务端发送心跳、上报 JVM 信息、采集自己所在服务器的硬件数据。「监控者自身也被监控」——这种设计确保了代理端不会成为监控盲区。
三、工程结构:按职责分包
代理端的源码组织清晰地映射了它的「双重身份」。先看整体包结构:
com.gitee.pifeng.monitoring.agent
├── AgentApplication.java # SpringBoot 启动入口
├── config/ # 配置类
│ ├── RestTemplateConfig.java # HTTP 连接池配置
│ ├── ThreadPoolConfig.java # Docker 采集线程池配置
│ ├── WebSocketConfig.java # WebSocket 端点配置
│ ├── Knife4jConfig.java # API 文档配置
│ ├── MonitoringAgentDevConfig.java # 开发环境监控配置
│ └── MonitoringAgentProdConfig.java # 生产环境监控配置
├── constant/
│ └── UrlConstants.java # 服务端 URL 常量(30+ 个端点地址)
├── core/
│ ├── AgentPackageConstructor.java # 代理端数据包构造器
│ └── MethodExecuteHandler.java # 方法执行助手(核心调度器)
├── business/
│ ├── client/ # 面向客户端的接口层
│ │ ├── controller/ # 18 个 REST 控制器
│ │ └── service/ # 10 个服务接口 + 实现
│ ├── server/ # 面向服务端的通信层
│ │ └── service/ # 12 个服务接口 + 实现
│ └── agent/ # 代理端自身的采集层
│ ├── collector/ # 采集器(DockerCollector)
│ ├── core/docker/ # Docker 中央控制器 + 事件回调
│ └── thread/ # 采集线程(DockerThread)
└── util/docker/ # Docker 工具类
├── DockerClientUtils.java # 连接管理
├── DockerContainerUtils.java # 容器操作
├── DockerImageUtils.java # 镜像操作
├── DockerInfoUtils.java # 系统信息
└── DockerStatsUtils.java # 资源统计
三个关键的 business 子包,精确对应了三个职责域:
business.client:对「下游」——接收客户端发来的数据包business.server:对「上游」——把数据包转发给服务端business.agent:对「自身」——代理端自己的采集任务
这种分包方式的好处在于,当你想了解「代理端是怎么接收客户端心跳的」,直奔 business.client.controller.HeartbeatController;想了解「心跳包是怎么转发给服务端的」,转向 business.server.service.IHeartbeatService;想了解「Docker 信息是怎么采集的」,翻开 business.agent.collector.DockerCollector。职责边界一目了然。
四、启动入口:AgentApplication
@Slf4j
@Indexed
@EnableRetry
@SpringBootApplication
@ComponentScan(nameGenerator = UniqueBeanNameGenerator.class)
public class AgentApplication extends CustomizationUndertowWebServerFactoryCustomizer {
public static void main(String[] args) {
// 计时器
TimeInterval timer = DateUtil.timer();
SpringApplication.run(AgentApplication.class, args);
// 时间差(毫秒)
String betweenDay = timer.intervalPretty();
log.info("监控代理端启动耗时:{}", betweenDay);
}
}
短短十几行,暗藏四个设计决策:
1. @EnableRetry——重试能力。 代理端的核心操作是「转发数据到服务端」,网络请求天然不可靠。@EnableRetry 开启 Spring Retry 支持,让服务层方法可以通过 @Retryable 注解获得自动重试能力。在分布式场景中,这是对抗瞬时故障的基础设施。
2. CustomizationUndertowWebServerFactoryCustomizer——Undertow 定制。 代理端选择 Undertow 而非 Tomcat 作为嵌入式 Web 服务器。Undertow 以轻量、高性能著称,非常适合代理端这种「高吞吐转发」的场景。继承这个定制器可以对 Undertow 做细粒度调优。
3. UniqueBeanNameGenerator——Bean 命名策略。 代理端同时依赖了 phoenix-common-web 和 phoenix-client-spring-boot-starter 两大模块,不同模块中可能存在同名的 Bean 定义。使用全限定类名作为 Bean ID,从根本上杜绝了命名冲突。
4. @Indexed——加速组件扫描。 @Indexed 注解配合 spring-context-indexer 在编译期生成组件索引文件,避免运行时的 classpath 扫描。对于包含大量组件的代理端而言,这能显著缩短启动时间。
五、数据中转:从接收到转发
代理端最核心的职责之一是「透明中转」——接收客户端的数据包,然后转发给服务端。这个流程涉及三层:Controller → Client Service → Server Service。
5.1 Controller 层:18 个入口
代理端总共有 18 个 REST 控制器,覆盖了所有类型的数据交互。按功能可以分为两大类:
第一类:数据包转发型。 接收客户端的数据包,通过对应的 Service 转发给服务端。
@Deprecated
@PostMapping("/accept-alarm-package")
public BaseResponsePackage acceptAlarmPackage(@RequestBody AlarmPackage alarmPackage) {
return this.alarmService.dealAlarmPackage(alarmPackage);
}
以 AlarmController 为例——接收告警包,交给 IAlarmService 处理。注意 @Deprecated 注解,这并不意味着接口废弃,而是标记该接口是「遗留的 HTTP 接口」。在新版本中,客户端已经通过 WebSocket 通道发送这些数据包,HTTP 接口保留只是为了向后兼容。
第二类:请求代理型。 接收客户端的请求,转发给服务端处理后,把服务端的响应原样返回。
@PostMapping("/test-monitor-db")
public BaseResponsePackage testMonitorDb(@RequestBody BaseRequestPackage baseRequestPackage)
throws NetException {
return this.baseRequestPackageService.dealBaseRequestPackage(
baseRequestPackage, UrlConstants.TEST_MONITOR_DB_URL);
}
以 DbController 为例——客户端请求测试数据库连通性,代理端并不自己执行测试,而是把请求原封不动地转发给服务端,服务端执行完测试后返回结果,代理端再传递给客户端。这类接口涵盖了数据库连通性测试、网络连通性测试、HTTP 连通性测试、MySQL/Oracle 会话管理、Redis/Mongo 信息查询等。
5.2 MethodExecuteHandler:核心调度器
MethodExecuteHandler 是代理端数据转发的核心枢纽。它通过 InvokerHolder(命令执行器管理器)查找注册的服务实例,调用对应的方法将数据包发往服务端:
public static BaseResponsePackage sendAlarmPackage2Server(AlarmPackage alarmPackage) {
// 通过命令执行器管理器,获取指定的命令执行器
Invoker invoker = InvokerHolder.getInvoker(IAlarmService.class, "sendAlarmPackage");
// 执行命令,返回执行结果
return execute(invoker, alarmPackage);
}
为每一种数据包类型(心跳、告警、异常、服务器、JVM、Docker、网络设备、命令、下线、基础请求),MethodExecuteHandler 都提供了一个专门的 sendXxxPackage2Server() 方法。这种设计虽然看起来有些「手工路由」的味道,但胜在类型安全——每个方法的参数和返回值都是明确的 DTO 类型,编译期就能发现类型错误。
execute() 方法是所有转发操作的统一出口:
public static BaseResponsePackage execute(Invoker invoker, Object... objects) {
BaseResponsePackage responsePackage;
try {
assert invoker != null;
Object object = invoker.invoke(objects);
responsePackage = (BaseResponsePackage) object;
} catch (Exception e) {
Result result = Result.builder().isSuccess(false).msg(e.getMessage()).build();
responsePackage = AGENT_PACKAGE_CONSTRUCTOR.structureBaseResponsePackage(result);
}
return responsePackage;
}
任何转发失败都不会向上抛出异常——而是构造一个 isSuccess = false 的响应包返回。这和客户端 Monitor.sendAlarm() 的设计理念一脉相承:监控链路上的故障不应该向调用方传播。
5.3 UrlConstants:30+ 个端点地址
UrlConstants 类定义了代理端与服务端之间所有的通信端点地址,根 URI 从监控配置中动态读取:
private static final String ROOT_URI = ConfigLoader.getMonitoringProperties()
.getComm().getHttp().getUrl();
public static final String HEARTBEAT_URL = ROOT_URI + "/heartbeat/accept-heartbeat-package";
public static final String ALARM_URL = ROOT_URI + "/alarm/accept-alarm-package";
public static final String SERVER_URL = ROOT_URI + "/server/accept-server-package";
// ... 30+ 个端点
这意味着代理端和服务端之间的接口路径是完全对称的。代理端的 /alarm/accept-alarm-package 接收客户端的告警包,然后转发到服务端的 /alarm/accept-alarm-package——路径一模一样,只是主机和端口不同。这种对称设计极大地降低了理解和维护成本。
六、信息采集:Docker 收集器的四步编排
代理端「自主采集」的能力主要集中在 Docker 信息采集上(服务器信息和网络设备信息的采集依赖客户端 SDK 的内置能力,因为代理端本身就是一个客户端实例)。Docker 采集的启动流程是一个精心设计的四步编排。
6.1 DockerCollector:CommandLineRunner 的巧妙运用
@Component
public class DockerCollector implements CommandLineRunner {
@Override
public void run(String... args) {
MonitoringDockerInfoProperties dockerInfoProperties =
this.monitoringProperties.getDockerInfo();
boolean dockerInfoEnable = dockerInfoProperties.getEnable();
if (dockerInfoEnable) {
// 第一步:获取 Docker 客户端
DockerClient dockerClient = getDockerClient(dockerInfoProperties);
// 第二步:初始化 Docker 中央控制器(单例)
DockerCentralController dockerCentralController =
DockerCentralController.getInstance().init(dockerClient);
// 第三步:启动事件监听回调
startAllDockerCallback(dockerCentralController);
// 第四步:定时采集 Docker 信息
long rate = this.monitoringProperties.getDockerInfo().getRate();
this.dockerMonitorScheduledThreadPoolExecutor
.scheduleAtFixedRate(new DockerThread(), 30, rate, TimeUnit.SECONDS);
}
}
}
CommandLineRunner 是 Spring Boot 提供的启动钩子——应用上下文完全初始化后才会执行。这比 @PostConstruct 更晚、更安全,确保所有 Bean(包括线程池、配置属性)都已经准备就绪。
四步流程的设计逻辑非常清晰:先建连接,再初始化控制器,然后启动事件监听,最后开启定时采集。 其中前置检查 dockerInfoEnable 确保了当 Docker 采集被关闭时(默认关闭),整个流程不会浪费任何资源。
6.2 DockerCentralController:枚举单例模式
public class DockerCentralController {
private DockerClient dockerClient;
private DockerCentralController() {}
private enum Singleton {
INSTANCE;
private final DockerCentralController instance;
Singleton() { instance = new DockerCentralController(); }
private DockerCentralController getInstance() { return instance; }
}
public static DockerCentralController getInstance() {
return Singleton.INSTANCE.getInstance();
}
public DockerCentralController init(DockerClient dockerClient) {
this.dockerClient = dockerClient;
return this;
}
}
DockerCentralController 采用枚举单例模式——这是 Java 中最安全的单例实现方式:天然线程安全、防止反射攻击、自动支持序列化。它作为 Docker 操作的「总控中心」,统一管理 Docker 客户端实例,提供三大能力:
- 信息采集:
getDockerInfo()一次性获取系统概况、容器列表、镜像列表和资源统计 - 事件监听:
startDockerEventCallback()注册异步事件回调 - 命令执行:
executeDockerCommand()处理来自 UI 端的容器/镜像操作指令
6.3 DockerEventCallback:实时事件流
public class DockerEventCallback implements ResultCallback<Event> {
@Override
public void onNext(Event event) {
// 解析事件类型、动作、参与者信息
EventDomain eventDomain = new EventDomain();
eventDomain.setEventType(type.getValue());
eventDomain.setEventAction(action);
// ...
// 立即发送给服务端
this.sendDockerEvent(eventDomain);
}
@Override
public void onComplete() {
log.warn("docker事件监听终止!");
// 自动重新启动事件监听
DockerCentralController.getInstance().startDockerEventCallback(this);
log.warn("重新启动docker事件监听!");
}
}
DockerEventCallback 实现了 docker-java 的 ResultCallback<Event> 接口,以异步回调的方式实时接收 Docker 事件(容器启停、镜像拉取、网络创建等)。两个设计亮点值得注意:
即时转发:事件不做缓存,onNext() 中每收到一个事件就立即封装为 DockerPackage 发送给服务端。这保证了 Docker 事件在 UI 端的「近实时」展示。
自动重连:onComplete() 被调用意味着事件流断开了——可能是 Docker 守护进程重启,也可能是网络中断。回调在此处自动重新注册自身,实现了「断开即重连」的弹性设计。
6.4 DockerThread:定时采集
public class DockerThread implements Runnable {
@Override
public void run() {
try {
DockerCentralController controller = DockerCentralController.getInstance();
Docker docker = controller.getDockerInfo();
MethodExecuteHandler.send(docker, Docker.class);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
}
DockerThread 是定时采集的执行体,由 DockerCollector 以 scheduleAtFixedRate 的方式调度。每次执行时:
- 通过
DockerCentralController获取完整的 Docker 信息(系统概况 + 容器 + 镜像 + 资源统计) - 通过
MethodExecuteHandler.send()将信息封装为DockerPackage并发送给服务端
注意这里使用的是 scheduleAtFixedRate(固定速率)而非 scheduleWithFixedDelay(固定延迟)——与心跳机制的选择不同。为什么?因为 Docker 信息的「时间点」很重要:容器的 CPU 和内存统计需要在固定间隔的时间点上采样,才能计算出准确的速率值。如果采用固定延迟,采样间隔会因每次采集耗时不同而产生偏移,影响速率计算的精度。
七、配置体系:双环境 + 灵活开关
7.1 monitoring.properties:采集行为的「遥控器」
代理端的采集行为完全由 monitoring-dev.properties(开发环境)和 monitoring-prod.properties(生产环境)控制:
# 实例端点类型:标识自己是 agent
monitoring.instance.endpoint=agent
# 实例名称
monitoring.instance.name=phoenix-agent
# 心跳频率(秒),不可低于30秒
monitoring.heartbeat.rate=30
# 服务器信息采集
monitoring.server-info.enable=true
monitoring.server-info.rate=30
# JVM 信息采集
monitoring.jvm-info.enable=true
monitoring.jvm-info.rate=60
# Docker 信息采集
monitoring.docker-info.enable=false
monitoring.docker-info.rate=60
monitoring.docker-info.host=unix:///var/run/docker.sock
# 网络设备信息采集
monitoring.network-device-info.enable=false
monitoring.network-device-info.rate=300
# 加密算法
monitoring.secure.encryption-algorithm-type=aes
几个值得关注的配置:
monitoring.instance.endpoint=agent——这个配置告诉服务端:「我是代理端,不是普通客户端。」服务端会据此在 UI 上以不同的标识展示代理端实例。
Docker 采集默认关闭——monitoring.docker-info.enable=false。这是因为不是每台服务器都安装了 Docker,强制启用会导致启动报错。只有在确认目标服务器运行了 Docker 服务后,才需要在配置中开启。
网络设备采集频率较高——300 秒(5 分钟)一次,远低于服务器信息的 30 秒。这是因为 SNMP 协议的交互成本较高,而且网络设备的状态变化通常比较缓慢,不需要高频轮询。
7.2 application.yml:Web 服务配置
server:
servlet:
context-path: /phoenix-agent
shutdown: graceful
spring:
lifecycle:
timeout-per-shutdown-phase: 30s
代理端的 context-path 为 /phoenix-agent,默认端口 12000。graceful 优雅停机配合 30 秒的缓冲期,确保代理端在关闭时能完成正在处理的请求、关闭 Docker 连接、发送下线通知——不会「硬杀」导致数据丢失。
7.3 RestTemplateConfig:高性能 HTTP 连接池
// 设置整个连接池的最大连接数
manager.setMaxTotal(300);
// 每个路由的最大连接数
manager.setDefaultMaxPerRoute(200);
// 在从连接池获取连接时,连接不活跃多长时间后需要进行一次验证
manager.setValidateAfterInactivity(30 * 1000);
代理端作为数据中转站,同时处理多个客户端的转发请求,对 HTTP 连接池的要求远高于普通应用。300 个最大连接数、200 个单路由连接数、连接重试 3 次——这套配置能支撑中等规模的并发转发场景。如果你的监控规模更大(比如上百个客户端同时通过一个代理端转发),可以进一步调大这些参数。
7.4 ThreadPoolConfig:IO 密集型线程池
@Bean(name = "dockerMonitorScheduledThreadPoolExecutor", destroyMethod = "shutdown")
public MonitoredScheduledThreadPoolExecutor dockerMonitorScheduledThreadPoolExecutor() {
return new MonitoredScheduledThreadPoolExecutor(
(int) (ProcessorsUtils.getAvailableProcessors() / (1 - 0.8)),
// ...
);
}
Docker 采集线程池的线程数公式为 Ncpu / (1 - 0.8),即 CPU 核心数的 5 倍。这是 IO 密集型任务的经典配置——Docker API 调用大部分时间在等待网络响应,CPU 实际占用率很低,需要更多线程来提高吞吐。同时,线程池本身是 MonitoredScheduledThreadPoolExecutor,自身的运行指标(活跃线程数、队列大小等)也会被纳入 Phoenix 的线程池监控体系。
八、数据流全景:一个 Docker 事件的旅程
把以上所有组件串起来,看一个 Docker 容器启动事件是如何从发生到最终展示在 UI 上的:
Docker 守护进程 代理端 服务端
│ │ │
│ 容器 start 事件 │ │
│ ───────────────────▶ │ │
│ DockerEventCallback.onNext() │
│ ├── 解析事件类型、动作、时间 │
│ ├── 构建 EventDomain │
│ └── sendDockerEvent() │
│ │ │
│ MethodExecuteHandler.send() │
│ ├── AgentPackageConstructor │
│ │ └── structureDockerPackage() │
│ ├── sendDockerPackage2Server() │
│ │ └── InvokerHolder → IDockerService │
│ └── HTTP POST (加密) ───────────▶ │
│ │ │
│ │ 解密 → 反序列化 │
│ │ DockerServiceImpl
│ │ └── 持久化到 │
│ │ MONITOR_DOCKER_EVENT
│ │ │
│ │ UI 端查询展示
整个链路清晰可追踪:Docker 事件 → 回调捕获 → 数据包构造 → 加密转发 → 服务端入库 → UI 展示。每一步都有明确的代码对应,每一层都有异常捕获和日志记录。
九、代理端 vs 服务端:API 对称性
仔细对比代理端和服务端的 Controller 接口,你会发现一个有趣的设计原则——API 路径完全对称:
| 功能 | 代理端端点 | 服务端端点 |
|---|---|---|
| 接收心跳包 | /phoenix-agent/heartbeat/accept-heartbeat-package |
/phoenix-server/heartbeat/accept-heartbeat-package |
| 接收告警包 | /phoenix-agent/alarm/accept-alarm-package |
/phoenix-server/alarm/accept-alarm-package |
| 测试数据库 | /phoenix-agent/db/test-monitor-db |
/phoenix-server/db/test-monitor-db |
| 测试 HTTP | /phoenix-agent/http/test-monitor-http |
/phoenix-server/http/test-monitor-http |
除了 context-path 不同(/phoenix-agent vs /phoenix-server),路径后缀完全一致。这不是巧合,而是刻意为之——客户端只需要修改一个配置项(服务地址),就能在「直连服务端」和「经代理端中转」之间无缝切换。对客户端来说,代理端和服务端是「可替换」的。
这种对称性的背后,是 IBaseRequestPackageService 的统一抽象:
public BaseResponsePackage dealBaseRequestPackage(
BaseRequestPackage baseRequestPackage, String url) {
return MethodExecuteHandler.sendBaseRequestPackage2Server(baseRequestPackage, url);
}
代理端的 Service 实现极其简洁——接到请求,直接委托 MethodExecuteHandler 转发,不做任何业务处理。它只是一个「透明管道」。
十、小结
这一篇我们从宏观到微观,全面剖析了 Phoenix 代理端的整体架构与角色定位。回顾几个核心要点:
-
双重身份:代理端既是「基础设施的信息采集者」(Docker、服务器、网络设备),又是「客户端的数据中转站」(透明转发所有类型的监控数据包)。
-
自我监控:代理端通过引入
phoenix-client-spring-boot-starter,让自身也成为一个被监控的应用实例——心跳、JVM、线程池信息一个不落。 -
三层分包:
business.client(面向客户端)、business.server(面向服务端)、business.agent(自身采集)——职责边界清晰,代码导航直觉化。 -
Docker 采集四步编排:建立连接 → 初始化单例控制器 → 启动事件回调 → 开启定时采集。
DockerEventCallback的自动重连和DockerCentralController的枚举单例是两个值得学习的设计。 -
API 对称性:代理端和服务端的接口路径完全对称,客户端可以在「直连」和「中转」之间无缝切换,实现了部署拓扑与业务代码的彻底解耦。
-
核心调度器 MethodExecuteHandler:作为数据转发的枢纽,通过
InvokerHolder实现服务方法的动态查找和调用,异常兜底确保转发失败不会向调用方传播。
代理端的本质,是在客户端与服务端之间搭建了一座桥——既拓展了监控能力的边界(从 JVM 内延伸到操作系统和容器),又突破了网络拓扑的限制(打通内外网)。从下一篇开始,我们将深入代理端最「重」的采集能力——服务器信息采集,看看 oshi 和 Sigar 这两套方案各自的实现细节与取舍。敬请期待。
项目地址:
https://gitcode.com/monitoring-platform/phoenix
https://gitee.com/monitoring-platform/phoenix
https://github.com/709343767/phoenix

评论