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

461 lines
33 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
type: arch
status: active
created: 2026-09-01
step: 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. 总体拓扑
```mermaid
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 方案接入流程示例
```mermaid
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 容灾位) |