Files
OpenFlare/docs/design/observability-data-model.md
T
ryan ee9d651c8a docs(obs): 约定访问日志 cache_status 与明细三态展示
仅上报 $upstream_cache_status,不上报回源地址;UI 由原始值推导
命中缓存 / 回源 / 未使用缓存。
2026-07-18 22:46:46 +08:00

30 KiB
Raw Blame History

Agent 上报协议与观测落库数据模型

你会学到:重构后 Agent 心跳/WS 上报的 数据结构、Server 如何解析与写入、ClickHouse / 关系库 目标表结构。
无协议兼容层:Agent 以销毁重建或二进制替换升级;旧字段不解析、旧缓冲整文件丢弃。

本设计是 边缘可观测与业务流量统计重构 的 协议与存储专章,实现时以本文字段与 DDL 为准。

先读传输全景与示例: 观测数据传输模型。


1. 设计目标

目标 说明
Agent 只报事实 明细 + 主机读数 + 边缘健康瞬时态;无业务预聚合
一张业务明细表 访问日志是 L1 唯一写入路径
聚合在库内/控制面 小时汇总由 ClickHouse MV 或查询生成,Agent 不写汇总表
字段不重叠 bytes_sent = 已提供数据;网卡 network_* = 宿主机;不再有业务 openresty_tx
可演进 新字段可选;缺省数值填 0,不解析已删除的旧协议字段

2. 分层与写入总览

                    Agent NodePayload (v2)
                              │
              ┌───────────────┼───────────────┐
              ▼               ▼               ▼
         access_logs     host_metrics    edge_health
         (L1 明细)       (L3 读数)       (L2 瞬时)
              │               │               │
              ▼               ▼               ▼
     of_node_access_logs  of_node_metric_  of_node_edge_health
              │           snapshots              │
              │               │                  │
              ▼               ▼                  │
     of_access_log_hourly  of_node_metric_       │
     (MV, Server 侧)       capacity_hourly (MV)  │
              │               │                  │
              └─────── 管理端聚合 API ───────────┘

关系库 (PostgreSQL/SQLite):节点最新状态、Profile、健康事件(非明细湖)
层 含义 Agent 上报块 ClickHouse 事实表
L1 业务交付 access_logs of_node_access_logs
L2 边缘健康 edge_health of_node_edge_health
L3 宿主机资源 host_metrics of_node_metric_snapshots

3. Agent 上报数据结构(协议 v2)

3.1 顶层 NodePayload

传输:HTTP 心跳 body 与 WebSocket status 消息共用同一结构。

{
  "schema_version": 2,
  "node_id": "n_xxx",
  "name": "edge-1",
  "ip": "1.2.3.4",
  "version": "3.3.0",
  "ext_version": "",
  "current_version": "cfg-checksum-or-version",
  "last_error": "",
  "profile": { },
  "host_metrics": { },
  "edge_health": { },
  "access_logs": [ ],
  "buffered": [ ],
  "health_events": [ ],
  "waf_ip_group_checksums": { "1": "md5..." }
}
字段 类型 必填 说明
schema_version int 建议 固定为 2(本设计)
node_id string ✅ 节点 ID
name string ✅ 显示名
ip string ✅ 上报 IP
version / ext_version string ✅ Agent 版本
current_version string 本地激活配置版本摘要
last_error string 最近同步/运行错误,可空
openresty_status string ✅(有 OpenResty 时) 最新健康态权威字段 → 写 PG 节点表
openresty_message string 最新健康说明权威字段 → 写 PG 节点表(不进 CH)
profile object 主机概况,变化时上报(可节流)
host_metrics object 建议每拍 L3 资源快照
edge_health object 建议每拍 L2 连接时序 + 与顶层一致的 status
access_logs array 本拍增量访问明细
buffered array 离线补传的事实批次(见 §3.6)
health_events array 边缘健康事件
waf_ip_group_checksums map 差分同步用,非观测湖

已删除、Server 不再解析的字段(无兼容层):

旧字段 处置
traffic_report 不存在于协议;不落库
openresty_observation 不存在;连接与状态走 edge_health
snapshot 不存在;仅用 host_metrics
buffered_observability 不存在;仅用 buffered

3.2 profile — 主机概况(低频)

对应关系库 of_node_system_profiles(或现有等价表),不进 ClickHouse 明细湖。

