Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

控制面快照协议

ScootGate 通过两个独立 HTTPS pull 协议消费控制面状态:

  • 授权快照:客户端 API key 摘要;
  • 路由快照:backends 与公共 models(仅 snapshot 模式)。

MQTT 只提示“可能有新版本”,不能携带或直接应用快照。

通用拉取契约

ScootGate 在配置 URL 后追加当前版本:

GET <snapshot_url>?since=<current-version>
Authorization: Bearer <service-token>

如果配置 URL 已有 query,则使用 &since=

控制面响应:

状态含义
200 OKbody 为完整 JSON 快照
304 Not Modified没有更新,body 为空
其他同步失败,保留当前状态

两类 body 上限均为 8 MiB。网络、HTTP、JSON、schema 或语义校验失败时, 旧快照保持不变。失败轮询指数退避,最长 300 秒;MQTT 通知会在 500ms debounce 后提前触发一次相同的受认证拉取。

控制面应使用 HTTPS。Bearer token 只授予对应节点读取快照的最小权限。

授权快照

配置:

[auth]
snapshot_url = "https://control.example.com/v1/auth-snapshot"
token = "AUTH_CONTROL_TOKEN"
cache_path = "/var/lib/scootgate/auth-snapshot.json"
poll_interval_secs = 30

JSON schema

{
  "version": 7,
  "keys": [
    {
      "key_id": "client-123",
      "tenant_id": "tenant-a",
      "key_sha256": "299ccbc5cc6d53eb7860d96cb94e58213a550cd3aaa12d18f6ec2a3b305808a4",
      "status": "active"
    }
  ]
}

完整示例:auth-snapshot.json

字段要求

字段类型要求
versionu64大于 0 且严格大于当前版本
keysarray完整 key 集合
key_idstring非空
tenant_idstring非空
key_sha256string原始客户端 key 的 64 位 SHA-256 hex
statusstringactiverevoked

同一快照不得出现重复 digest,未知字段会使整份快照失败。hex 大小写均可, 节点内部规范化为小写。只有 active key 进入查找表;revoked 与未知 key 在数据面都表现为 401。

控制面计算 digest:

lowercase_hex(SHA-256(UTF-8 bytes of the exact client API key))

不要对 key 做 trim、Unicode 规范化或其他预处理。

路由快照

仅在以下模式启用:

[routing_source]
mode = "snapshot"
snapshot_url = "https://control.example.com/v1/routing-snapshot"
token_env = "SCOOTGATE_ROUTING_TOKEN"
cache_path = "/var/lib/scootgate/routing-snapshot.json"
poll_interval_secs = 30
max_stale_secs = 300

Bearer token 从 token_env 指向的环境变量读取。

JSON schema

{
  "schema": 1,
  "version": 41,
  "backends": {
    "provider-a": {
      "protocol": "openai_compatible",
      "upstream": "https://api.example.com",
      "credential_ref": "provider-a",
      "max_inflight_per_node": 64,
      "enabled": true
    }
  },
  "models": {
    "chat-fast": {
      "protocol": "openai_compatible",
      "enabled": true,
      "targets": [
        {
          "backend": "provider-a",
          "model": "real-model",
          "weight": 100,
          "supports_stream": true,
          "supports_tools": true,
          "supports_json_schema": true
        }
      ]
    }
  }
}

完整示例:

顶层要求

字段类型要求
schemau32固定为 1
versionu64严格大于已应用版本
backendsobject完整 backend map,至少一项
modelsobject完整公共 model map,至少一项

内部字段与配置参考一致。额外要求:

  • credential_ref 必须存在于节点本地 [credentials]
  • 快照绝不能包含 Provider credential 值;
  • model target 引用的 backend 必须存在且协议一致;
  • upstream 必须是合法 origin,生产使用 HTTPS;
  • model target 池不能为空,权重与容量必须在范围内;
  • 任意未知字段或非法引用都会整体拒绝。

原子应用与状态继承

快照完整编译成功后一次性替换。请求在开始时固定一份表,热更新不影响在途请求。

backend 的 id、protocol、origin、credential 内容和容量均不变时,节点继承:

  • EWMA 首包延迟;
  • 在途计数;
  • 熔断/half-open;
  • 429 冷却;
  • 401/403 停用状态。

任一身份字段变化会创建新的 backend runtime。

Freshness

成功的 200 应用或 304 会刷新 routing control-plane freshness。 超过 max_stale_secs 没有成功同步时:

  • /readyz 返回 503;
  • 已有最后有效表继续服务;
  • 流量入口应摘除该节点;
  • 后续成功同步会自动恢复 ready。

发布与回滚

控制面必须先使完整快照可被 HTTPS 拉取,再发送 MQTT 版本通知。

版本号不能回退。若要恢复版本 41 的内容,应发布相同内容但使用新版本 43, 而不是重新发送 41。这样重复、乱序和延迟消息不会倒退节点状态。

缓存

节点将最后有效快照原子写入本地缓存(Unix 文件权限 0600),重启时先加载缓存:

  • 授权缓存只含 digest 与身份;
  • 路由缓存只含 credential reference;
  • 缓存损坏或过大时忽略并等待控制面;
  • 缓存不是控制面事实源,后续仍持续轮询。