高并发服务网关与实时流量分析平台
FluxGate 为一组内部 HTTP 服务提供统一入口:请求在入口完成命名路由、限流和受控重试,每个最终响应形成结构化流量事件;C++ 采集器异步、可恢复地写入分析存储,查询 API 再按路由、状态码和延迟返回可核对的实时聚合。
当前版本为 0.1.0,面向单机 Docker Compose 的可复现开发与验收。是否达到完整交付门槛只以 docs/ACCEPTANCE.md 及其证据目录为准。
当前权威公开运行为 20260904T194934Z-07759130:在 commit 874eaabe5563578ed3d78fa899a5adfe08c0f8de 的干净工作树上完成无缓存构建、13/13 必需阶段和最终清理,结果为 status=passed、authoritative=true。
只把代理、业务服务和数据库启动起来,并不能回答这些运维问题:某条路由是否真的进入了正确服务;一次失败是否按限定策略恢复;被限流的请求有没有进入统计;分析存储短暂不可用后数据是否能追上;聚合计数是否被重放事件放大。
FluxGate 把这些问题收敛为一条可执行闭环:
- 所有业务请求从同一个公共 listener 进入;
catalog与orders两条命名路由进入可区分的 C++ 服务;- 网关在指定路由执行本地 token bucket,并只为幂等故障演示路由做一次 5xx 重试;
- Envoy 把客户端最终看到的结果追加为 JSONL 事件;
- C++ collector 以 checkpoint-after-ACK 方式批写 ClickHouse;
- C++ control 服务只暴露受限聚合 API,并用
FINAL消除 at-least-once 重放造成的逻辑重复; - 自动化验收从公共入口制造真实流量,并交叉核对响应、组件日志、Envoy 计数器和 ClickHouse 行。
- 统一路由:
GET /api/catalog与GET /api/orders返回不同服务身份,后端容器不直接暴露到宿主机。 - 确定性限流:
GET /api/limited使用单 Envoy 进程共享的 local token bucket;冻结参数为容量 2、每 60 秒补充 2 个 token。 - 受控故障恢复:
GET /api/unstable的第一次上游尝试返回503,Envoy 最多重试一次,第二次返回200;只对该幂等GET生效。 - 真实流量事件:入口为每个完成的请求写一行 JSONL,包含 route、最终 status、耗时、上游、尝试次数和字节数。
- 可靠 C++ 采集:只消费换行结束的完整记录;坏行进入 quarantine;稳定
event_id、批写重试、成功确认后原子推进 checkpoint。 - 受限实时查询:按 route、status 和 latency 提供 JSON API;不开放任意 SQL,时间窗最长 24 小时。
- 证据化验收:构建、单测、双路由、限流、一次重试、入库、聚合对照、真实 checkpoint 回退重放、ClickHouse 停机恢复、固定请求数负载和空卷 README smoke 均有自动化入口。
host 127.0.0.1:18080
Client ───────────────────────────────────► Envoy
│
┌────────────────────────┼───────────────────────┐
│ │ │
route catalog route orders analytics routes
│ │ │
▼ ▼ ▼
catalog C++ orders C++ control C++
brpc :8080 brpc :8080 brpc :8080
│
Envoy ── append JSONL ──► access-log volume ──► C++ collector ───────────┤
│ batch INSERT │ FINAL query
▼ ▼
ClickHouse traffic_events
catalog、orders、control 和 collector 由同一个自研 fluxgate_node 二进制按 role 启动。请求转发与事件入库解耦:ClickHouse 暂时不可用时,业务路由仍可服务,JSONL 留在共享卷,collector 保持未确认偏移并重试。
FluxGate/
├── gateway/envoy.yaml # listener、routes、限流、重试、JSONL access log
├── src/
│ ├── main.cpp # 多 role 进程入口
│ ├── services.cpp # 双后端与 analytics/control API
│ ├── collector.cpp # tail、批写、重试、checkpoint 状态机
│ ├── core.cpp # 事件校验、event_id、fallback 计数与原子状态
│ └── clickhouse_client.cpp # 基于 brpc Channel 的 ClickHouse HTTP 客户端
├── clickhouse/init/001_schema.sql # traffic_events 表契约
├── tests/unit_tests.cpp # event/checkpoint 与 10,001 个 fallback request ID 滚动单测
├── scripts/
│ ├── smoke.py # 已启动栈的公共入口 smoke
│ ├── acceptance.py # 完整、证据化自动验收
│ └── load_test.py # 本机固定请求数负载工具
├── deploy/compose.acceptance.yaml # 仅验收时暴露 loopback ClickHouse
├── compose.yaml # 本地产品栈
├── Dockerfile # 固定 bRPC SHA 的多阶段构建
├── docs/ARCHITECTURE.md # 冻结架构与可靠性语义
├── docs/UPSTREAM_AUDIT.md # 固定上游审计证据
└── docs/ACCEPTANCE.md # 验收项、命令、结果与证据索引
- Windows PowerShell;
- Docker Desktop,支持 Docker Compose v2 的
--wait; - Python 3(smoke 只使用标准库);
- 可访问固定上游源码与容器 registry;
- 宿主端口
18080、19901空闲。
第一次构建会从固定 SHA 获取并编译 bRPC,耗时取决于网络、CPU 与 Docker 缓存。
下面的第一次 down --volumes 只清理 Compose 项目 fluxgate 的容器和具名卷,以保证从空数据、空 checkpoint 开始;它会删除此前的 FluxGate 本地事件数据。
# 克隆仓库后,在 FluxGate 仓库根目录执行
$env:FLUXGATE_CLICKHOUSE_PASSWORD = "fluxgate-local-only-change-me"
docker compose down --volumes --remove-orphans
docker compose up --build -d --wait
python scripts/smoke.py成功时 smoke.py 返回 JSON 且顶层 pass 为 true。它只通过公共入口检查 /api/catalog、/api/orders、一次重试恢复和三个 analytics API,并等待对应事件进入查询结果。
使用完毕后清理容器、空卷和当前 PowerShell 进程中的本地密码:
docker compose down --volumes --remove-orphans
Remove-Item Env:FLUXGATE_CLICKHOUSE_PASSWORD若启动或 smoke 失败,也应执行上述清理命令。若需要保留本地分析数据,省略 --volumes,但这不再是空卷复现。
默认公共基址为 http://127.0.0.1:18080。所有公开契约当前只接受 GET。
| Endpoint | 作用 | 关键响应/边界 |
|---|---|---|
GET /healthz |
入口和 control liveness | 返回 status、component、clickhouse_reachable;HTTP 200 本身不等于完整数据闭环健康 |
GET /api/catalog |
路由到 catalog C++ 服务 | JSON 中 service=catalog、route=catalog |
GET /api/orders |
路由到 orders C++ 服务 | JSON 中 service=orders、route=orders |
GET /api/limited |
catalog 业务响应加 route-local 限流 | 同一 60 秒补充周期的前两个请求放行,后续返回 429 与 x-fluxgate-rate-limited: true |
GET /api/unstable |
确定性的一次 503 恢复 | 客户端最终 200;body 中 attempt=2、recovered=true,header 中 x-envoy-attempt-count: 2 |
GET /v1/analytics/routes |
按 route 聚合 | route_name、requests、server_errors、rate_limited、avg_latency_ms |
GET /v1/analytics/statuses |
按最终状态码聚合 | status_code、requests |
GET /v1/analytics/latency |
按 route 返回精确分位数 | requests、p50_ms、p95_ms、p99_ms;这些是查询窗口内的观测值,不是产品 SLO |
Analytics 公共参数:
from、to:Unix epoch 毫秒;默认最近 1 小时,必须有序且跨度不超过 24 小时;route:可选,必须匹配^[a-z][a-z0-9_]{0,63}$;status_code:可选,整数100..599。
PowerShell 示例:
$base = "http://127.0.0.1:18080"
$headers = @{ "x-request-id" = "readme-catalog-1" }
Invoke-RestMethod "$base/api/catalog" -Headers $headers
Invoke-RestMethod "$base/api/orders"
$to = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds()
$from = $to - 3_600_000
Invoke-RestMethod "$base/v1/analytics/routes?from=$from&to=$to"
Invoke-RestMethod "$base/v1/analytics/statuses?from=$from&to=$to&status_code=200"
Invoke-RestMethod "$base/v1/analytics/latency?from=$from&to=$to&route=catalog"先固定 to 再发 analytics 请求,可以避免查询请求自己的 access event 落入被查询窗口。
| 项目 | 默认/要求 | 说明 |
|---|---|---|
FLUXGATE_CLICKHOUSE_PASSWORD |
必填 | 由 Compose 注入 ClickHouse、collector 和 control;不要放入命令行、镜像或提交到仓库 |
.env.example |
仅本地示例 | 示例值明确不可用于生产;实际 .env 被 .gitignore 忽略 |
| bRPC source pin | 5b61020a9533887bde2d8cc03f9306b6b7104906 |
同时冻结于 compose.yaml 与 Dockerfile;升级必须重新审计并完整验收 |
| Envoy / ClickHouse images | 内容摘要固定 | 部署按 name@sha256:... 拉取,避免可变 tag 漂移;当前显式选择 linux/amd64 |
容器内部的 CLICKHOUSE_URL、CLICKHOUSE_USER、CLICKHOUSE_PASSWORD、ACCESS_LOG_PATH、CHECKPOINT_PATH 和 QUARANTINE_PATH 已由 compose.yaml 统一装配,不是公共 API。ClickHouse 使用专用数据库和账号 fluxgate,常规产品栈不把数据库端口暴露到宿主机。
| Host | 可见性 | 用途 |
|---|---|---|
127.0.0.1:18080 |
仅 loopback | 本地开发公共入口,映射 Envoy 容器 :8080 |
127.0.0.1:19901 |
仅 loopback | Envoy admin readiness 与验收计数器;不是产品 API |
127.0.0.1:18123 |
仅完整验收时 | deploy/compose.acceptance.yaml 临时映射 ClickHouse HTTP,供验收交叉核对 |
catalog、orders、control、collector 和 ClickHouse 在 fluxgate 内部网络通信。生产部署不能直接照搬开发端口和安全策略。
Envoy 把最终下游结果追加到 access-log 具名卷。collector 从 checkpoint 的下一偏移开始读取,只处理以换行结束的完整 JSON;无效完整行写入带偏移与原因的 quarantine,末尾半行留到下一轮。
collector 用 SHA-256(source_id + generation + source_offset + raw_line) 生成稳定 event_id:同一日志代际内的同一行重试 ID 不变,日志 inode 变更或长度回退时则先原子持久化新的 generation 再从偏移 0 读取,避免不同代际的同偏移事件相撞。它以最多 500 行或 512 KiB 组成批次,通过同步 ClickHouse HTTP INSERT ... FORMAT JSONEachRow 写入。只有收到整批成功响应后才以临时文件、fsync 和原子 rename 提交已确认偏移;网络错误、超时或非成功状态使用 250 ms 到 5 s 的有界退避重试。已换行但无效的事件(包括无效 UTC 日历时间)使用 generation + offset + raw 的稳定 quarantine ID 幂等留存,不会因 ClickHouse 确定性类型错误永久卡住后续好事件。
该设计对“已完整写入 JSONL 的事件到 ClickHouse 接收”提供 at-least-once,不是 exactly-once。ClickHouse 表固定为:
ENGINE = ReplacingMergeTree(ingest_version)
PARTITION BY toYYYYMM(event_time)
ORDER BY event_id服务端可能已提交但 collector 未收到确认时,同一事件会重放。所有产品查询都显式使用 FROM fluxgate.traffic_events FINAL 获取按 event_id 去重后的逻辑行;不依赖后台 merge,也不使用会改变存储状态的 OPTIMIZE TABLE ... FINAL 作为查询或验收前置条件。
limited的 bucket 作用域是单个 Envoy 进程,不是每连接,也不是分布式全局配额;Envoy 重启会重置内存状态。unstable固定retry_on=5xx、num_retries=1、per_try_timeout=300ms、route 总超时1s。- 网关删除公共请求携带的 Envoy retry/timeout 控制 header,外部调用方不能把静态策略扩成更多重试或更长超时。
- 最终 access event 记录客户端看到的
200;第一次内部503必须由 catalog 日志和 Envoy retry counter 证明,不能从最终事件反推。
ClickHouse 停机时,业务入口和两个后端仍可处理请求;collector 不推进未确认 checkpoint,JSONL 在共享卷累积并持续重试。恢复后 collector 追赶,analytics 最终通过 FINAL 看到逻辑去重事件。此保证不覆盖日志写入前掉电、卷永久损坏、磁盘写满、人工删除日志/checkpoint 或跨主机复制;v1 不宣称零丢失或无限缓冲。
常用本地诊断:
docker compose ps
docker compose logs --tail 200 envoy collector control
Invoke-WebRequest http://127.0.0.1:19901/ready
Invoke-RestMethod http://127.0.0.1:18080/healthz- app 容器以非 root 用户运行,启用只读根文件系统与
no-new-privileges;可写数据限定在具名卷或tmpfs。 - ClickHouse 使用专用
fluxgate账号;密码只通过调用方环境传入。生产环境应改用受管 secret、最小权限分离与轮换机制。 - 当前 Compose 不包含 TLS、终端用户认证、WAF 或租户鉴权;
18080默认仅绑定 loopback。如需对外暴露,必须先补齐 TLS、认证与边界防护。 - 本地契约保留调用方提供的
x-request-id,以便跨响应、后端日志和 ClickHouse 事件关联。它不是身份凭据或可信安全属性;互联网入口必须限制长度/字符集,并依据已认证调用方选择接受、重写或拒绝,不能用它单独承担授权或业务幂等。 - analytics 只执行预定义 SQL;route、status 和时间窗都受验证,不提供任意 SQL 代理。
docker compose down --volumes会删除事件、access log 和 collector checkpoint;执行前确认不需要保留本地证据。
日常 smoke 针对已经启动的栈:
python scripts/smoke.py最终完整 acceptance 的权威命令是:
python scripts/acceptance.py --clean --requests 1000 --concurrency 32--clean 会使用独立 Compose project,从该项目空卷开始并执行无缓存构建;随后依次检查环境、固定源码构建与单测、readiness、限流、双后端路由、503 一次重试、ClickHouse 原始行与聚合、三个 analytics API、真实 checkpoint 回退重放与 FINAL 去重、ClickHouse 停机恢复、固定请求数负载,以及再次从空卷启动的 README smoke。脚本不会为抢占端口而杀死其他进程;18080、19901 或验收用 18123 被占用时会明确失败。
每次运行默认在 artifacts/acceptance/public/<run-id>/ 生成带时间戳的响应、日志、查询结果、环境事实和 SHA-256 证据索引。只有当 FluxGate 是独立 Git 仓库、运行前后 HEAD 不变、工作区干净、必需阶段全部通过,且 evidence_privacy.passed=true 时,manifest.json 才会标记 authoritative=true。
本仓库只放行上述精确 run ID;其 189 个索引证据文件的字节数与 SHA-256 已重新校验,evidence_privacy.passed=true。其他新生成或历史本地证据默认被 .gitignore 排除;只能在审核 HEAD、dirty 状态、隐私门禁和文件哈希后精确放行新 run。逐项结果与限制见 docs/ACCEPTANCE.md。
FluxGate v1 是单 Envoy、单 ClickHouse 节点的本地产品栈,不包含 xDS 控制面、分布式全局限流、多节点数据库、跨地域容灾、Web 大屏、生产发布或 exactly-once。本地验收结论不外推到生产环境。
FluxGate 自研源码采用 Apache License 2.0。运行和构建依赖仍由各自权利人拥有并适用各自许可证;固定源码/镜像、NOTICE 和基础库归属见 THIRD_PARTY_NOTICES.md,源码、许可证和父仓关系的固定证据见 docs/UPSTREAM_AUDIT.md。
核心上游职责仅作归属说明:Envoy 承担代理数据面,Apache bRPC 承担 C++ 服务与客户端框架,ClickHouse 承担分析存储。FluxGate 不复制三个上游完整源码树,不把上游实现或指标主张为本项目原创。