{
  "hostname": "edge-1",
  "os_name": "linux",
  "os_version": "...",
  "kernel_version": "...",
  "architecture": "amd64",
  "cpu_model": "...",
  "cpu_cores": 8,
  "total_memory_bytes": 16106127360,
  "total_disk_bytes": 107374182400,
  "uptime_seconds": 864000,
  "reported_at_unix": 1720000000
}
字段 语义
硬件/OS 描述字段 事实读数
reported_at_unix Agent 采集时刻(UTC 秒)

3.3 host_metrics — 宿主机资源(L3)

全部为读数,不做 24h 业务总量。
网卡/磁盘字节为 内核累计计数器原值(单调递增,重启可归零);CPU 为瞬时百分比;内存/磁盘占用为当前用量。

{
  "captured_at_unix": 1720000000,
  "cpu_usage_percent": 12.5,
  "memory_used_bytes": 4294967296,
  "memory_total_bytes": 16106127360,
  "storage_used_bytes": 50000000000,
  "storage_total_bytes": 107374182400,
  "disk_read_bytes": 9000000000,
  "disk_write_bytes": 12000000000,
  "network_rx_bytes": 500000000000,
  "network_tx_bytes": 800000000000
}
字段 类型 语义 Server 如何用
captured_at_unix int64 采样时刻 captured_at
cpu_usage_percent float 瞬时 CPU% 直接存;趋势取平均
memory_* / storage_* int64 当前用量/总量 直接存;算占用率
disk_read_bytes / disk_write_bytes int64 累计 IO 字节 存原值;查询时相邻差分
network_rx_bytes / network_tx_bytes int64 累计 网卡字节 存原值;查询时相邻差分 →「宿主机网卡入/出站」

Agent 禁止 在上报前对网卡/磁盘做「本周期增量」替换累计值(否则 Server 差分会错)。

3.4 edge_health — OpenResty 边缘健康(L2)

仅瞬时态,不包含业务吞吐。

{
  "captured_at_unix": 1720000000,
  "status": "healthy",
  "message": "",
  "connections": 42
}
字段 类型 语义
status string healthy / unhealthy / unknown(须与顶层 openresty_status 一致)
message string 状态说明(上报可带;仅用于回填 PG 最新态,不进 CH)
connections int64 stub_status Active connections

健康状态权威源(收敛)

数据 权威存储 说明
当前 OpenResty 是否健康 + 说明文案 PG 节点表 openresty_status / openresty_message UI 徽章、列表、告警以这里为准
时序 健康 status + 连接数 CH of_node_edge_health(status, connections) 连接曲线 / 健康状态历史;无 message 列
Agent 上报 顶层 status/message + edge_health Server 归一化后二者 status 对齐;message 只写 PG

因此:查「现在是否 unhealthy」→ 读 PG;查「过去 24h 连接数」→ 读 CH。

3.5 access_logs[] — 访问明细(L1,业务唯一事实)

Agent:tail access.log → 解析 JSON 行 → 原样字段上报(可截断 path)。

{
  "logged_at_unix": 1720000001,
  "remote_addr": "203.0.113.10",
  "host": "www.example.com",
  "path": "/api/v1/ping",
  "status_code": 200,
  "bytes_sent": 1024,
  "request_length": 128,
  "request_time_ms": 15,
  "user_agent": "Mozilla/5.0 ...",
  "cache_status": "HIT"
}
字段 类型 必填 来源(OpenResty) 业务含义
logged_at_unix int64 ✅ $time_iso8601 解析 请求完成时间
remote_addr string ✅ $remote_addr 客户端 IP → UV
host string ✅ $host 域名 → Zone 归属
path string ✅ $request_uri,Agent 可截断 路径
status_code int ✅ $status 状态码
bytes_sent int64 ✅ $body_bytes_sent 已提供数据(响应体)
request_length int64 建议 $request_length 接收数据
request_time_ms int64 可选 $request_time * 1000 耗时;缺省 0
user_agent string 建议 $http_user_agent UA;可截断入库
cache_status string 建议 $upstream_cache_status 边缘缓存结果(见 §3.5.1)

明确不由 Agent 上报(由 Server 写入):

  • region / 国家:入库时 GeoIP 解析
  • id / created_at:Server 生成
  • node_id:取自 payload / 鉴权上下文

