跳转到内容

边缘可观测与业务流量统计重构设计

你会学到:本次重构要解决的问题(「看板 OpenResty 出站」与「Zone 已提供数据」不一致、字段与聚合冗余),以及目标架构如何让 Agent 只上报事实、Server 只解释事实,业务流量以访问日志为唯一真相源。


1. 目标

1.1 要解决的问题

  1. 双真相源:业务吞吐同时来自访问日志聚合与 OpenResty 观测差分,数值长期对不上。
  2. Agent 越权计算:边缘预聚合 TrafficReport、吞吐累计,控制面再聚合一遍,语义难演进、难对账。
  3. 字段语义重叠:「OpenResty 出站」与「已提供数据」对用户是同一业务问题,系统却用两套字段、两条管道。
  4. 瞬时与累计混用:60 秒窗口计数被当成进程累计做 24h 差分,造成严重偏低。
  5. UI 诱导错误对比:看板与 Zone 页使用相近「流量/数据」文案,却未声明范围与口径差异。

1.2 重构目标

目标说明
单一业务真相请求数、已提供数据、UV、状态码分布、Top 域名等 从访问日志(及其 Server 侧派生汇总)得出
Agent 只上报事实明细日志 + 机器读数 + 健康瞬时态;禁止 业务 UV/TopN/24h 总量等预聚合
字段收敛一个业务概念对应一个权威字段;机器网卡与业务交付严格分名
可对账全局「已提供数据」≈ 各 Zone「已提供数据」之和(差仅为未绑定/未知 Host)
可演进改时间窗、TopN、归属规则只改 Server,不升 Agent

1.3 非目标(本设计不覆盖)

  • 建成通用日志平台、全量日志长期归档或检索产品。
  • 替换 ClickHouse / 取消分析库依赖。
  • 改造 Relay / OpenFlared 的主机指标采集(可对齐原则,但不在本轮协议主路径)。
  • 实时流式告警引擎、APM 链路追踪(OpenTelemetry 服务端已有,与本业务流量模型正交)。

2. 范围与约束

2.1 产品约束(继承)

  • 单租户、全局单激活配置;观测不引入多租户计费隔离。
  • 访问日志与时序观测走可切换日志主库(默认 ClickHouse,可切换 PostgreSQL/SQLite),见 日志存储解耦
  • Agent 无入向控制、Pull 模型;离线期间本地 OpenResty 继续服务,观测可本地缓冲后补传。

2.2 工程约束

  • Agent 保持轻量:解析日志行、读 /proc、健康检查;不做业务分析。
  • 控制面 API 错误仍走统一信封与 response.Abort*
  • 访问日志字段变更须同时更新 OpenResty log_format 与 Agent 解析器;Agent 与控制面同版本发布,不保留旧协议解析。

3. 设计原则

原则 P1:Agent 上报事实,Server 解释事实

text
Agent  = 采集 + 可靠投递(原始/近原始)
Server = 入库 + 聚合 + 归属 + 趋势 + 对账

允许的边缘处理(采集)

  • 将 JSON access.log 行解析为结构化字段
  • path 长度上限、丢弃非法行、跳过观测端口自身请求
  • 读取网卡/CPU/内存等计数器 原值
  • 批量、压缩、离线缓冲与重试

禁止的边缘处理(业务计算)

  • UV / Top 域名 / 状态码直方图 / 窗口 request_count 作为权威指标
  • 为看板单独维护「业务入出站累计」
  • Zone / 域名归属统计、国家分布(国家可在 Server 入库时解析)

原则 P2:业务流量唯一真相 = 访问日志

业务问题唯一答案
提供了多少数据sum(bytes_sent)
多少请求count()
多少独立访客uniqExact(remote_addr)(或产品约定哈希)
状态码 / Top 域名对日志 group by

原则 P3:三层指标互不混用

