From 88570c9aab25704a114b26ef91104bc41f4c793c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E6=A9=98?= Date: Fri, 3 Apr 2026 03:57:41 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20Sigil=20Backend=20abstraction=20+=20LRU?= =?UTF-8?q?=20scheduling=20design=20=E2=80=94=20=E5=B0=8F=E6=A9=98=20?= =?UTF-8?q?=F0=9F=8D=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - SigilBackend interface: deploy/invoke/remove/list/inspect/status - WorkerPool (): LRU eviction, fixed hash subdomain, async evict - Platform (5): dispatch namespace, no LRU needed - Eviction priority: ephemeral > normal > persistent - Preheat strategy + async page-out optimization - Phase 1 MVP checklist --- docs/shared/sigil-backend-lru.md | 375 +++++++++++++++++++++++++++++++ mkdocs.yml | 1 + 2 files changed, 376 insertions(+) create mode 100644 docs/shared/sigil-backend-lru.md diff --git a/docs/shared/sigil-backend-lru.md b/docs/shared/sigil-backend-lru.md new file mode 100644 index 0000000..5bc2aa8 --- /dev/null +++ b/docs/shared/sigil-backend-lru.md @@ -0,0 +1,375 @@ +# Sigil Backend — 抽象接口与 LRU 调度 + +!!! abstract "一句话" + 一套接口,两种实现。$5 方案用 LRU 换页在 500 个 Worker 配额内调度无限能力;$25 方案用 dispatch namespace 直接常驻。用户按预算选择,Agent 代码不用改。 + +**作者**: 小橘 🍊(NEKO Team) +**日期**: 2026-04-03 +**前置阅读**: [Sigil 能力注册表](sigil-capability-registry.md) · [Uncaged 能力虚拟化](uncaged-capability-virtualization.md) + +## 为什么需要抽象 + +Sigil 的第一版设计直接选择了 Workers for Platforms($25/月)。但重新审视后发现: + +- **单用户场景**:每个 Sigil 实例只服务一个用户(主人)+ 几个 Agent +- **400 个能力绰绰有余**:$5 方案 500 配额 - 系统保留 ≈ 400+ 可用槽位 +- **$25 对个人用户偏贵**:WfP 的"无限 Worker"是为多租户 SaaS 设计的,我们用不完 + +所以正确的做法不是二选一,而是**抽象出统一接口,底层可切换**。 + +## 抽象接口 + +```typescript +/** + * Sigil Backend — 能力生命周期管理 + * 两种实现共享同一套接口,Agent 和 Sigil 网关不感知底层差异 + */ +interface SigilBackend { + /** 部署一个能力(新建或更新) */ + deploy(params: DeployParams): Promise + + /** 调用一个能力 */ + invoke(name: string, request: Request): Promise + + /** 移除一个能力 */ + remove(name: string): Promise + + /** 列出所有能力 */ + list(filter?: ListFilter): Promise + + /** 获取能力元数据 */ + inspect(name: string): Promise + + /** 获取后端状态(配额、LRU 信息等) */ + status(): Promise +} + +interface DeployParams { + agent: string // Agent 标识 + name: string | null // null = 自动生成临时名 + code: string // Worker 源码 + type: 'persistent' | 'normal' | 'ephemeral' + ttl?: number // 秒,仅 ephemeral + bindings?: string[] // 所需 bindings 声明 +} + +interface DeployResult { + capability: string // 完整名:xiaoju--ping + url: string // 调用 URL + expires_at?: string // 仅 ephemeral + cold_start: boolean // 是否触发了换页(仅 WorkerPool) + evicted?: string // 被淘汰的能力名(仅 WorkerPool) +} + +interface BackendStatus { + backend: 'worker-pool' | 'platform' + total_slots: number // 总槽位(WorkerPool: ~400, Platform: Infinity) + used_slots: number // 已用槽位 + agents: number // 注册 Agent 数 + lru_enabled: boolean // LRU 是否激活 + eviction_count: number // 累计淘汰次数 +} +``` + +## 两种实现 + +### WorkerPool($5/月)— 默认推荐 + +**原理**:每个能力是一个独立 CF Worker,Sigil 通过 CF API 管理部署/删除,用 LRU 策略在有限配额内调度。 + +``` +┌──────────────────────────────────────────────┐ +│ Sigil 网关 │ +│ sigil.shazhou.workers.dev │ +│ ┌────────┬────────┬────────┬─────────────┐ │ +│ │ 路由表 │ LRU 表 │ 鉴权 │ CF API 客户端│ │ +│ │ (KV) │ (KV) │ (KV) │ │ │ +│ └───┬────┴───┬────┴───┬───┴──────┬──────┘ │ +└──────┼────────┼────────┼──────────┼──────────┘ + │ │ │ │ + ▼ ▼ ▼ ▼ + ┌─────────┐ ┌─────────┐ CF API + │ s-xxx │ │ s-yyy │ (deploy/delete) + │ (热) │ │ (热) │ │ + └─────────┘ └─────────┘ ▼ + ┌─────────┐ + │ s-zzz │ + │ (换入) │ + └─────────┘ +``` + +**请求流(命中)**: +``` +请求 → Sigil → 查路由表 → 已部署 → 302 重定向到 s-xxx.shazhou.workers.dev + 或 subrequest 转发 +延迟:< 5ms(一次 KV 读 + 重定向) +``` + +**请求流(未命中 — 换页)**: +``` +请求 → Sigil → 查路由表 → 未部署 → 触发换入 + → 配额满?→ LRU 选出最冷 Worker → CF API 删除(换出) + → CF API 从 KV 拉代码 → 部署新 Worker(换入) + → 更新路由表 + LRU 表 + → 转发请求 +延迟:1-3s(CF API 部署时间) +``` + +### Platform($25/月)— 大规模方案 + +**原理**:使用 Workers for Platforms 的 dispatch namespace,namespace 内 Worker 无限且常驻。 + +``` +┌──────────────────────────────────────────────┐ +│ Sigil 网关 │ +│ sigil.shazhou.workers.dev │ +│ ┌────────┬────────┬─────────────────────┐ │ +│ │ 路由 │ 鉴权 │ DISPATCHER binding │ │ +│ └───┬────┴───┬────┴──────────┬──────────┘ │ +└──────┼────────┼───────────────┼──────────────┘ + │ │ │ + │ │ env.DISPATCHER.get(name) + │ │ │ + ▼ ▼ ▼ + ┌─────────────────────────────────────────┐ + │ Dispatch Namespace: production │ + │ 无限 Worker,全部常驻,无需换页 │ + └─────────────────────────────────────────┘ +``` + +**请求流**: +``` +请求 → Sigil → 鉴权 → env.DISPATCHER.get("xiaoju--ping") → 响应 +延迟:< 1ms(进程内调用,无网络开销) +``` + +### 对比 + +| 维度 | WorkerPool ($5) | Platform ($25) | +|------|-----------------|----------------| +| 月费 | $5 | $25 | +| 能力上限 | ~400(LRU 管理) | 无限 | +| 命中延迟 | < 5ms(KV + 重定向) | < 1ms(进程内) | +| 未命中延迟 | 1-3s(CF API 部署) | 无(全部常驻) | +| LRU | **核心机制** | 不需要 | +| 实现复杂度 | 高 | 低 | +| 适合 | 个人用户、小团队 | 平台级、多租户 | +| 子域名占用 | 每个热能力 1 个 | 只占 Sigil 1 个 | + +## LRU 调度详细设计(WorkerPool 方案核心) + +### 数据结构 + +用 KV 存储两张表: + +**路由表** `route:{capability}` → Worker 子域名映射 + +```json +{ + "worker_name": "s-a3f8c1", + "subdomain": "s-a3f8c1.shazhou.workers.dev", + "agent": "xiaoju", + "deployed_at": 1743648000, + "type": "persistent" +} +``` + +**LRU 表** `lru:{capability}` → 访问时间戳 + +```json +{ + "last_access": 1743648500, + "access_count": 42, + "deployed": true +} +``` + +### 淘汰算法 + +``` +function evict(): + candidates = 所有 deployed=true 的 LRU 条目 + + # 分优先级淘汰(先淘汰低优先级) + # 1. 临时能力(已过期的直接删,未过期的也优先淘汰) + # 2. 普通能力(按 last_access 排序) + # 3. 持久能力(最后才动,按 last_access 排序) + + candidates.sort(by: priority ASC, last_access ASC) + victim = candidates[0] + + CF_API.delete(victim.worker_name) + KV.put("lru:" + victim.capability, { deployed: false, ... }) + KV.delete("route:" + victim.capability) + + return victim +``` + +**淘汰优先级**: + +``` +临时(过期) > 临时(未过期) > 普通 > 持久 +``` + +同优先级内按 `last_access` 升序(最久未访问的先淘汰)。 + +### 换入流程 + +``` +function page_in(capability): + # 1. 检查配额 + used = count(deployed=true) + if used >= max_slots: + evicted = evict() # 换出最冷的 + log("evicted", evicted) + + # 2. 从 KV 拉代码 + code = KV.get("code:" + capability) + + # 3. 生成 Worker 名(或复用已有名) + worker_name = "s-" + hash(capability)[:6] + + # 4. CF API 部署 + CF_API.deploy(worker_name, code) + + # 5. 更新路由表 + LRU 表 + KV.put("route:" + capability, { worker_name, ... }) + KV.put("lru:" + capability, { deployed: true, last_access: now() }) + + return worker_name +``` + +### Worker 命名与子域名 + +``` +能力名:xiaoju--ping +Worker 名:s-a3f8c1(hash 前 6 位) +子域名:s-a3f8c1.shazhou.workers.dev +``` + +用 `s-` 前缀标记 Sigil 管理的 Worker,与用户自己的 Worker 区分。hash 基于能力全名,同一个能力换出再换入会复用同一个子域名,**外部链接不会失效**。 + +!!! tip "解决方案一的子域名失效问题" + 固定 hash 映射意味着 `s-a3f8c1` 永远对应 `xiaoju--ping`。即使被换出,重新换入后子域名不变。这是 WorkerPool 方案相比朴素 LRU 的关键改进。 + +### 配额分配 + +```yaml +sigil: + backend: "worker-pool" # 或 "platform" + worker_pool: + total_quota: 500 # CF 账户总配额 + system_reserved: 5 # 系统 Worker(sigil 自身、forge 等) + user_reserved: 50 # 用户自己的 Worker(可配置) + safety_margin: 5 # 安全余量 + # max_slots = 500 - 5 - 50 - 5 = 440 + + eviction: + priority: ["ephemeral_expired", "ephemeral", "normal", "persistent"] + + agents: + max_agents: 8 + deploy_cooldown: 5s + + ephemeral: + max_per_agent: 20 + default_ttl: 3600 + max_ttl: 86400 +``` + +### 预热策略 + +冷启动 1-3s 对首次请求体验不好。两个缓解方案: + +**1. 热门能力预热** + +Sigil cron(每小时)扫描 `stats:` 前缀,把访问频率 top-N 的能力提前部署: + +``` +cron → 读 stats → 排序 → top-N 未部署的 → page_in +``` + +**2. 异步换入 + 队列响应** + +对非实时请求,Sigil 可以先返回 202 + 回调 URL: + +```json +{ + "status": "warming", + "callback": "https://sigil.shazhou.workers.dev/_status/xiaoju--heavy-task", + "eta_seconds": 3 +} +``` + +Agent 轮询或 webhook 回调拿结果。 + +### 性能指标 + +| 场景 | 延迟 | 说明 | +|------|------|------| +| 命中(热能力) | < 5ms | KV 读 + 重定向/subrequest | +| 未命中(冷启动) | 1-3s | CF API 部署 + 转发 | +| 命中 + 换出 | 1-3s | 先换出再换入(异步换出可优化到不阻塞) | +| 临时能力创建 | 1-3s | 部署新 Worker | + +### 异步换出优化 + +换出操作(CF API 删除)不需要阻塞当前请求。可以: + +1. 标记 victim 为 `evicting`(路由表保留) +2. 先部署新 Worker +3. 新请求可达后,异步删除 victim + +这样用户感知的延迟只有换入的 1-3s,换出在后台完成。 + +## 配置切换 + +用户在 Sigil 配置中一行切换: + +```yaml +# $5 方案(默认) +sigil: + backend: "worker-pool" + +# $25 方案 +sigil: + backend: "platform" +``` + +Sigil 网关初始化时根据配置加载对应的 `SigilBackend` 实现。Agent 和调用方完全不感知底层差异。 + +## 演进路径(修订) + +``` +Phase 0(当前): 独立 Worker,手动部署 + ↓ +Phase 1: Sigil 网关 + WorkerPool backend($5) + 基础路由 + LRU 换页 + 健康端点 + ↓ +Phase 2: Agent 鉴权 + 临时能力 + TTL 清理 + ↓ +Phase 3: 抽象接口稳定 + Platform backend 实现($25 可选) + ↓ +Phase 4: 预热策略 + 异步换出 + 可观测性 + 告警 +``` + +**Phase 1 最小可行产品**: + +- [ ] Sigil dispatch Worker 骨架 +- [ ] KV 路由表 + LRU 表 +- [ ] `deploy()` → CF API 部署 Worker +- [ ] `invoke()` → 查路由 → 命中转发 / 未命中换入 +- [ ] `remove()` → CF API 删除 + 清理 KV +- [ ] `status()` → 返回配额和 LRU 状态 +- [ ] `/_health` 端点 + +## 相关链接 + +- [Sigil 能力注册表](sigil-capability-registry.md)(架构总览) +- [Uncaged 能力虚拟化](uncaged-capability-virtualization.md)(前置概念) +- [CF Workers API](https://developers.cloudflare.com/api/resources/workers/subresources/scripts/)(部署/删除 Worker) +- [Workers for Platforms](https://developers.cloudflare.com/cloudflare-for-platforms/workers-for-platforms/)($25 方案参考) + +--- + +来源:2026-04-03 主人提出抽象接口 + 双实现方案,小橘基于 LRU 核心地位重新设计 diff --git a/mkdocs.yml b/mkdocs.yml index c1c5f48..47579d0 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -98,6 +98,7 @@ nav: - 三省六部 Edict 架构分析: shared/edict-three-ministries.md - Uncaged 能力虚拟化: shared/uncaged-capability-virtualization.md - Sigil 能力注册表: shared/sigil-capability-registry.md + - Sigil Backend 与 LRU 调度: shared/sigil-backend-lru.md - 基础设施: - Bootstrap 新设备: shared/bootstrap-onboarding.md - Onboarding Checklist: shared/onboarding-checklist.md