明确不上报:

  • upstream_addr / 回源地址 / origin_fetched:不做回源端点追踪;「是否回源」仅由 cache_status 在控制面推导(§3.5.1)

3.5.1 cache_status — 缓存命中与回源(明细优先)

目标(第一期): 访问日志明细/详情能展示「是否命中缓存 / 是否回源 / 未使用缓存」。
口径: 只存 OpenResty $upstream_cache_status 原始值;不上报 upstream 地址。

原始值(入库)

值 含义(OpenResty)
HIT 命中缓存
MISS 未命中,向 upstream 取内容
BYPASS 跳过缓存(如 method/cookie/策略导致 $openflare_skip_cache)
EXPIRED 缓存过期后回源
STALE 提供陈旧缓存(stale)
UPDATING 后台更新中,可能返回旧缓存
REVALIDATED 协商验证后仍用缓存
- 或空 未经过 proxy_cache(如 Pages 本地静态、非代理 location)

UI 三态推导(不落库)

控制面展示用派生枚举 cache_outcome,不写 CH:

三态 条件(cache_status) 列表标签建议
命中缓存 HIT / STALE / REVALIDATED / UPDATING 命中
回源 MISS / EXPIRED 回源
未使用缓存 BYPASS / - / "" 未缓存

详情可同时显示三态 + 原始 cache_status。

边界

  • Pages 静态 / 无 proxy_cache 的 location:多为空或 - → 未使用缓存,不得标成「命中」。
  • 第一期只做明细可见;命中率看板、hourly 维度可后续用同一列聚合。

单次心跳条数建议:

  • 软上限例如 2000 条/拍;超出进入 buffered 下一批,禁止 在 Agent 压成 TrafficReport。

3.6 buffered[] — 离线补传(只装事实)

{
  "captured_at_unix": 1719999900,
  "host_metrics": { },
  "edge_health": { },
  "access_logs": [ ]
}
字段 说明
captured_at_unix 该批次采集/缓冲时刻,用于 ack 与去重窗口
host_metrics / edge_health / access_logs 与主 payload 同结构;可省略空块

禁止 在 buffered 中携带 traffic_report 或 rx/tx 吞吐。

3.7 health_events[]

{
  "event_type": "openresty_unhealthy",
  "severity": "critical",
  "message": "...",
  "triggered_at_unix": 1720000000,
  "metadata": { }
}

写入关系库健康事件表(现有模型即可),不进访问日志湖。

3.8 Go 协议草图(目标)

// pkg/protocol/agent.go(目标形态,实现时替换旧类型)

type NodePayload struct {
    SchemaVersion       int                    `json:"schema_version,omitempty"`
    NodeID              string                 `json:"node_id"`
    Name                string                 `json:"name"`
    IP                  string                 `json:"ip"`
    Version             string                 `json:"version"`
    ExtVersion          string                 `json:"ext_version"`
    CurrentVersion      string                 `json:"current_version"`
    LastError           string                 `json:"last_error"`
    OpenrestyStatus     string                 `json:"openresty_status"`  // PG 最新态权威
    OpenrestyMessage    string                 `json:"openresty_message"` // PG 最新态权威;不进 CH
    Profile             *NodeSystemProfile     `json:"profile,omitempty"`
    HostMetrics         *NodeHostMetrics       `json:"host_metrics,omitempty"`
    EdgeHealth          *NodeEdgeHealth        `json:"edge_health,omitempty"`
    AccessLogs          []NodeAccessLog        `json:"access_logs,omitempty"`
    Buffered            []BufferedFacts        `json:"buffered,omitempty"`
    HealthEvents        []NodeHealthEvent      `json:"health_events"`
    WAFIPGroupChecksums map[string]string      `json:"waf_ip_group_checksums,omitempty"`
}

type NodeHostMetrics struct {
    CapturedAtUnix    int64   `json:"captured_at_unix"`
    CPUUsagePercent   float64 `json:"cpu_usage_percent"`
    MemoryUsedBytes   int64   `json:"memory_used_bytes"`
    MemoryTotalBytes  int64   `json:"memory_total_bytes"`
    StorageUsedBytes  int64   `json:"storage_used_bytes"`
    StorageTotalBytes int64   `json:"storage_total_bytes"`
    DiskReadBytes     int64   `json:"disk_read_bytes"`
    DiskWriteBytes    int64   `json:"disk_write_bytes"`
    NetworkRxBytes    int64   `json:"network_rx_bytes"`
    NetworkTxBytes    int64   `json:"network_tx_bytes"`
}