名称用途典型字段
L1 业务交付Business Traffic用户与 Zone 对账、看板业务趋势access log
L2 边缘健康Edge HealthOpenResty 是否活着、当前连接status、connections
L3 宿主机资源Host Capacity容量规划、机器是否打满CPU、内存、磁盘、网卡

禁止将 L3 网卡或 L2 瞬时计数命名为「已提供数据」;禁止将 L1 与 L3 画在同一摘要卡片上却不标注语义。

原则 P4:一个业务概念一个字段

  • 已提供数据 ≡ 响应体交付量 ≡ 历史文案中的「OpenResty 出站(业务含义)」→ 只保留 bytes_sent 聚合
  • 接收数据(可选)≡ 请求侧体量 → 日志 request_length 聚合
  • 宿主机出站network_tx 差分,文案必须含「宿主机/网卡」

4. 重构前的问题(基线)

4.1 重构前数据流(冗余)

text
一次 HTTP 请求

  ├─ access.log 一行
  │     → Agent tail → AccessLogs[]
  │     → CH of_node_access_logs
  │     → Zone「已提供数据」✅

  ├─ Lua shared dict 窗口/累计计数
  │     → /openflare/observability
  │     → TrafficReport + OpenrestyObservation(rx/tx)
  │     → CH request_reports / obs_openresty
  │     → 看板「OpenResty 入/出站」❌ 易与 Zone 不一致

  ├─ access.log 二次汇总(观测 endpoint 失败时回退)
  │     → 又一份 TrafficReport / 吞吐

  └─ 宿主机 network_rx/tx
        → Snapshot → 网络趋势中的「主机」曲线

4.2 字段重叠

用户感知系统字段 A系统字段 B问题
出站 / 已提供openresty_tx_bytesbytes_sent业务语义重复
入站openresty_rx_bytesrequest_length(日志)业务语义重复
请求数TrafficReport.request_countcount(access_logs)聚合重复且窗口易重计
出站(机器)network_tx_bytes(无业务对应)应单独命名,勿与业务对账

4.3 典型故障模式

  1. 窗口计数被当累计差分 → 24h 业务吞吐严重偏低。
  2. 小时 rollup max−min 对重置型计数失效。
  3. Zone 用日志、看板用观测 → 用户认为系统算错。
  4. 改口径需同步改 Lua、Agent 状态累计、Server 差分、前端文案。

5. 目标架构

5.1 目标数据流

mermaid
flowchart TB
  subgraph edge [边缘节点]
    OR[OpenResty]
    LOG[access.log]
    PROC[主机 /proc 与磁盘]
    STUB[stub_status 连接数]
    AG[Agent]
    OR -->|log_format 写行| LOG
    LOG -->|仅 tail 增量明细| AG
    PROC -->|读数快照| AG
    STUB -->|瞬时连接| AG
    OR -->|健康探测| AG
  end

  subgraph server [控制面 Server]
    HB[心跳 / WS 接收]
    CH[(ClickHouse)]
    AGG[聚合查询层]
    API[管理端 API]
    HB --> CH
    CH --> AGG
    AGG --> API
  end

  subgraph ui [管理端]
    DASH[看板:全局业务趋势]
    ZONE[Zone:按域名过滤]
    NODE[节点:主机资源 + 健康]
  end

  AG -->|AccessLogs + HostSnapshot + Health| HB
  API --> DASH
  API --> ZONE
  API --> NODE

5.2 职责矩阵

能力AgentServer前端
写 access.logOpenResty
读并上报明细入库
sum/count/uniq/TopN展示
Zone 域名过滤选择 Zone
主机 CPU/内存/网卡读原值上报差分/平均节点/看板资源区
OpenResty 连接数读瞬时上报最近值节点健康
业务 24h 入出站日志聚合统一称「已提供/接收数据」

6. 指标与字段模型

6.1 权威字段表(目标)

L1 业务交付(来自访问日志)

