onesvm-browser-server/docs/design-arch-20260901.md
chii 983259836d chore: init workspace with onesvm-dev-md + casa-commander
docs: 联网搜索服务架构方案全套(plan-final/design-arch/选型决策/整合导览/MCP文档/部署预设/联调手册)
bench: 5 方案 + 代理 + 站点矩阵本机实测工程(无密钥)
部署目标:primary mgr1 先行测试(待批准后执行)
2026-09-01 15:19:52 +08:00

33 KiB
Raw Blame History

type status created step
arch active 2026-09-01 S4 / plan-20260901-01 / run-20260901-browser-arch

onesvm-browser-server 架构设计(任务 1–4)

时间口径:Asia/Shanghai(UTC+8)。本文覆盖用户任务 1–4(拓扑、统一出口、拓展坞协议、排队/代理/集群规格); 任务 5(实测评定与最终入选结论)由 S5 整合,本文只留占位。 设计输入:.dsh/artifacts/run-20260901-browser-arch/research/01–05、proxy-probe.md、input-candidate-tables.md; 组织规范:onesvm-dev-md/base/development-standards.md(§5 urlapi、§时间东八区)、deployment-rules.md(镜像传输 / Swarm 惯例)、service-secret-protocol(认证头分轨)。 标注约定:「估算·待实测校准」= 数字来自二手资料或线性外推,S3/S5 压测后回填。

0. 硬约束(来自 input-candidate-tables.md 附加约束)

约束 值 出处
部署目标 dev-swarm 内网 3 节点(192.168.1.61/.62/.63,各 ~6.5GB RAM) 附约束 + inventory
本项目全栈内存 ≤1GB,渐进递增 附约束
会话峰值 60 = 队列深度 + 在途会话合计(非 60 并发浏览器) research/03 §1
中间件 能不加 Redis 就不加 → 本架构零 Redis research/03 §5
出口形态 MCP Server 为主 + HTTP API 兜底(urlapi 惯例) 附约束 + research/01 §4
浏览器 无头轻量优先;有头仅抽象插拔预留 附约束 + research/04 §1
代理池事实 SUB-1 共 69 真实节点,52 vless 全部探活可用,17 hysteria2 不可用 proxy-probe.md §2

1. 总体拓扑