type NodeEdgeHealth struct {
    CapturedAtUnix int64  `json:"captured_at_unix"`
    Status         string `json:"status"`
    Message        string `json:"message"`
    Connections    int64  `json:"connections"`
}

type NodeAccessLog struct {
    LoggedAtUnix  int64  `json:"logged_at_unix"`
    RemoteAddr    string `json:"remote_addr"`
    Host          string `json:"host"`
    Path          string `json:"path"`
    UserAgent     string `json:"user_agent,omitempty"`
    CacheStatus   string `json:"cache_status,omitempty"` // $upstream_cache_status
    StatusCode    int    `json:"status_code"`
    BytesSent     int64  `json:"bytes_sent"`      // body_bytes_sent,已提供数据
    RequestLength int64  `json:"request_length"`  // 接收数据
    RequestTimeMs int64  `json:"request_time_ms"` // 可选
}

type BufferedFacts struct {
    CapturedAtUnix int64            `json:"captured_at_unix"`
    HostMetrics    *NodeHostMetrics `json:"host_metrics,omitempty"`
    EdgeHealth     *NodeEdgeHealth  `json:"edge_health,omitempty"`
    AccessLogs     []NodeAccessLog  `json:"access_logs,omitempty"`
}

4. Server 解析与落库流程

4.1 入口

  • HTTP:POST /api/v1/agent/... 心跳(现有路径)
  • WebSocket:type=status payload = NodePayload
  • 鉴权:X-Agent-Token → 绑定 node_id(payload.node_id 必须与 token 节点一致)

4.2 处理流水线(单次 payload)

1. 反序列化 NodePayload
2. 归一化(normalize)
   - schema_version < 2:
       host_metrics ← snapshot
       edge_health.status ← openresty_status
       edge_health.connections ← openresty_observation.connections(若有)
       traffic_report → drop
       openresty_observation.rx/tx → drop
       buffered ← buffered_observability
   - path 再截断、status 范围钳制、负数字节 → 0
3. 关系库事务(节点最新态)
   - 更新 node 在线时间、IP、版本、edge_health.status/message
   - upsert profile(若有)
   - insert health_events(若有)
4. ClickHouse 异步 batch(失败记日志,不阻断心跳响应的配置下发)
   a. access_logs + buffered[].access_logs
        → 补 region(GeoIP)
        → 分配 snowflake id
        → BatchInsert of_node_access_logs
   b. host_metrics + buffered[].host_metrics
        → of_node_metric_snapshots
   c. edge_health + buffered[].edge_health
        → of_node_edge_health(仅 connections + status 快照可选)
5. 返回心跳响应(settings / active_config / waf 差分)
6. 若使用 buffer ack:按 buffered.captured_at_unix 列表确认

4.3 归一化规则(硬约束)

规则 行为
logged_at 超前 now+5m 钳制为 now 或丢弃该条(实现选定一种并单测)
logged_at 早于 now−TTL 仍可写入,依赖表 TTL 清理
空 host 允许,聚合进「未归属」
bytes_sent / request_length < 0 置 0
单批 access_logs > N 截断并打点监控(或只入 buffer 队列),不改为预聚合
重复补传 CH 允许少量重复行;查询用 sum 近似(不强制精确去重)

4.4 字段映射表(上报 → 表)

上报路径 目标存储 列
access_logs[] CH of_node_access_logs 见 §5.1
host_metrics CH of_node_metric_snapshots 见 §5.2
edge_health CH of_node_edge_health + PG node 最新状态 见 §5.3 / §5.6
profile PG of_node_system_profiles 现有列
health_events PG 健康事件表 现有模型
waf_ip_group_checksums 不落观测表 同步逻辑
traffic_report(旧) 不写 —
openresty_rx/tx(旧) 不写 —

4.5 查询侧(不落新「业务出站」列)

产品指标 SQL 语义(示意)
已提供数据 sum(bytes_sent)
接收数据 sum(request_length)
请求数 count()
UV uniqExact(remote_addr)
5xx countIf(status_code >= 500)
按域名/状态码/地区 GROUP BY host / status_code / region
宿主机网卡出站 对 network_tx_bytes 按 node 时间序非负差分后 sum
OpenResty 连接 of_node_edge_health.connections 最新或平均