概念存储字段聚合展示名
请求时间logged_at时间窗过滤
节点node_idgroup
客户端 IPremote_addruniq → UV唯一访问者
Hosthostgroup / Zone 映射域名
路径path可选
状态码status_codegroup状态码分布
已提供数据bytes_sentsum已提供数据
接收数据request_lengthsum接收数据(可选展示)
地区region(Server 解析写入)group来源地区

说明:OpenResty log_format 中 JSON 键名可继续叫 bytes_sent,值必须来自 $body_bytes_sent(与现网一致),表示响应体交付量,即「已提供数据」。

L2 边缘健康(瞬时,不做 24h 业务总量)

概念字段说明
OpenResty 健康openresty_status / message已有
当前连接openresty_connectionsstub_status
(可选)近窗 QPS 粗估仅节点详情「此刻」,不得作为 24h 总量权威若实现须标明「瞬时」

L3 宿主机资源

概念字段展示名
CPU / 内存 / 磁盘占用host_metrics保持
网卡累计字节network_rx_bytes / network_tx_bytes宿主机网卡入/出站
磁盘 IO 累计disk_read_bytes / disk_write_bytes磁盘读/写

6.2 已删除字段(无兼容层)

原字段处置原因
openresty_tx_bytes / openresty_rx_bytes删除业务字节以 access log 为准
TrafficReport 及 TopN/窗内 UV删除边缘预聚合
Agent state 内业务 lifetime 累计删除违背 P1
Lua shared dict 业务吞吐/窗口请求计数删除非投递主路径

6.3 命名对照(前端文案强制)

禁止混用文案正确文案数据来源
OpenResty 出站(指业务量)已提供数据sum(bytes_sent)
OpenResty 入站(指业务量)接收数据sum(request_length)
网络出站(未说明)宿主机网卡出站network_tx 差分
已提供数据 vs 出站 两套卡片只保留一套业务卡片日志

7. Agent 设计

7.1 心跳载荷(目标协议)

保留并强化:

text
NodePayload
  identity / version / openresty_status / openresty_message  # 最新态 → PG
  profile                  # 主机概况(低频)
  host_metrics             # L3 资源读数(含网卡累计原值)
  edge_health              # L2:status + connections(CH 时序;message 不进 CH)
  access_logs[]            # L1 明细(主路径)
  health_events[]
  buffered[]               # 缓冲的是上述事实,不是报表
  waf_ip_group_checksums

协议中已删除(无兼容层):

text
traffic_report
openresty_observation
snapshot / buffered_observability 别名

7.2 Access log 上报要求

每条明细至少包含:

字段必填备注
logged_at_unix请求完成时间
remote_addrUV
hostZone 映射
path可截断
status_code
bytes_sentbody 字节,已提供数据
request_length接收数据

Agent 职责:

  1. 按 offset tail access.log(截断/轮转时重置 offset,只上报文件中仍存在的新行)。
  2. 结构化解析后批量放入心跳 / WS。
  3. 离线写入本地 buffer,连通后按窗口补传。
  4. 不对明细做 sum/count/uniq。

7.3 主机 Snapshot

  • 继续上报网卡/磁盘 累计计数器原值(非业务预聚合)。
  • Server 侧对累计值做相邻采样非负差分 → 宿主机趋势。
  • 这与「已提供数据」无关,UI 必须分区展示。

7.4 OpenResty 本地观测

收敛后的状态:

  • 保留:健康检查、stub_status 当前连接。
  • 主路径不再依赖 log.lua 的 shared dict 业务计数;/openflare/observability 只返回健康与连接快照,不作为业务报表来源。

7.5 与 Agent 设计文档的关系

本设计强化 Agent 与发布模型 中的「纯粹数据落地」:

  • 配置与证书:落地与上报应用状态。
  • 观测:只搬运事实,不搬运业务结论。

8. Server 设计

8.1 入库

