3、数据采集层:日志采集规范、消息队列选型与数据埋点最佳实践
数据采集层是整个实时特征计算的起点。说白了,你后面所有的模型、规则、实时决策,都依赖这一层把原始数据干净、及时地送进来。我见过太多项目,算法模型再牛,结果数据采集阶段就漏了、乱了、延迟了,最后整个系统都是白搭。
这一章,我们聚焦三个核心问题:日志怎么采、消息队列怎么选、埋点怎么设计。嗯,都是实战中踩过坑才总结出来的东西。
3.1 日志采集规范:别让数据在源头就脏了
日志采集看似简单,但我在项目中遇到过最头疼的问题,往往就出在日志格式不统一。不同团队、不同服务,日志字段名不一样、分隔符不一样、时间格式不一样,到了消费端解析起来简直是一场噩梦。
核心原则:日志采集必须做到「三统一」——统一格式、统一字段、统一时间戳。
3.1.1 推荐日志格式:JSON 行协议
我个人习惯使用 JSON 行协议(JSON Lines)。每行一个完整的 JSON 对象,换行分隔。为什么?因为 JSON 自描述、易扩展、解析效率高,而且几乎所有语言都有原生支持。
// 标准日志格式示例
{"timestamp": "2025-03-15T10:30:00.123Z", "level": "INFO", "service": "user-service", "trace_id": "abc123", "user_id": "u_98765", "event": "login", "duration_ms": 45, "extra": {"ip": "192.168.1.1", "device": "mobile"}}
{"timestamp": "2025-03-15T10:30:01.456Z", "level": "WARN", "service": "order-service", "trace_id": "def456", "order_id": "o_54321", "event": "payment_timeout", "duration_ms": 3002, "extra": {"amount": 199.00}}
小技巧:时间戳统一使用 ISO 8601 格式,带毫秒和时区。千万别用 Unix 时间戳,不同服务精度不一致(秒级 vs 毫秒级),后面做窗口聚合时你会哭的。
3.1.2 必选字段清单
不管业务怎么变,以下字段必须出现在每条日志中:
| 字段名 | 类型 | 说明 |
|---|---|---|
| timestamp | string | 事件发生时间,ISO 8601 格式 |
| level | string | 日志级别:INFO / WARN / ERROR |
| service | string | 产生日志的服务名 |
| trace_id | string | 全链路追踪 ID,用于关联上下游 |
| event | string | 事件名称,如 login、order_create |
| duration_ms | int | 事件耗时(毫秒),用于性能监控 |
| extra | object | 扩展字段,按业务需求灵活添加 |
避坑指南:我曾经在一个项目中,日志里混入了换行符,导致 JSON 行协议解析错乱。后来强制要求所有字符串字段做转义,并在采集端做校验。记住:日志采集层一定要有「格式校验」和「脏数据隔离」机制,坏数据不能阻塞好数据的处理。
3.2 消息队列选型:Kafka vs Pulsar
日志采集上来之后,需要一个高吞吐、低延迟的消息队列来缓冲和分发。目前主流的选择就是 Kafka 和 Pulsar。你想想看,这两个到底怎么选?
我两个都用过,说说我的真实感受。
3.2.1 Kafka:成熟稳定,生态丰富
Kafka 是目前最成熟的消息队列,社区活跃,周边生态(Kafka Connect、KSQL、Schema Registry)非常完善。如果你的团队对 Kafka 比较熟悉,而且业务场景以「高吞吐日志采集」和「流式处理」为主,Kafka 基本不会出错。
- 优势:吞吐量极高(百万级/秒)、持久化可靠、分区机制成熟
- 劣势:分区数量有限制(建议不超过 1000 个)、Rebalance 时会有短暂不可用、存储与计算耦合
- 适用场景:日志采集、监控指标、事件溯源、流式 ETL
3.2.2 Pulsar:云原生,分层架构
Pulsar 是后起之秀,它的核心优势是「存储与计算分离」。说白了,Broker 只管计算,数据存在 BookKeeper 里。这意味着你可以独立扩缩容,而且分区数可以轻松到几万个。
- 优势:分区数无上限、Rebalance 无感、支持多租户、延迟更低
- 劣势:社区相对较小、运维复杂度略高、部分工具链不如 Kafka 成熟
- 适用场景:多租户场景、需要大量分区的实时特征计算、跨地域复制
3.2.3 选型对比表
| 对比维度 | Kafka | Pulsar |
|---|---|---|
| 架构 | 存储与计算耦合 | 存储与计算分离 |
| 最大分区数 | 建议 ≤ 1000 | 可到数万 |
| Rebalance 影响 | 有短暂不可用 | 无感 |
| 消息保留 | 基于日志段过期 | 基于 BookKeeper 分段 |
| 多租户 | 需额外设计 | 原生支持 |
| 延迟(P99) | 5-10ms | 2-5ms |
| 社区成熟度 | 极高 | 中等 |
我的建议:如果团队规模不大、运维能力有限,选 Kafka 更稳妥。如果你们在做云原生架构、需要支持大量 Topic 和分区,或者有多租户需求,Pulsar 值得一试。我个人在实时特征计算项目中,更倾向于 Pulsar,因为它的分层架构让存储和计算可以独立扩展,非常适合特征数据量大、消费端多的场景。
3.3 数据埋点最佳实践
埋点,就是决定「采什么数据」和「怎么采」。很多团队在埋点上走了弯路——要么埋得太少,特征不够用;要么埋得太多,数据量爆炸,成本飙升。
3.3.1 埋点设计原则
我总结了一个「三层埋点法」:
- 基础层:所有服务必须埋的通用事件,如请求入口、响应出口、异常、超时。这些用于监控和链路追踪。
- 业务层:核心业务事件,如登录、下单、支付、退款。这些用于实时特征计算和业务分析。
- 行为层:用户行为事件,如点击、滑动、停留时长。这些用于用户画像和推荐系统。
关键原则:埋点数据必须「可回溯、可校验、可治理」。每条埋点都要有明确的 owner 和用途,避免「埋了不用」的垃圾数据。
3.3.2 埋点 SDK 设计要点
埋点 SDK 是数据采集的「最后一公里」。我见过最糟糕的做法是每个业务方自己写日志,格式五花八门。正确的做法是统一 SDK,强制规范。
// 埋点 SDK 调用示例(伪代码)
// 统一埋点接口
tracer.emit("order_create", {
user_id: "u_98765",
order_id: "o_54321",
amount: 199.00,
payment_method: "wechat",
// SDK 自动补充:timestamp, trace_id, service, host, env
});
// SDK 内部自动完成:
// 1. 补充公共字段
// 2. 序列化为 JSON
// 3. 异步发送到本地 Agent 或直接写入 Kafka
// 4. 失败重试 + 本地缓存兜底
避坑指南:我曾经遇到一个线上事故——埋点 SDK 同步发送阻塞了业务线程,导致接口响应时间从 10ms 飙升到 500ms。后来强制要求 SDK 必须异步发送,且使用独立线程池。另外,SDK 一定要有「熔断机制」,当消息队列不可用时,不能拖垮业务服务。
3.3.3 埋点数据治理
埋点数据量一大,治理就成了大问题。我推荐建立「埋点元数据中心」,统一管理所有埋点的定义、字段、owner、变更历史。
| 元数据字段 | 说明 |
|---|---|
| event_name | 事件名称,全局唯一 |
| owner | 负责团队/个人 |
| fields | 字段列表及类型 |
| sample_rate | 采样率,用于控制数据量 |
| status | 启用/废弃/灰度 |
| created_at | 创建时间 |
小技巧:对于非核心的埋点,可以设置采样率。比如用户行为日志,采样 10% 就足够做特征分析了。但注意:涉及金额、支付等核心事件,必须全量采集。
3.4 本章知识体系总览
下面这张图,把数据采集层的核心逻辑串起来了。从日志产生到消息队列,再到下游消费,每一步都有规范和选型要点。
数据采集层是实时特征计算的「地基」。地基不稳,上层建筑再漂亮也是空中楼阁。日志规范、消息队列选型、埋点设计,这三件事做好了,后面的实时计算才能顺畅跑起来。