5. 表结构(目标 DDL)

引擎与 TTL 与现网一致倾向:访问日志 90 天,指标 30 天。
id 使用控制面 Snowflake/唯一 UInt64。

5.1 L1 事实表:of_node_access_logs

CREATE TABLE IF NOT EXISTS of_node_access_logs
(
    id              UInt64,
    node_id         String,
    logged_at       DateTime64(3, 'UTC'),
    remote_addr     String,
    region          String,              -- Server GeoIP 写入,Agent 不传
    host            String,
    path            String,
    user_agent      String DEFAULT '',   -- $http_user_agent
    cache_status    String DEFAULT '',   -- $upstream_cache_status
    status_code     Int32,
    bytes_sent      UInt64,              -- 已提供数据(body)
    request_length  UInt64 DEFAULT 0,    -- 接收数据
    request_time_ms UInt32 DEFAULT 0,    -- 可选
    created_at      DateTime64(3, 'UTC')
)
ENGINE = MergeTree()
PARTITION BY toYYYYMM(logged_at)
ORDER BY (node_id, logged_at, host, status_code, remote_addr)
TTL toDateTime(logged_at) + INTERVAL 90 DAY
SETTINGS index_granularity = 8192;
列 类型 来源
id UInt64 Server
node_id String 鉴权/payload
logged_at DateTime64(3) logged_at_unix
remote_addr String 上报
region String Server GeoIP
host String 上报
path String 上报
user_agent String 上报(可空)
cache_status String 上报(可空)→ 缓存状态
status_code Int32 上报
bytes_sent UInt64 上报 → 已提供数据
request_length UInt64 上报 → 接收数据
request_time_ms UInt32 上报可选
created_at DateTime64(3) Server now

迁移: 现表已有 bytes_sent / request_length / request_time_ms / user_agent;缓存状态新增:

ALTER TABLE of_node_access_logs
    ADD COLUMN IF NOT EXISTS cache_status String DEFAULT '';

5.2 L1 小时汇总(Server 侧 MV)

禁止 Agent 写入。 供看板/节点 24h 快速查询请求数、错误数、字节量。

已实现选型:SummingMergeTree + 不含 UV 列。

CREATE TABLE IF NOT EXISTS of_access_log_hourly
(
    node_id         String,
    hour            DateTime('UTC'),
    host            String,
    request_count   UInt64,
    error_count     UInt64,
    bytes_sent      UInt64,
    request_length  UInt64
)
ENGINE = SummingMergeTree()
PARTITION BY toYYYYMM(hour)
ORDER BY (node_id, hour, host)
TTL hour + INTERVAL 90 DAY;

CREATE MATERIALIZED VIEW IF NOT EXISTS of_access_log_hourly_mv
TO of_access_log_hourly
AS
SELECT
    node_id,
    toStartOfHour(logged_at) AS hour,
    host,
    toUInt64(count()) AS request_count,
    toUInt64(countIf(status_code >= 500)) AS error_count,
    sum(bytes_sent) AS bytes_sent,
    sum(request_length) AS request_length
FROM of_node_access_logs
GROUP BY node_id, hour, host;

历史小时(MV 创建前已入库的明细)需一次性回填,见迁移 202607180003_backfill_access_log_hourly.sql(ANTI JOIN 防重)。

UV 策略(必须遵守)

场景 数据源 算法 说明
窗口总 UV(看板汇总、节点卡片、Zone 汇总) of_node_access_logs 明细 uniqExact(remote_addr)(TrafficSummary / 节点聚合) 唯一权威;不可用小时 UV 相加
24h 趋势折线请求/错误/字节 of_access_log_hourly 优先,缺数据回落明细桶 sum(request_count) 等 小时路径 不填 unique_visitor_count(恒为 0)
24h 趋势折线分时 UV 仅明细桶路径 桶内 uniqExact 走 hourly 时 UI 应展示空/0 或隐藏 UV 序列,禁止对小时行做 sum(UV)