输入说明
access_logs[]of_node_access_logs权威业务明细
host_metricsof_node_metric_snapshotsL3;网卡/磁盘累计
openresty_status / openresty_messagePG 节点表L2 最新态权威(message 仅此)
edge_healthof_node_edge_healthL2 时序:status + connections(无 message

GeoIP:继续在 Server 入库路径解析 remote_addrregion,不在 Agent 做。

8.2 聚合层(统一)

所有业务趋势与 Zone 统计共用同一查询语义:

text
过滤:logged_at ∈ [since, until]
可选:node_id / host IN (...)
指标:
  request_count     = count()
  unique_visitors   = uniqExact(remote_addr)
  bytes_provided    = sum(bytes_sent)      -- 已提供数据
  bytes_received    = sum(request_length)  -- 接收数据
  按 hour/bucket 折叠 series
  按 status_code / host / region 分布

实现位置:

  • Zone:GET .../zones/:id/stats(已有,对齐字段命名)
  • 看板:overview 的 traffic / 业务网络趋势 改为调用同一聚合(全局、无 host 过滤或 Top 过滤)
  • 节点详情:业务量 = 该 node_id 过滤的同一聚合;主机网卡仍走 metric 差分

8.3 派生汇总(可选性能路径)

当明细查询在 24h 全量节点上过重时,允许 Server 侧 物化视图:

text
of_access_log_hourly
  (hour, node_id, host, request_count, bytes_sent, bytes_received, ...)

约束:

  • 仅由 CH 从 of_node_access_logs 派生,禁止 Agent 直接写该表。
  • Zone / 看板优先读 rollup,缺口回退明细(与现有 metric hourly 策略类似)。

8.4 停用的分析路径

路径迁移后
BuildNetworkTrendPoints 对 openresty_rx/tx 差分删除或仅保留 network_* 主机曲线
of_node_obs_openresty 吞吐字段停止写入;TTL 过期后删表或缩列
of_node_request_reports + traffic hourly业务趋势不再依赖;可整表废弃
Dashboard compact 中 openresty_tx 序列改为 bytes_provided 序列

9. API 与前端

9.1 语义统一的响应字段

建议在业务统计 API 中统一使用:

json
{
  "request_count": 0,
  "unique_visitors": 0,
  "bytes_provided": 0,
  "bytes_received": 0,
  "series": [
    {
      "bucket_started_at": "...",
      "request_count": 0,
      "unique_visitors": 0,
      "bytes_provided": 0,
      "bytes_received": 0
    }
  ]
}

API 业务字节字段使用 bytes_provided / bytes_received(访问日志聚合);不再返回 openresty 吞吐别名。

9.2 看板

  • 业务区:请求趋势、已提供数据、接收数据(可选)、状态码、Top 域名、来源地区 —— 全部 L1。
  • 资源区:CPU/内存、宿主机网卡、磁盘 IO —— 全部 L3。
  • 禁止:在业务区展示「OpenResty 入/出站」作为与 Zone 对账的指标。

「24 小时网络与磁盘趋势」建议拆分或改标题:

  • 「24 小时业务流量」→ bytes_provided / bytes_received / 请求
  • 「24 小时宿主机网络与磁盘」→ network_* / disk_*

9.3 Zone /websites/:id

  • 保持「已提供的数据总计」等卡片。
  • 数据与看板业务区 同一聚合函数,仅 hosts = zone 域名列表
  • 文档与 UI 可注明:全局看板含全部 Host;本页仅本 Zone。

9.4 节点详情

  • 业务吞吐:该节点 sum(bytes_sent) 等。
  • OpenResty:健康 + 当前连接。
  • 网卡:明确「宿主机」。

10. OpenResty 与日志格式

10.1 保持

现有 JSON log_format 核心字段:

text
ts, host, path, remote_addr, status, request_time,
bytes_sent (= $body_bytes_sent), request_length

10.2 变更

  • 不再依赖 log phase 写入业务 shared dict 计数作为控制面输入。
  • 观测端口请求继续不写业务统计(或 access_log off)。

10.3 Agent 解析

  • 协议 NodeAccessLog 增加 request_length
  • 旧日志行缺字段时按 0,不阻断整批。

11. 升级与迁移(无兼容层)

11.1 阶段回顾(已落地)

阶段内容
M1–M5读路径切 access log;协议 v2;停预聚合;edge_health + access_log_hourly;删旧表与 API 兼容字段

11.2 升级策略

  • Agent:销毁重建优先;允许二进制替换。
  • 二进制替换时:本地旧观测缓冲(含 snapshot / openresty_observation / traffic_report整文件删除,运行后重建。
  • Server 解析 v1 字段,双读 request_reports / openresty 吞吐。
  • 明细缺失时段:业务图为空或仅部分;不得用网卡或已删除的 openresty 吞吐冒充已提供数据。

11.3 数据回填

  • 历史「已提供数据」以 access log 为准。
  • of_access_log_hourly 创建前历史用 goose 回填 SQL(ANTI JOIN 防重)。

11.4 健康状态权威

  • 当前态:PG openresty_status / openresty_message
  • 时序:日志主库 of_node_edge_health(status + connections;无 message)。

11.5 UV

  • 整窗独立访客uniqExact(remote_addr)(看板合计、Zone 合计)。
  • 分桶 UV(Zone 曲线):桶内 uniq,不可跨桶相加;UI 须标明。
  • 小时趋势路径:不绘 / 不填分时 UV(hourly 表不含 UV)。

12. 存储与容量

  • 业务趋势依赖明细或 hourly rollup,需关注 of_node_access_logs TTL 与采样。
  • 若明细量过大:优先 Server 侧 rollup,而不是恢复 Agent 预聚合。
  • 可对 path 高基数场景限制明细 path 长度(已有),聚合默认不按完整 path 做全局 Top。

13. 风险与权衡

风险缓解
明细量大导致 CH 与心跳变重批量、压缩、采样策略评估;Server rollup;限制单次条数
短暂丢失日志导致业务量偏低本地 buffer 与轮转处理;监控 access log 采集滞后
用户仍对比「网卡出站」与「已提供」UI 分区与文案强制「宿主机」前缀
旧 Agent 长期在线无兼容层;必须升级/重建 Agent

为何不保留 Agent 预聚合作为优化?

  • 省带宽的代价是再次分裂真相、口径漂移、本次问题重演。
  • 优化应落在 Server 派生表与查询,而不是边缘业务计算。

14. 关键决策摘要

决策选择否决方案
业务流量真相访问日志OpenResty dict / TrafficReport
Agent 角色只上报事实边缘 UV/TopN/吞吐累计
「出站」与「已提供」合并为已提供数据双字段双管道长期并存
网卡流量独立 L3,单独文案与业务出站并列对账
性能CH rollupAgent 预聚合
迁移先切读路径再瘦身 Agent先删明细依赖预聚合

15. 文档与代码映射

区域主要路径
协议pkg/protocol/agent.go
Agent 采集internal/apps/agent/observability/heartbeat/
OpenResty 日志与 Luapkg/render/openresty/internal/apps/agent/nginx/observability_assets.go
Server 入库internal/apps/openflare/agent/observability.go
日志聚合internal/repository/analytics/node_access_log*.gointernal/apps/openflare/zone/stats.go
看板internal/apps/openflare/dashboard/internal/apps/openflare/observability/analytics.go
前端frontend/app/(main)/page.tsxcomponents/dashboard/*websites/.../zone-overview.tsx

推荐阅读顺序:

  1. 观测数据传输模型(最新:传什么、从哪采、频率、示例 JSON)
  2. Agent 上报协议与观测落库数据模型(协议字段与 DDL)

16. 修订记录

日期说明
2026-07-17初稿:针对双真相、Agent 预聚合、字段冗余给出目标架构与迁移阶段
2026-07-17增补协议/表结构专章链接 observability-data-model.md

基于 Apache License 2.0 发布