1. 崩溃上报整体架构与数据流

1.1 架构概览:四层职责分离

崩溃上报系统并非简单的“捕获→发送”两步走,而是一条包含客户端初始化、采集队列、上报服务、存储链路四个核心段的完整流水线。每段承担独立职责,并通过明确定义的接口或协议衔接,便于隔离故障、横向扩展和独立演进。

设计原则: 每层只处理本层数据;层间通过队列或 RPC 通信,禁止跨层直调。

1.2 四层核心组件与数据流

层次职责关键组件输出数据形态
① 客户端初始化拉起 SDK、加载配置、挂载信号/异常处理器SignalHandler、CrashConfig处理器上下文 + 配置对象
② 采集队列接收原生崩溃数据,序列化、压缩、保存到本地队列CrashCollector、RingBuffer、LocalStorage序列化后的二进制或 JSON 包
③ 上报服务从队列拉取数据,执行重试/退避策略,发送至远端UploadService、RetryPolicy、HttpClientHTTP(S) 请求体 + 元数据
④ 存储链路服务端接收、校验、解析、持久化,并触发告警CrashServer、Parser、DB (如 ClickHouse)结构化事件记录
崩溃上报四层架构与数据流 客户端初始化 CPU 信号钩子 采集队列 RingBuffer 上报服务 退避 / 重试 存储链路 ClickHouse 关键说明 层间通过本地 Socket 或内存队列交换数据;上报服务与存储链路之间使用 HTTP/HTTPS 协议。

1.3 客户端初始化流程

  1. SDK 加载: 应用启动时通过 dlopen 或静态链接加载崩溃采集动态库。
  2. 配置读取: 读取 crash_config.json(路径由环境变量 CRASH_CFG_PATH 指定),包含队列大小、超时、最大重试次数。
  3. 信号处理挂载: 调用 sigaction 接管 SIGSEGV、SIGABRT、SIGBUS 等信号。
  4. 线程空间初始化: 分配独立栈(sigaltstack)用于崩溃时安全调用。
  5. 回执测试: 通过模拟崩溃回执确认链路可用(可配置、生产环境默认关闭)。
初始化 SDK 示例(伪代码):
crash_handle_t handle = crash_init("crash_config.json");
crash_set_callback(handle, on_crash_callback);
crash_install_signal_handlers(handle, SIGSEGV | SIGABRT);

1.4 采集队列工作机制

崩溃发生后,信号处理函数会在独占栈上运行。由于此时堆内存可能已损坏,队列采用预分配环形缓冲区(RingBuffer)存储原始数据,避免二次崩坏。

  • 写入阶段: 崩溃上下文(寄存器、堆栈指针、信号信息)被压入固定大小的内存块。
  • 转储阶段: 应用重启后,启动器扫描本地临时目录,将 RingBuffer 内容序列化为 JSON 协议包,写入磁盘队列。
  • 队列结构:
字段类型说明
crash_iduint64崩溃唯一标识(时间戳 + 随机数)
timestampuint64崩溃发生时刻(毫秒)
signal_numint32信号编号
raw_payloadbytes经过 LZ4 压缩的堆栈二进制帧
upload_statusenumpending | uploading | uploaded | failed

1.5 上报服务与重试角色

上报服务(UploadService)是状态机最密集的一层。每次从队列取出一个 crash 记录后,进入重试轮询:

  1. 构造 HTTP POST 请求,Body 为 JSON 协议包。
  2. 发送至服务端点:POST /v1/crash/report。
  3. 接收响应。若 HTTP 状态码为 2xx,标记为 uploaded 并从磁盘队列删除。
  4. 若状态码为 4xx 或 5xx,根据退避策略等待后重新入队。
  5. 达到最大重试次数(默认 5)后,标记为 failed 并归档。
重要约束: 上报服务不得修改已经序列化的 raw_payload,只负责传输和状态转换。重试间隔使用指数退避(Exponential Backoff)+ 随机抖动。退避配置参见 retry_policy.yaml。

1.6 存储链路(服务端)

存储链路是上报服务的消费者,包含以下处理环节:

  • 接入层: 通过 Nginx/OpenResty 进行验签、限流(每秒每客户端 10 次)。
  • 解析层: 解析 JSON 协议包,执行堆栈符号化(调用 addr2line 或读取 .debug 文件)。
  • 持久化: 写入 ClickHouse 表 crash_reports,按 timestamp 分区。
  • 告警: 若同 crash_id 出现频次超过阈值(如 100 次/分钟),触发 P0 级钉钉/邮件告警。
存储表结构(核心列):
CREATE TABLE crash_reports (
    crash_id     UInt64,
    app_ver      String,
    signal_num   Int32,
    raw_payload  String,
    city         String,
    reported_at  DateTime
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(reported_at)
ORDER BY (reported_at, crash_id);
此表设计支持每秒数万条写入,实际生产可配合 Kafka 做削峰填谷。

1.7 性能与稳定性关注点

  • 信号安全: 仅使用 Async-Signal-Safe 函数(write、sigaction、snprintf),禁用 malloc、printf、锁。
  • 队列写放大: 崩溃上下文体积通常 < 4KB,但符号化信息可达 50KB。采集队列阶段只保留压缩后的托管栈,避免 IO 溢出。
  • 上报并发: 每个客户端启动一个独立线程池(默认 2 个线程)执行上传,防止阻塞主业务线程。