为何 hourly 不存 UV:

  1. SummingMergeTree 只能安全合并可加和计数;uniqExact 跨 part 合并需要 AggregatingMergeTree + state,实现与查询更重。
  2. 即便存每小时 UV,对多小时窗口 相加会严重高估(同一 IP 跨小时重复计)。
  3. 产品「24h 独立访客」只认整窗 uniqExact;趋势图主序列是请求量/错误/字节,分时 UV 非主指标。

可选未来:若需要分时 UV 曲线,再单独加 AggregatingMergeTree 状态表或查询时对明细做 uniqExact 按小时 group(成本更高,不阻塞当前看板)。

5.3 L3 事实表:of_node_metric_snapshots(保留,语义明确)

CREATE TABLE IF NOT EXISTS of_node_metric_snapshots
(
    id                  UInt64,
    node_id             String,
    captured_at         DateTime64(3, 'UTC'),
    cpu_usage_percent   Float64,
    memory_used_bytes   Int64,
    memory_total_bytes  Int64,
    storage_used_bytes  Int64,
    storage_total_bytes Int64,
    disk_read_bytes     Int64,    -- 累计原值
    disk_write_bytes    Int64,
    network_rx_bytes    Int64,    -- 累计原值 → 宿主机网卡入站
    network_tx_bytes    Int64,    -- 累计原值 → 宿主机网卡出站
    created_at          DateTime64(3, 'UTC')
)
ENGINE = MergeTree()
PARTITION BY toYYYYMM(captured_at)
ORDER BY (node_id, captured_at, id)
TTL toDateTime(captured_at) + INTERVAL 30 DAY
SETTINGS index_granularity = 8192;

列与现网一致;文档与 API 必须标注 network_ 为宿主机网卡累计值*。

5.4 L3 小时汇总:of_node_metric_capacity_hourly(保留)

现有 min/max 用于累计计数器小时增量近似 + CPU/内存平均。逻辑不变:

  • network_tx_max - network_tx_min ≈ 该小时宿主机出站
  • 不得 用于「已提供数据」

5.5 L2 事实表:of_node_edge_health(新建,替换吞吐型 openresty 表)

CREATE TABLE IF NOT EXISTS of_node_edge_health
(
    id           UInt64,
    node_id      String,
    captured_at  DateTime64(3, 'UTC'),
    status       LowCardinality(String),  -- healthy / unhealthy / unknown
    connections  Int64,
    created_at   DateTime64(3, 'UTC')
)
ENGINE = MergeTree()
PARTITION BY toYYYYMM(captured_at)
ORDER BY (node_id, captured_at, id)
TTL toDateTime(captured_at) + INTERVAL 30 DAY
SETTINGS index_granularity = 8192;
列 说明
status 瞬时健康(与 PG 当前态同源;用于时序,非唯一 UI 权威)
connections 当前连接数

无 message 列(说明文案仅 PG 最新态)。
无 openresty_rx_bytes / openresty_tx_bytes。

5.6 关系库(节点最新态,非分析湖)

与观测湖分离,保持「最新一份」:

表(逻辑名) 用途 关键列
of_nodes(或现节点表) 在线、版本、IP last_seen_at, openresty_status, openresty_message, agent_version
of_node_system_profiles profile upsert hostname, cpu_cores, total_memory_bytes, ...
健康事件表 health_events event_type, severity, message, triggered_at

具体物理表名以仓库现有 GORM 模型为准;本设计不强制改名,只强制 不再把业务吞吐写进节点表。

5.7 废弃表(停止写入 → TTL 后删除)

表 原因 替代
of_node_request_reports Agent 预聚合 of_node_access_logs + hourly
of_node_traffic_hourly + MV 依赖 request_reports of_access_log_hourly
of_node_obs_openresty 含业务 rx/tx of_node_edge_health
of_node_openresty_hourly + MV 业务吞吐差分 of_access_log_hourly 的 bytes_*

Relay 专用 of_node_obs_frps / of_node_obs_frpc 保留(非本 Agent 主路径,但同属 CH 观测)。


6. 表与协议对照总表

