SDK 接入改造整体架构设计

前面我们聊了日志采集的基础概念和痛点分析,现在该动真格的了。
SDK 接入改造,说白了就是给老系统装一套新的"数据神经"。
我个人习惯先把架构图画清楚再动手,不然改着改着就乱了。

这一章我们来搭整体的架子。
你想想看,一个 SDK 要从零接入到现有的业务系统,里面涉及的东西不少:怎么切流量、怎么保证不丢数据、怎么跟老代码和平共处……
嗯,我把这个架构拆成了三层,每层干各自的事。

1. 三层架构模型

我见过很多团队一上来就写代码,结果数据通路没理清,日志要么重复要么丢失。
后来我总结了一个通用的分层模型,用了好几个项目,基本能覆盖大多数场景。

层级名称核心职责典型组件
L1接入层拦截日志、封装上下文、路由转发Filter、Interceptor、SDK Client
L2缓冲层异步存储、聚合去重、限流降级RingBuffer、Batch Processor、本地队列
L3输出层序列化、协议转换、远端上报Serializer、Sender、Retry Queue

三层之间通过明确的数据契约连接。
每一层只依赖下一层的接口,不跨层调用。
我在上一个电商项目中就用这个模型,从改到上线只花了三周,线上事故为零。

2. 核心模块划分

每个层级里还能再切几个小模块。我常用的划分方式是这样的:

  • 拦截器模块:在业务入口处统一捕获日志上下文。比如 HTTP 请求进来时,从 Request 里拿 traceId、userId。
  • 上下文管理模块:用 ThreadLocal 或 AsyncLocal 存储链路信息。记得要清空,不然内存泄漏够你查半天。
  • 缓冲调度模块:定时批量把日志从本地队列捞出,交给下一层。我常用 100ms 或 1000条,哪个先到就触发。
  • 指标收集模块:统计日志写入量、丢包率、处理延迟。这玩意平时不起眼,出问题时能救命。
  • 故障恢复模块:当远端服务不可用时,本地文件或数据库兜底,等恢复后再偿上传。

避坑指南:
我曾经只画了模块图就直接编码,结果上下文管理模块和缓冲调度模块互相持有引用,形成了循环依赖。改起来简直是噩梦。后来我强制要求每个模块只能单向依赖,再也没出过这种问题。

3. 整体架构流程(SVG)

下面这张图概括了核心数据流。注意我是从"业务线程"出发的,因为日志采集不能阻塞业务主逻辑。

SDK接入改造整体架构图 SDK 接入改造整体架构图 业务应用 L1 接入层 SDK Client / Filter L2 缓冲层 RingBuffer / Batch L3 输出层 Serializer / Sender 远端接收器 本地文件 故障兜底 图例 主数据流 降级/恢复路径

你看,数据从业务线程出来后就进了接入层,经过缓冲层削峰填谷,再通过输出层发到远端。如果远端挂了,就走虚线路径落到本地文件,等恢复后再补发。

4. 数据流设计要点

架构图只是骨架,真正的血肉是数据流细节。我挑几个关键点说一下:

  • 线程隔离:业务线程不能直接写远端。我见过有人图省事在 Filter 里直接发 HTTP,结果业务接口 P99 从 30ms 飙到 2s。
  • 批量聚合:缓冲层拿到日志后,按 traceId 或时间段聚合,减少网络包数量。我们内部用了一个固定大小的 RingBuffer,写满或超时都触发 flush。
  • 序列化格式:我建议文本格式用 JSON,二进制用 Protobuf。JSON 调试方便,Protobuf 性能好,用哪个取决于你们后端生态。
  • 重试与幂等:远端可能超时或返回 500。输出层必须支持最多 3 次重试,每次间隔指数退避。另外每条日志要带唯一 ID,远端做去重。

一个小建议:
缓冲层不要用 Java 的 BlockingQueue 直接存日志对象——GC 压力太大。我更喜欢用 byte[] 数组或者直接内存 DirectBuffer,把对象序列化早一点做,减少新生代对象存活时间。

5. 关键设计原则

架构设计不是拼乐高,有些规矩必须立在前面:

  1. 最少依赖性:SDK 本身不要依赖公司内部的基础设施 jar 包。你想想看,万一内部 jar 版本冲突,整个业务都起不来。
  2. 配置驱动:开关、阈值、采样率全走配置中心。我习惯给每个参数一个默认值,让 SDK 开箱即用,上线后再调整。
  3. 可观测性内建:SDK 自己也要出日志。每上报 1000 条打一行成功计数,失败时打完整异常栈。这样排查问题不用猜。
  4. 优雅降级:本地缓存满了或写入太慢,直接丢弃最旧的日志,保证业务性能不受影响。数据准确性永远不会比系统可用性高。

重点提醒:
我在第一个版本里没做配置动态刷新,每次改采样率都要发版本,被运维骂惨了。后来加了一个 ConfigWatcher 线程定期拉取配置中心的值,变更后 10s 内生效,才算解决了这个痛点。