# ztrace
**Repository Path**: 44346460/ztrace
## Basic Information
- **Project Name**: ztrace
- **Description**: 基于 Spring AOP 的非侵入式事件采集与清洗框架,支持同步/异步采样、多存储介质转储、以及链式收集器处理.
- **Primary Language**: Unknown
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 1
- **Forks**: 3
- **Created**: 2021-08-05
- **Last Updated**: 2026-07-22
## Categories & Tags
**Categories**: Uncategorized
**Tags**: Java, 埋点, 采样
## README
# ZTrace — 动态事件追踪框架
[](https://spring.io/projects/spring-boot)
[](https://openjdk.java.net/)
[](https://opensource.org/licenses/Apache-2.0)
> 基于 Spring AOP 的非侵入式事件采集与清洗框架,支持同步/异步采样、多存储介质转储、以及链式收集器处理。
## 📋 目录
- [项目背景](#项目背景)
- [架构概览](#架构概览)
- [核心概念](#核心概念)
- [快速开始](#快速开始)
- [1. 引入依赖](#1-引入依赖)
- [2. 定义采样器(Sampler)](#2-定义采样器sampler)
- [3. 定义收集器(Collector)](#3-定义收集器collector)
- [4. 在业务方法上埋点](#4-在业务方法上埋点)
- [5. 启动配置](#5-启动配置)
- [配置项参考](#配置项参考)
- [采样端(Client)](#采样端client)
- [收集端(Server)](#收集端server)
- [设计特性](#设计特性)
- [存储介质说明](#存储介质说明)
- [注意事项](#注意事项)
---
## 项目背景
在大多数业务系统中,**功能开发总是先行于数据埋点**。埋点需求往往在产品运营稳定后才逐步浮现——用于辅助运营决策、产品迭代分析或用户行为追踪。然而,当这些"后知后觉"的埋点需求落到已有业务代码上时,开发者通常面临两难困境:
- **侵入式改动风险高**:为了插入埋点逻辑,不得不修改原本稳定的业务代码,极易引入新的 Bug,破坏既有功能。
- **埋点方式因人而异**:不同开发者对埋点的实现方式各不相同,有的直接硬编码在 Service 层,有的另起异步线程,有的通过拦截器……导致**功能性代码与非功能性代码严重耦合、混乱交织**,后期维护成本陡增。
- **重复造轮子**:每个团队、每个项目都在重复解决"如何在不改业务代码的前提下采集数据"这一相同问题。
ZTrace 正是为了解决上述痛点而设计。它基于 **Spring AOP** 提供了一套**规范化、统一化、非侵入式**的埋点方案:
- 业务代码**零改动**,仅通过注解声明即可实现方法级拦截;
- 采样逻辑与收集逻辑**完全解耦**,由框架统一调度,杜绝"各自为战"的埋点乱象;
- 支持多种存储介质与灵活的部署方式,一套框架即可覆盖从开发测试到生产运营的全生命周期。
---
## 架构概览
```
┌─────────┐ 写入样本流 ┌──────────────┐ 读取样本流 ┌───────────┐ 过滤/筛选/加工/转储 ┌──────────────┐
│ Sampler │ ──────────────→ │ IEventStorage │ ──────────────→ │ Collector │ ─────────────────────→ │ IEventStorage │
│ (采样端) │ │ (样本池) │ │ (收集端) │ │ (清洗后存储) │
└─────────┘ └──────────────┘ └───────────┘ └──────────────┘
│ │
▼ ▼
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│ MQ │ DB │ File │ HTTP │ Redis │ Other (如第三方数据分析平台:神策、GrowingIO 等) │
└─────────────────────────────────────────────────────────────────────────────────────────┘
```
**数据流向说明:**
1. **Sampler(采样端)**:通过 AOP 拦截业务方法,采集方法入参或返回值,组装为 `TraceEvent`,写入 **IEventStorage(样本池)**
2. **Collector(收集端)**:从样本池消费 `TraceEvent`,按 `group` 路由到对应的收集器,完成过滤、加工后,再次写入 **IEventStorage(清洗后存储)**
3. **最终存储**:清洗后的数据可落库、发往下游 MQ、调用 HTTP 接口、或写入第三方服务
> **部署灵活性**:Sampler 与 Collector 可在同一 JVM 实例中集成,也可拆分为独立服务部署。
---
## 核心概念
| 组件 | 职责 | 关键点 |
|------|------|--------|
| **TraceSampler** | 定义"采什么"——从方法入参/返回值中提取业务字段 | 必须线程安全,框架会缓存实例 |
| **TraceCollector** | 定义"怎么洗"——对样本做过滤、加工、转储 | 支持链式处理,按 `group` 匹配 |
| **IEventStorage** | 定义"存哪里"——统一的数据转储接口 | 一套接口,两端复用(采样端 + 收集端) |
| **TraceEvent** | 数据载体 | `group`(分组)+ `type`(类型)+ `properties`(属性) |
---
## 快速开始
### 1. 引入依赖
```xml
com.hyw
trace
0.0.1
```
> 默认依赖 **Spring Cloud Stream Kafka** 作为消息中间件,请确保已配置 Kafka 连接信息。配置方式请参考 [Spring Cloud Stream 官方文档](https://spring.io/projects/spring-cloud-stream)。
---
### 2. 定义采样器(Sampler)
实现 `TraceSampler`,从方法入参中提取需要追踪的字段:
```java
@Component
public class OrderSampler extends TraceSampler {
@Override
public Map process(OrderDTO order) {
Map props = new HashMap<>();
props.put("orderId", order.getOrderId());
props.put("userId", order.getUserId());
props.put("amount", order.getAmount());
props.put("status", order.getStatus());
return props; // 返回 null 或空 Map 时,该事件会被丢弃
}
}
```
**重要约束:**
- `TraceSampler` 必须是**线程安全**的,框架会对实例进行缓存
- 若 `process()` 返回 `null` 或空 Map,该事件会被自动丢弃
- 泛型 `T` 的匹配规则:
- `T = Object`:取参数列表第一个对象
- `T = Object[]`:将整个参数列表打包为数组
- `T = 具体子类`:遍历参数列表,取第一个匹配类型的参数
- 若参数列表为空或找不到期望类型,抛出 `IllegalArgumentException`
- **若采样来源是方法返回值,则 `T` 只能为 `Object`**
---
### 3. 定义收集器(Collector)
实现 `TraceCollector`,对样本进行清洗并指定转储目标:
```java
@Component
public class OrderCollector extends TraceCollector {
public OrderCollector(IEventStorage storage) {
super(storage); // 例如:注入一个写入数据库的 IEventStorage 实现
}
@Override
public void collect(TraceEvent event) {
// 清洗逻辑:过滤敏感字段、格式化、补充上下文等
String cleaned = doClean(event);
storage.storage(cleaned); // 转储至目标存储
}
@Override
public boolean isSupport(TraceEvent event) {
// 按 group 路由,只处理订单相关事件
return "order".equals(event.getGroup());
}
}
```
**设计说明:**
- 支持**链式处理**:同一个 `TraceEvent` 可被多个 `TraceCollector` 依次处理
- `isSupport()` 用于按 `group` 做路由匹配,实现分组收集
---
### 4. 在业务方法上埋点
使用 `@TraceSource` 声明拦截点:
```java
@Service
public class OrderService {
@TraceSource(
group = "order",
type = "create",
description = "订单创建",
sampler = OrderSampler.class // 指定采样器
)
public OrderDTO createOrder(CreateOrderRequest request) {
// 业务逻辑...
return orderDTO;
}
}
```
**多采样点支持:**
```java
@MultiTraceSources({
@TraceSource(group = "order", type = "create", sampler = OrderSampler.class),
@TraceSource(group = "audit", type = "order_create", sampler = AuditSampler.class)
})
public OrderDTO createOrder(CreateOrderRequest request) {
// 业务逻辑...
}
```
> **AOP 使用前提**:目标方法必须满足 Spring AOP 的拦截条件(基于接口代理或 CGLIB 类增强)。建议业务层保持良好的接口设计规范。
---
### 5. 启动配置
**采样端(Client)** — 在启动类添加:
```java
@EnableZTraceClient
@SpringBootApplication
public class SamplerApplication {
public static void main(String[] args) {
SpringApplication.run(SamplerApplication.class, args);
}
}
```
**收集端(Server)** — 在启动类添加:
```java
@EnableZTraceServer
@SpringBootApplication
public class CollectorApplication {
public static void main(String[] args) {
SpringApplication.run(CollectorApplication.class, args);
}
}
```
> 同一实例可同时启用 Client + Server:同时标注 `@EnableZTraceClient` 和 `@EnableZTraceServer`。
---
## 配置项参考
### 采样端(Client)
- | 属性 | 类型 | 说明 | 默认值 |
|:---|:---|:---|:---|
| `ztrace.sampler.default.core.thread` | Integer | 异步采样线程池核心线程数 | `20` |
| `ztrace.sampler.default.max.thread` | Integer | 异步采样线程池最大线程数 | `40` |
| `ztrace.sampler.default.max.queue` | Integer | 异步采样线程池队列容量 | `1000` |
| `ztrace.sampler.default.thread.keepalive` | Long | 非核心线程空闲存活时间(ms) | `1000` |
| ztrace.sampler.default.reject | String | 异步采样线程池的拒绝策略类型。主要有:
**Abort** :rejected tasks that throws a RejectedExecutionException
**Discard** :rejected tasks that silently discards the rejected task
**CallerRuns** :runs the rejected task directly in the calling thread
**CallerBlock** :blocks the caller until the executor has room in its queue, or a timeout occurs with default timeout 5s
| CallerRuns |
| `ztrace.sampler.group.skip-pattern` | String | 按 `group` 过滤的正则表达式,`null` 表示不过滤 | `null` |
| `ztrace.sampler.type.skip-pattern` | String | 按 `type` 过滤的正则表达式,`null` 表示不过滤 | `null` |
| `ztrace.client.meta.send.type` | String | 采样数据转储方式:`MQ` / `HTTP` / `REDIS` | `MQ` |
| `ztrace.client.meta.send.uri` | String | `send.type=HTTP` 时,收集端接收地址 | `http://localhost:8080/ztrace/collect` |
| `ztrace.client.meta.send.debug` | Boolean | `send.type=HTTP` 时,是否打印请求日志 | `true` |
| `ztrace.client.meta.redis.topic` | String | `send.type=REDIS` 时,Redis 频道名 | `ZTrace` |
### 收集端(Server)
| 属性 | 类型 | 说明 | 默认值 |
|:---|:---|:---|:---|
| `ztrace.server.meta.receive.type` | String | 样本消费方式:`MQ` / `HTTP` / `REDIS` | `MQ` |
| `ztrace.server.meta.redis.topic` | String | `receive.type=REDIS` 时,Redis 频道名 | `ZTrace` |
> **HTTP 模式**:当 `receive.type=HTTP` 时,框架会自动注册端点 `/ztrace/collect`,无需手动编写 Controller。
---
## 设计特性
### 1. 非侵入式埋点
基于 Spring AOP 实现,通过注解声明在方法签名上,**不修改业务代码逻辑**。支持接口动态代理和 CGLIB 类增强。
### 2. 异常隔离
采样过程默认全局异常捕获,**任何框架异常都不会外抛**,确保业务功能不受影响。异常信息通过日志输出,开发者需关注日志排查问题。
### 3. 采样效率控制
- **采样率**:支持间隙性抽样,默认全量采集
- **同步/异步采集**:异步模式基于线程池,支持核心线程数、队列容量等参数调优
- **过滤正则**:支持按 `group` 和 `type` 做正则过滤,减少无效采集
### 4. 灵活部署
- Sampler 与 Collector 可**同实例集成**,也可**独立服务部署**
- 支持多种存储介质,适应不同架构场景
---
## 存储介质说明
| 模式 | 适用场景 | 说明 |
|:---|:---|:---|
| **MQ**(默认) | 高并发(高流量)生产环境 | 基于 Spring Cloud Stream,默认 Kafka。Sampler 写入 Topic,Collector 消费 Topic |
| **HTTP** | 低并发(低流量)生产环境 | Sampler 直接 HTTP POST 到 Collector 端点。适合无 MQ 或者Redis等基础中间件设施的场景, 但对于Collector而言要考虑请求负载冲击自身业务的可能性。 |
| **Redis** | 较高并发(较高流量)生产环境 | 基于 Redis List 实现(`LPUSH`/`BRPOP`),适用于已有 Redis 且消息消费不严格的场景 |
| **Console / 文件** | ⚠️ **仅限本地测试** | 打印到控制台或写入本地日志文件,**严禁用于生产环境** |
---
## 注意事项
1. **线程安全**:`TraceSampler` 实例会被框架缓存复用,实现时必须保证线程安全
2. **参数匹配**:`TraceSampler` 的泛型 `T` 决定参数注入规则,若采样来源为方法返回值,则 `T` 只能为 `Object`
3. **事件丢弃**:`TraceSampler.process()` 返回 `null` 或空 Map 时,该事件会被静默丢弃
4. **AOP 前提**:`@TraceSource` 依赖 Spring AOP,目标 Bean 必须能被代理(建议基于接口编程)