产品概念 协议字段 表.列 聚合
已提供数据 access_logs[].bytes_sent of_node_access_logs.bytes_sent sum
接收数据 access_logs[].request_length ...request_length sum
请求数 行数 — count
UV(窗口总) remote_addr 同左明细 uniqExact(禁止 sum 小时 UV)
Top 域名 host 同左 group by
状态码分布 status_code 同左 group by
来源地区 — region(Server) group by
宿主机网卡出站 host_metrics.network_tx_bytes of_node_metric_snapshots.network_tx_bytes 时间序差分
宿主机网卡入站 network_rx_bytes 同左 差分
磁盘读/写 disk_*_bytes 同左 差分
CPU/内存 瞬时字段 同左 avg
OpenResty 连接 edge_health.connections of_node_edge_health.connections 最新/avg
OpenResty 健康 edge_health.status 节点表 + 可选 CH 最新

不再存在的映射:

旧概念 旧字段 处置
OpenResty 出站 openresty_tx_bytes 删除;用已提供数据
OpenResty 入站 openresty_rx_bytes 删除;用接收数据
窗口请求报告 traffic_report 删除

7. OpenResty 日志格式(与明细对齐)

目标 log_format(保证 bytes_sent 键 = body;含 UA 与缓存状态):

log_format openflare_json escape=json
  '{"ts":"$time_iso8601","host":"$host","path":"$request_uri",'
  '"remote_addr":"$remote_addr","status":$status,'
  '"request_time":$request_time,'
  '"bytes_sent":$body_bytes_sent,"request_length":$request_length,'
  '"user_agent":"$http_user_agent",'
  '"cache_status":"$upstream_cache_status"}';

Agent 解析:

  • ts → logged_at_unix
  • bytes_sent → 协议 bytes_sent(已提供)
  • request_length → 协议 request_length
  • request_time → 可选 request_time_ms = round(sec * 1000)
  • user_agent → 协议 user_agent
  • cache_status → 协议 cache_status(原样透传,不做三态压缩)

8. 升级策略(无兼容层)

项 策略
Agent 升级 销毁重建优先;允许二进制替换
协议 仅 schema v2 字段;旧 JSON 字段不解析
本地观测缓冲 若仍是旧格式(含 snapshot / openresty_observation / traffic_report)或损坏 → 整文件删除,运行中重建
读路径 业务 API 只读 access_logs(及 hourly);健康当前态读 PG;连接时序读 CH edge_health
旧 Agent 必须升级;控制面不提供 v1 双读路径

9. 示例:一次心跳的落库结果

Agent 上报(节选):

{
  "schema_version": 2,
  "node_id": "n1",
  "host_metrics": {
    "captured_at_unix": 1720000000,
    "cpu_usage_percent": 10,
    "memory_used_bytes": 1,
    "memory_total_bytes": 2,
    "storage_used_bytes": 3,
    "storage_total_bytes": 4,
    "disk_read_bytes": 100,
    "disk_write_bytes": 200,
    "network_rx_bytes": 1000,
    "network_tx_bytes": 2000
  },
  "edge_health": {
    "captured_at_unix": 1720000000,
    "status": "healthy",
    "message": "",
    "connections": 5
  },
  "access_logs": [
    {
      "logged_at_unix": 1720000001,
      "remote_addr": "1.1.1.1",
      "host": "a.example.com",
      "path": "/",
      "status_code": 200,
      "bytes_sent": 500,
      "request_length": 80
    }
  ]
}

写入:

  1. PG 节点最新态:openresty_status / openresty_message(若上报)
  2. of_node_metric_snapshots 1 行(network_tx=2000 累计)
  3. of_node_edge_health 1 行(status + connections=5;无 message)
  4. of_node_access_logs 1 行(bytes_sent=500, request_length=80, region=Server 填充)
  5. MV 异步计入 of_access_log_hourly

查询 24h 已提供数据: sum(bytes_sent) → 至少 500(加历史)
查询宿主机出站: 对 snapshots 差分,与 500 无强制相等关系。


10. 实现检查清单

  • pkg/protocol:仅 v2 字段,无兼容别名
  • Agent:只组 host_metrics / edge_health / access_logs / buffered
  • Server:无 request_reports / openresty 吞吐;健康当前态 PG、时序 CH
  • CH migration:request_length、request_time_ms、of_node_edge_health、of_access_log_hourly、hourly 回填
  • 看板/Zone API 统一读 access log 聚合
  • UV:整窗 uniqExact;Zone 曲线标明分桶 UV;小时趋势不绘 UV

11. 修订记录

日期 说明
2026-07-17 初稿:协议 v2、Server 落库流水线、CH/关系库目标表结构与废弃表清单