flowchart LR
    subgraph Consumers[智能体消费者]
        V[Vlepontas]
        E[EAI]
        O[其它注册主体]
    end

    subgraph GW[统一出口网关 gateway · 无状态 ×N]
        MCP[MCP Server<br/>POST /mcp<br/>Streamable HTTP(2026 无状态目标<br/>2025 兼容腿挂 O9 待决)]
        API[HTTP API 兜底<br/>/bs-api/v1/* · urlapi]
        AUTH[Auth 内核<br/>key 校验/配额/限流]
        POL[出站策略引擎<br/>SSRF/域名/robots]
        MCP & API --> AUTH --> POL
    end

    subgraph SCHED[排队/调度 scheduler · replicas=1]
        Q[SQLite WAL 任务队列<br/>+ 内存快通道]
        RT[能力路由器<br/>direct/proxy/js-render 标签]
        BP[背压 /pressure<br/>CONCURRENT+QUEUED=60]
        Q --> RT --> BP
    end

    subgraph DOCK[拓展坞 worker 集群 Dock Protocol]
        A1[方案A 适配器<br/>无浏览器: SearXNG / HTTP+fit]
        A2[方案B 适配器<br/>轻渲染: Lightpanda / Bouncy]
        A3[方案C 适配器<br/>保真: chrome-headless-shell]
        A4[(有头/完整 Chromium<br/>插拔预留位 · 默认不部署)]
    end

    subgraph PM[ProxyManager · replicas=1]
        SUB[订阅解析 Clash YAML<br/>多订阅容灾]
        HC[健康检查/轮换<br/>vless 热池]
        RT2[域名路由<br/>国内直连 / 国外代理 / deny]
        SUB --> HC --> RT2
    end

    V & E & O --> GW --> SCHED --> DOCK
    A2 & A3 -->|需境外出口| PM --> OUT((境外目标站))
    A1 -->|国内域| DIRECT((国内直连))
    A1 -->|境外搜索| PM

要点(追溯):

  • 网关无状态多副本,调度器与 ProxyManager 单副本(replicas=1,Swarm 约束放置)——SQLite 单写者原则(research/03 §2.1)。
  • MCP 与 HTTP 共用同一 Auth → Policy → Queue → Engine 内核(research/01 §4.2);不为 MCP 引入会话存储,MCP 2026 无状态 POST 为目标(research/01 §4.1)。
  • A/B/C 三类方案适配器对应 research/04 §4 的分层内核策略;有头档只占接口位。
  • 会话不迁移:已建立的 CDP/WS 钉在原 worker 节点(research/03 §4)。

2. 统一出口:注册 / Key / 配额 / 双形态

2.1 消费者与凭证模型

映射 one-api/new-api 的「主体 + 可吊销 key + 预扣配额」模型(research/01 §2.5),字段按 Firecrawl「智能体锁定」+ Tavily「dev/prod 档位」裁剪(research/01 §5.2):

实体 关键字段(伪签名)
Consumer id, name, contact?, status, created_at(注册主体,如 vlepontas、eai)
ApiKey id, consumer_id, prefix, hash, name, scopes[], formats[], rpm, daily_quota, monthly_quota, concurrent_sessions, status∈{enabled,disabled,expired,exhausted}, expires_at
QuotaLedger key_id, window(day/month), used, reserved(预扣 → 执行 → 结算)

规则:

  • 明文 key 只在创建时回传一次;存储 prefix(展示)+ hash(校验,SHA-256+salt);比对用 hmac.compare_digest。
  • 默认 scope:search, read;extract / browse / screenshot / rawHtml 需显式打开(Firecrawl Key Restrictions 思路,research/01 §2.3)。普通 key 不得破墙、不得无限 crawl(research/05 §5.5)。
  • 吊销即时生效:status=disabled 拒绝;无 Redis 时接受 ≤5–10s 进程内缓存窗口,用短 TTL 收敛(research/01 §5.2)。
  • 账户至少保留一把有效 key(防自锁,Firecrawl 惯例)。

2.2 认证头(遵循全局 service-secret-protocol)

面 头 说明
静态服务 key(唯一对外凭证形态) X-Service-Token: bs_<random> 服务对服务 / 智能体长期凭据;🔴 禁 Authorization: Bearer 塞静态 secret
JWT(将来若引入用户态) Authorization: Bearer <jwt> 仅携带用户身份时使用,与静态 key 分轨不混用
  • 密钥经 ${BROWSER_SERVER_KEYS_SEED:?required} 类变量注入,fail-closed;禁默认值、禁入 git。
  • 管理面(签发/吊销 key 的 admin 接口)仅监听 overlay 内网 + 独立 admin token,不对消费者暴露。

2.3 限流与配额(两层)

  1. 速率:每 key 进程内令牌桶(rpm),超限 429 + Retry-After + X-RateLimit-Limit/Remaining/Reset(Brave 惯例,research/01 §2.2)。
  2. 额度:日/月计数写 SQLite,预扣防超卖;耗尽 402(Firecrawl 错误码分层,research/01 §2.3)。
  3. 并发:浏览器会话单独计数(concurrent_sessions),与搜索 QPS 分开;超额进入队列而非立即失败。
  4. 自定义头 X-Session-Remaining 提示剩余会话额度,让智能体自行退避(research/01 §3)。

2.4 MCP 为主 + HTTP 兜底(同一内核)

面 路径 / 形态 说明
MCP(主) POST /mcp,Streamable HTTP 以 2026-07-28 无状态规范为目标;2025 兼容腿待 O9 决策(倾向首版不带,出现旧客户端再加,单 handler 双世代分流即可,research/01 §4.1);不开 SSE 旧路径
HTTP(兜底) /v1/search /v1/read(/v1/extract 特权) urlapi 惯例:独立端口 + 服务名前缀路径
  • 工具名与 REST 路径 1:1;MCP tool isError 携带与 HTTP 一致的 code(401/402/429/503/denied/timeout…,见 §3.4),消费者一套重试策略通吃。
  • HTTP 侧 urlapi 落地(development-standards §5 + 组织 WSG 先例 /search-api):网关 HTTP 监听独立端口 :8640(S6 修订:原设计值 :8300 经 inventory 实证已被 voc-analysis-dev 占用,:8640 无冲突记录;最终确认见 plan-final D3);经反代暴露时用 /bs-api 前缀(browser-server 缩写)strip 后转发,与 /search-api 先例一致;后端内部调用直连 overlay。
  • 上游(SearXNG / 浏览器内核 / 代理)全部内网,只认网关服务账号;消费者永远只见到本项目 key(research/01 §5.5)。

2.5 网关进程预算

Go/Rust 单二进制 + SQLite WAL,空载目标 <32MB;鉴权 + SQLite mmap 合计 20–70MB(估算·待实测校准,research/01 §5 尾)。瓶颈在浏览器进程,不在 key 网关。

3. 拓展坞协议(Dock Protocol)

目标:任意「方案」(A 无浏览器 / B 轻渲染 / C 保真浏览器,单点或集群形态)经统一适配器接入,产出统一 AI 原生 Schema,内部引擎细节不外泄(research/02 §5.1)。

3.1 统一输入:两类意图

意图 输入(伪签名) 说明
Search {query, max_results=5(≤20), time_range?, safesearch?, lang?} 发现 URL;不返回全文
Read {url, formats=["markdown"], max_chars=20000, extract?{schema, prompt?}} 单页精读;fit markdown 为主

browse(多步交互)作为第三类特权意图在协议中预留,默认关闭(research/01 §4.3)。

3.2 方案适配器接口(伪签名)

interface DockAdapter {
  init(config: AdapterConfig) -> Result<()>          // 拉进程/连远端/预热 HTTP 客户端
  health() -> Health { ok, rss_bytes, startup_ms,    // 必须上报内存与启动耗时
                       slots_free, details }         //   (research/04 §4.6 强制项)
  execute(job: JobEnvelope) -> Result<RawResult>     // 原生结构不出适配器
  teardown(grace: Duration) -> Result<()>            // 停新活、归还槽位、杀进程回收
  capabilities() -> Caps {
      intents: [search, read],                        // 支持的意图
      render: none | light | full,                    // JS 能力分级
      regions: [domestic, overseas],                  // 出口需求
      max_concurrent: int,                            // 自身并发上限
      proxy_required: bool,                           // 是否必须走 ProxyManager
      formats: [markdown, links, ...],                // 可产出格式
  }
}
  • 输入形式转换:适配器负责把统一意图译成引擎原生调用(SearXNG format=json、CDP navigate、Lightpanda CDP 等);引擎的 engines/score、DOM、AXTree 留在适配器内。
  • 输出整合(S7 P1-AR1 锁定归属):适配器只产出 RawResult(原生结构不出适配器);统一信封封装、Size/Type guard、fit markdown、安全扫描/PII redact/注入包裹全部收敛在 Dock worker 共享模版层(= 整合器)——词表/规则单源版本化,N 个适配器零合规代码,防门闩矩阵漂移;score 归一化到 0–1 也在模版层完成(SearXNG 位次分与语义分不可混用,research/02 §2.5)。
  • 回收约定:execute 必须 defer/finally 归还槽位;适配器自报 recycle_every_pages / recycle_every_minutes,由调度器按 N 页 / T 分钟硬重启进程(research/03 §3.4、research/04 §3.3)。

3.3 统一 AI 原生输出 Schema(v0,基于 research/02 §4 细化)

统一信封(三类 kind 共用顶层):

Envelope {
  ok: bool,
  kind: "search" | "read" | "extract",
  request_id: ulid,
  took_ms: int,
  usage: { credits: int, engine: string, tokens_estimate: int },  // ≈ceil(chars/4) 软提示
  provenance: { url?, final_url?, retrieved_at: "+08:00",        // 一等溯源字段
                adapter: string, proxy_exit: "direct"|"pool:<name>"|"none",
                cached: bool },
  error: null | { code, message, retry_after_s? },
}

kind=search:

{ query, answer: null,                       // 1GB 集群默认不做 LLM answer
  results: [{ id, title, url,
              content,                        // ≤800 字符 query 相关片段,非全文
              score: 0..1, published_at?, engine, favicon? }] }   // 空数组不给 null

kind=read:

{ url, final_url, title, description,
  markdown,                                   // fit 后正文(Pruning/BM25 过滤),默认唯一内容字段
  truncated: bool, char_count: int,           // max_chars 截断必须显式 truncated=true
  metadata: { status_code, content_type, language, retrieved_at },
  links?, images?, html?, screenshot_url?,    // 仅 formats 点名时非 null;截图只给可过期 URL 禁 base64
  extracted?: object | null }                 // schema 抽取旁路;失败=null + warnings,不吞正文

kind=extract(特权):{ extracted, sources: [{url, used}] };内部优先 JSON-LD/OpenGraph/CSS 零 LLM 路径,LLM 抽取走外部模型 API、默认关闭(research/02 §4.3、§5.5)。

Token 友好默认(research/02 §3.7):搜索 max_results=5 只给 title/url/content/score;读取默认 formats=["markdown"];禁止默认 rawHtml/整页截图(省 token 也省 1GB 瞬时缓冲)。

3.4 错误码(与网关 HTTP 对齐)

blocked | denied | timeout | quota | rate_limited | extract_failed | upstream。 合规拦截用 denied 并写审计,不假装抓取成功(research/02 §4.4、research/05 §4.5)。

3.5 A/B/C 方案接入流程示例

sequenceDiagram
    participant C as 消费者(智能体)
    participant G as 网关(MCP/HTTP)
    participant S as 调度器(队列/路由)
    participant W as Dock Worker(适配器)
    participant P as ProxyManager
    C->>G: search(query) / read(url)
    G->>G: Auth(key)→配额预扣→出站策略(SSRF/域名/robots)
    G->>S: JobEnvelope{intent, caps需求, key约束}
    S->>S: 能力路由: 国内直连? 需JS? 需代理?
    alt 方案A 无浏览器
        S->>W: A适配器 execute (SearXNG / HTTP+fit)
    else 方案B 轻渲染
        S->>W: B适配器 execute (Lightpanda/Bouncy, 槽位≥1)
    else 方案C 保真
        S->>W: C适配器 execute (chrome-headless-shell, CONCURRENT=1起)
    end
    W->>P: (如需) acquire_for_domain → sticky 出口
    W->>W: fetch/render → RawResult(适配器内)
    W->>W: 模版层整合: Size/Type guard → fit markdown → 安全扫描+PII redact+注入包裹 → Schema 信封(S7 P1-AR1)
    W-->>C: Envelope(ok / error.code)

降级链:C 槽满 → 尝试 B;B 不可用/页面超重 → 回 A 的 HTTP+fit;全部不可行 → error.code=blocked/upstream,不静默降级数据质量(打 warnings)。

4. 排队与调度(无 Redis)

4.1 为什么不要 Redis(research/03 §5 结论固化)

  • 60 深度任务量远低于 SQLite WAL 舒适区;one-api 官方亦言「DB 延迟低时开 Redis 反引入缓存滞后」(research/01 §2.5)。
  • Redis/ BullMQ 白占 30–80MB+,直接威胁 1GB 预算;NATS Core 不可靠、JetStream 是「另一个中间件」;PG SKIP LOCKED 需要现成 PG(本集群没有为此单拉)。playwright-distributed 把 Redis 写成硬依赖,排除。
  • 结论:队列 = SQLite WAL 任务表(单写者),热路径 = 进程内 bounded queue 快通道。

4.2 队列结构

  • 持久层:scheduler 本地 SQLite(WAL + busy_timeout,禁放跨节点共享卷),任务行含 id, priority, status, payload(JSON), available_at, lease_until, attempts, worker, created_at;原子抢单用 UPDATE ... WHERE id=(SELECT ... LIMIT 1) RETURNING(SQLite 3.35+,research/03 §2.1)。
  • 快通道与入队路径(S6 P1-2 修订):gateway 无状态,不写 SQLite;入站校验后须经 overlay 内网 POST /enqueue 交 scheduler;scheduler(SQLite 唯一写者)进程内 bounded queue 快通道接收 + 立即落 SQLite(WAL),落盘成功才 ACK 排队位(经 gateway 转告消费者)。scheduler 不可达 → gateway 503 + Retry-After,fail-closed 不在 gateway 本地落盘。
  • 必备机制:租约 lease_until、超时收割(reaper 协程)、指数退避重试(attempts≤2,仅对 timeout/upstream 类瞬时错误;denied/quota 不重试)、死信表。
  • 集群形态:scheduler replicas=1(Swarm placement 固定节点 + 本地卷);网关多副本无状态。不做会话迁移——已建立的浏览器会话钉在原 worker(research/03 §4)。

4.3 背压与 60 接纳上限(Browserless 三层模型缩表,research/03 §3.1/§4)

admit? --pressure 超阈--> 503/429 + Retry-After(只拒新,不杀旧)
  └→ queued(QUEUED 槽,FIFO)
       └→ running(CONCURRENT 槽,≪60)
            └→ release/timeout/fail → 归还槽 → 拉队列
旋钮 设计值 说明
ADMIT_MAX(running+queued 合计) 60 公平性/反滥用上限,非吞吐目标(research/03 §4)
CONCURRENT(浏览器槽) 1 起步 chrome-headless-shell;压测稳定后再试 2(research/04 §4.2)
QUEUED 59 − 非浏览器快任务保留位 搜索/HTTP 读走独立高并发通道,不进浏览器槽
HEALTH 内存阈 60–70% 比 Browserless 建议 80 更保守——预算本身就紧(research/03 §3.1)
超时 read 15–30s;硬顶 120s;idle 10s research/03 §5.4
错峰 浏览器 launch 间隔 5–10s 启动是 CPU 峰值,防 HEALTH 误杀

背压信号(全进程内/SQLite,无需 Redis):队列深度、平均等待、近期 429 数、worker RSS/cgroup memory.current 百分比、浏览器进程数。429/503 响应必带 Retry-After 与 running/queued 现状(research/03 §5.6)。

4.4 能力路由(标签制)

JobEnvelope 按 capabilities() 匹配 worker 标签:

标签 路由目标 典型流量
region=domestic 直连,禁走境外代理 国内站、*.onesvm.com
region=overseas 经 ProxyManager google/bing/境外站
render=none A 类适配器(SearXNG / HTTP+fit) ~90% 流量(附表 A 原作者建议)
render=light B 类(Lightpanda/Bouncy) 需 JS 但非强对抗
render=full C 类(chrome-headless-shell,单实例排队复用) 保真/强对抗,~10%

90% 流量不准入浏览器队列(research/03 §5.3):搜索走 SearXNG、读取走 HTTP+可读性抽取;只有 JS 必需页占 CONCURRENT 槽,否则浏览器槽前饿死。

4.5 优雅降级

  1. 浏览器槽满 → 排队至 TIMEOUT,超时返回 timeout + retry_after_s,不硬撑。
  2. 某适配器 health().ok=false → 从路由表摘除,任务按降级链回落(§3.5)。
  3. 代理池熔断 → 回队或降级直连(仅限合规允许的域),不换未知出口硬试(research/05 §3.5)。
  4. 内存超阈 → 503 拒新 + 杀 worker 不杀网关(cgroup 分容器限额,research/04 §4.4)。
  5. SQLite 不可写(磁盘满)→ 只读降级:快通道拒新任务,在途跑完,告警。
  6. 渲染互斥执行规则(S7 P2-AR2):shell 按需槽存活期间,lightpanda 停接新任务(scheduler 粗粒度规则:shell_active ⇒ panda 停新,入队侧执行,无需预知页面重量);shell 回收后自动恢复。

5. ProxyManager

单副本服务(replicas=1,与 scheduler 同节点或独立放置),出口以 mihomo(Clash.Meta 内核)为数据面,本模块为控制面。

5.1 订阅解析(proxy-probe.md §6.1)

  • 源:${PROXY_SUB_URL:?required} / PROXY_SUB_URLS(多订阅容灾;SUB-2 未提供,容灾位先留空)。订阅 URL 只进环境变量,不入库不入 git。
  • 格式探测流水线:Clash/Mihomo YAML(flow-style 与块式均支持)→ Base64 订阅 → URI 清单;实测 SUB-1 为 Clash YAML(517KB / 69 真实节点,proxy-probe §1)。
  • 剔除占位节点(名称匹配 剩余流量|套餐到期);解析 subscription-userinfo 余量头。
  • 安全边界:capped read(体积极限防 OOM)、超时、仅 http/https 拉取、自定义 UA;TTL 缓存(设计值 10min),失败 stale-on-error(research/05 §3.1)。
  • 全量节点落 SQLite;内存只持健康热池(设计值每区域 ≤20 个 endpoint,research/05 §5.3)。

5.2 节点池与健康检查(proxy-probe.md §6.2–6.4 实测结论固化)

项 设计
默认池 仅 vless(实测 52/52 探活成功);hysteria2 进 udp_optional 池,容器 UDP 通路未验证前不调度
探针 https://www.google.com/generate_204 HTTPS GET;禁用机场自带 http://cp.cloudflare.com/generate_204 HEAD
频率 活跃出口 30s/次,失败立即切热备;全池 5min/轮(69 节点 6 并发 8s 超时 ≈55s,实测)
摘挂 连续 2 次失败摘除;成功 1 次回候选;EWMA 延迟按域名分维护(不用单一 delay 代表所有目标)
轮换 P2C(低开销近似最优,research/05 §3.3);主用圣何塞 02/03/04(167–168ms 实测最优),次用东京 06/07(280ms)防美国入口集体抖动;同 server 前缀节点不同时作主备
粘滞 同一 session_id/目标域名在租约内钉死一条出口(进程内 TTL map,会话本不迁移);0.01 倍「下载」节点限速风险,搜索流量优先「三网推荐」

5.3 按目标域名路由(规则表)

SQLite 规则表 match_type=suffix|glob, action=direct|pool:<name>|deny,热路径编译内存 trie,变更重载(research/05 §3.4)。默认顺序(由具体到一般):

  1. 国内域 / *.onesvm.com / .cn 备案站 → DIRECT(省钱、少出境、少风险)。
  2. 黑名单域 → deny,不分配代理。
  3. 已知高对抗域(如 reddit——实测 curl 403 但链路通)→ 标记 render=full 交浏览器 worker,仍走美国 vless。
  4. 默认境外 → vless 数据中心池(主圣何塞、备东京)。
  5. 地区启发式仅作参考:节点名国旗 ≠ 目标站 RTT(proxy-probe §2 警示),不做死地理路由。

5.4 合规双向保险(research/05 §1/§4/§5 工程化)

生产侧——出口拦截,统一策略包单源(词表/规则版本化配置包,wordlist_version 入审计,支持热重载——实现期补,S7 P3-AR1)、两个执行点、流水线位置:Auth → Policy → Queue → Fetch → Size/Type guard → fit markdown → Safety scan → Schema(拦截在 Schema 之前,MCP/HTTP 共用一次):请求侧 Auth/Policy 在 gateway(入队前);Fetch 之后的 Size/Type guard / fit / Safety scan / 封装全部在 Dock worker 共享模版层(S7 P1-AR1 锁定):

道 内容 默认姿态
请求侧(必开) SSRF:禁 file:///localhost/RFC1918/链路本地/云 metadata,每次重定向重验;仅 80/443;默认仅 https fail-closed
域名策略 blocklist 优先(敏感路径、网盘、邮箱登录页等);robots.txt 按 UA 缓存 SQLite TTL 24h,Disallow 对普通 key 拒绝 普通 key 遵守 robots;特权 key 覆盖须审计打标。仅放行白名单域的默认姿态为可选更严档(政企交付场景),默认先 blocklist,见 §8 开放问题
体积/类型 max_download_bytes;Content-Type 白名单(html/xml/json/pdf/text) 超限 blocked
频率 每 host 最小间隔,独立于全局限流 60 会话封顶本身也是合规阀(research/05 §2.4)
响应侧 关键词分类词表(对齐《生成式 AI 办法》第 4 条内容清单),fit_markdown 与 title/description 分别扫;NFKC+同形字归一,前 64KB 解码扫描;命中高危 block,PII(身份证/手机/银行卡正则)默认 redact 规则在前、模型在后;不上本地分类大模型(research/05 §1)
注入防护 正文包明确 delimiter + 四阶段注入扫描,防页面「ignore previous instructions」 检索增强标配(research/05 §4.3)
审计 consumer_id, url, rule_id, ts(+08:00) 落 SQLite 对应办法第 14 条「能停能报」最低限度

消费侧——MCP 文档使用规则:MCP Server 的 server instructions / tool description 中固化「数据获取边界」——智能体不得将本服务用于:非法采集、绕过登录/支付墙/验证码(默认能力不含)、整站搬迁式 crawl、汇总公开个人信息成档案再分发、把含境内个人信息的 query 发往境外引擎。产品协议同步写明(对应办法第 9 条服务协议、个保法第 27 条拒绝处理渠道,research/05 §5.7)。

法律要点摘要(research/05 §2,非法律意见,上线前法务复核):

  • 主路径(境内集群拉境外公开网页回境内消费者)不是「数据出境」(《跨境流动规定》第 4 条精神);但一旦走境外托管 SaaS(Tavily/Firecrawl Cloud/Jina),query 即出境——默认「境内闭环」,境外 SaaS 仅特权降级且剥离联系方式(research/05 §5.1)。
  • 公开个人信息 ≠ 任意处理(个保法 §13(6)/§27):合理范围、个人拒绝除外、重大影响须同意;敏感个人信息即使出现在网页也应脱敏/丢弃。
  • 自动化收集公开数据边界(国家数据局 2026-04 解读):禁止非法侵入与干扰、保护技术措施、限定爬取内容、防止实质性替代 → 对应:遵守 robots、频率上限、不破墙为默认、不开无限 crawl、记录来源 URL。

5.5 内存预算

词表+正则 5–15MB;代理热池 2–5MB;mihomo 本体小(proxy-probe §6.6:瓶颈在 worker 并发而非代理进程);robots 缓存走 SQLite 不进堆(research/05 §5.6,均为估算·待实测校准)。

6. 集群规格(dev-swarm 3 节点,≤1GB 渐进)

6.1 节点布局(设计值)

节点 放置 说明
mgr1(.51) scheduler(replicas=1,本地卷)+ ProxyManager(replicas=1)+ gateway ×1 控制面集中,SQLite 单写者
mgr2(.52) searxng-cn + trafilatura + lightpanda 国内组 + 轻渲染主力(~90% 流量)
mgr3(.53) searxng-global + shell 按需槽 + gateway ×1 国外搜索 + 保真按需 + 网关冗余

【2026-09-01 用户更正】目标环境为 primary 生产 Swarm(mgr1–mgr3),测试期全部署于 mgr1 单节点;上表为扩展期布局。原 dev-swarm .61/.62/.63 布局作废(含此前 S6 修订版)。部署执行细节以 deploy-prod-preset-20260901.md 为准。

  • placement.constraints 固定有状态服务(scheduler 本地卷);网关可漂移(deployment-rules §3 惯例)。
  • 每组件 cgroup 内存上限为防暴走天花板,允许超配(上限合计可超档位常驻预算;S6 P1-1 修订);重页峰值由渲染互斥调度保证不同时触顶;超限杀 worker 不杀网关(research/04 §4.4)。

6.2 渐进档位表

【档位数字已被 plan-final-20260901.md §2.3 实测校准版取代 · 20260901 S6】本节保留设计推理与有头档抽象;记账口径以 plan-final §2.3「档标=常驻业务峰、cgroup=防暴走天花板允许超配」为准。

档 总预算 组件与副本 并发能力(估算)
L0 起步 ~256MB 256MB gateway ×1(64MB) + scheduler+PM(64MB) + SearXNG(80MB) + HTTP/fit(48MB) 60 路纯搜索/HTTP 读可行(research/03 §4);0 渲染
L1 标准 ~512MB 512MB L0 + Lightpanda(≤150MB) + gateway 升 ×2 + 十几路轻渲染(官方基准 25 进程 123MB,须本 Swarm 复现后信,research/04 §2.3)
L2 满配 1GB 1024MB L1 + chrome-headless-shell ×1(≤400MB 含 shm 64–128MB) + Bouncy 备选(≤100MB) 浏览器 CONCURRENT=1(官方容量表缩放,research/04 §2.2);轻渲染数十路;队列接纳恒 60
有头档 另议 完整 Chromium / Camoufox / 有头(xvfb) 仅抽象说明:经 Dock Protocol render=full+headful 能力位插拔接入,默认镜像不装、不占预算(附约束 + research/04 §4.1)

容量纪律:60 = 队列接纳,不是浏览器并发;「10 并发/GB」口碑与官方 4GB/5–10 会话表冲突,按官方表缩到 1GB ≈ 1–2 Chrome 会话(research/03 §4、research/04 §3.4)。浏览器 launch 错峰 5–10s;shm 64–128MB(与 Browserless 官方 2g 建议矛盾,接受大页崩溃风险换预算)。

6.3 实测校准占位(S5 填数)

  • chrome-headless-shell 开 example.com / 中文新闻页 / SPA 的 RSS(research/04 §4.5 三组)
  • Lightpanda 官方 demo 脚本在本 Swarm 镜像内复现(smem PSS 口径)
  • Bouncy 官方 hyperfine 复现 + 维护风险评估
  • 网关空载 RSS / SQLite WAL 持续写入表现
  • L0/L1/L2 各档实测总占用与可承并发

7. 可观测与运维

7.1 健康检查

组件 探针
gateway GET /healthz(liveness)+ /readyz(SQLite 可写 + 下游 scheduler 可达)
scheduler /pressure 等价接口:{cpu, memory_pct, running, queued, recently_rejected, is_available, reason∈{full,cpu,memory}}(抄 Browserless,research/03 §3.1)
dock-worker health():ok / rss_bytes / startup_ms / slots_free(协议强制,§3.2)
ProxyManager 热池存活数、活跃出口、最近切换时间

Swarm healthcheck + restart_policy: on-failure + resources.limits.memory 按档位表(deployment-rules §1 模板)。

7.2 指标(进程内暴露 /metrics,文本格式即可,无 Redis 无新中间件)

  • 队列:queue_depth、queue_wait_seconds_avg、admitted_total、rejected_total{reason}
  • 会话:sessions_running{adapter}、session_duration_seconds、recycles_total
  • 内存:cgroup_memory_current / limit 比、各 worker rss_bytes
  • 代理:proxy_pool_alive{pool}、proxy_delay_ewma_ms{node}、proxy_switches_total
  • 合规:denied_total{rule_id}、redact_total、审计表行数

7.3 日志

  • 结构化 JSON 行(ts 带 +08:00),request_id 贯穿 gateway→scheduler→worker→proxy 四跳。
  • 禁记:key 明文、订阅 URL、节点凭据、query 原文中的个人信息(合规最小必要,research/05 §2.2);query 只记 hash + 长度。
  • 审计日志(denied/redact)单独保留,留存期随产品协议定(§8 开放问题)。

7.4 部署与镜像(deployment-rules §2 + docker-image-transfer 惯例)

  • dev-swarm 节点禁止直接 pull/build;Mac 构建 --platform linux/amd64(或 .51 用 DaoCloud mirror)→ docker save | ssh root@.61/.62/.63 'docker load'(每个目标节点都要 load)→ docker stack deploy --resolve-image never。
  • 日常迭代复用 :dev tag 覆盖回收旧层;一次性调试 tag 验证后及时删;docker image prune -f(仅 dangling)可放脚本尾。
  • 版本锁定、禁 :latest;密钥走 docker secret / ${VAR:?required},.env.example 入库。
  • stack 入 Git stacks/browser-server.yml;新端口(:8640,D3 确认后)同步 inventory;有状态卷(scheduler SQLite)固定节点 + 备份方案(NAS sidecar 惯例,deployment-rules §7–8 checklist)。

8. 开放问题(留给用户决策 / S5 实测回填)

# 问题 默认倾向 决策人
O1 域名策略默认姿态:blocklist 优先 vs 仅放行白名单域(更严,适合政企交付但维护重) blocklist + 高危类 deny 用户
O2 境外托管 SaaS(Tavily/Firecrawl Cloud/Jina)是否作为特权降级通道启用 默认境内闭环,不启用 用户+法务
O3 SUB-2 容灾订阅是否提供;单订阅失效时是否允许降级直连境外(多数境外目标直连不可达,倾向回队+告警) 回队+告警 用户
O4 服务是否构成《生成式 AI 办法》「向公众提供」(涉及内容生产者责任与备案义务) research/05 §2.3 未判定 法务
O5 审计日志与配额账本的留存期限 90 天(占位) 用户
O6 HTTP 独立端口与前缀定名 提议 :8640 + /bs-api(:8300 被 voc-analysis-dev 占用已实证;:8640 无冲突记录) 用户(plan-final D3)
O7 1GB 不够时的扩容提议 【已被 plan-final §2.5 + D1 取代 · 20260901 S7】1GB 可维持(占用率 21%/3%/5%);L3 1.5GB 增量价值已给可决策数字 用户(D1)
O8 Bouncy 是否入选 B 类第二引擎(更轻但个人仓库、维护风险未验证) Lightpanda 优先,Bouncy 备选 S5
O9 MCP 2025 兼容腿是否在首个版本就带(还是仅 2026 无状态) 先仅 2026,出现旧客户端再加 用户
O10 管理面(key 签发/吊销)形态:CLI 先行 vs 简易 HTTP admin CLI + SQL 先行 用户
O11 scheduler SQLite 卷的备份频率 / RTO 目标 / 恢复演练(deployment-rules §有状态服务须设计备份并验证可恢复) NAS sidecar 日备 + 季度恢复演练(占位) 用户
O12 bench 数字为 darwin arm64 实测,生产 amd64(Chromium 系内存/启动通常更高)→ 首部署后同架构复测校准 L 档 部署后首轮复测再锁档标 实施轮
O13 镜像版本号/tag 约定(development-standards §6:新项目首部署前须建立) 首部署前按 §6 AskQuestion 定约定 用户

附录:可追溯性

本文章节 主要依据
§1 拓扑 research/01 §4.3、research/03 §4、research/04 §4.1
§2 统一出口 research/01 全文(§2 各家模型、§4 双形态、§5 建议);development-standards §5 urlapi;service-secret-protocol 头分轨
§3 Dock 协议 research/02 全文(§4 Schema 草案、§5 建议);research/04 §4.6(rss/startup 上报);input-candidate-tables 附表 A/B 候选池
§4 排队调度 research/03 全文(§2 SQLite WAL、§3 Browserless 模型、§4 可行性、§5 建议)
§5 ProxyManager proxy-probe.md 全文(52 vless 事实 + §6 建议);research/05 §3(订阅/健康/轮换/路由)、§2(法律)、§4(拦截)
§6 集群规格 research/04 全文(§2.5 保守区间、§3 Browserless 经验、§4 建议);research/03 §4 容量表
§7 可观测运维 research/03 §3.1(/pressure);deployment-rules §1/§2/§7/§8;docker-image-transfer 规则
§8 开放问题 research/05 §2.3(办法适用性)、§5.1(境内闭环);proxy-probe §6.1(SUB-2 容灾位)