docs: 联网搜索服务架构方案全套(plan-final/design-arch/选型决策/整合导览/MCP文档/部署预设/联调手册) bench: 5 方案 + 代理 + 站点矩阵本机实测工程(无密钥) 部署目标:primary mgr1 先行测试(待批准后执行)
461 lines
33 KiB
Markdown
461 lines
33 KiB
Markdown
---
|
||
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 容灾位) |
|