proxy-pool/docs/configuration/reference.md

715 lines
31 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.

# 配置参考
## 1. 加载规则
主配置格式为 YAML根字段 `version` 当前固定为 `1`。加载器启用严格字段
检查,拼写错误或未来版本字段不会被静默忽略。当前仓库可使用与未来进程启动
相同的严格加载器校验本地部署配置:
```powershell
go run ./deploy/tools/configcheck deploy/config/local.yaml
```
Controller 入口已实现,源码运行方式为:
```powershell
go run ./cmd/proxy-controller -config CONFIG_FILE
```
配置路径优先使用 `-config`,未提供时读取 `PROXY_POOL_CONFIG`。启用 Admin 时还
必须设置 `PROXY_POOL_CONFIG_FINGERPRINT_KEY`,值为至少 32 字节的独立高熵密钥;
所有 Controller 副本必须一致,且该密钥不得放入 YAML 或 PostgreSQL。该入口已装配
PostgreSQL 管理面迁移、Redis 活动池、Distribution/Admin 独立监听与优雅停机;
Controller Metrics 独立监听、`/livez`、`/readyz` 和基础 Prometheus 运行时指标;
Provider 自动补池、分布式配额、动态重载和 Admin 低基数统计已装配。Gateway 进程、
Worker 控制面会话、持续完整 Snapshot 刷新、凭据分发和 Snapshot 就绪探针已装配Checker 的任务流与
租约契约已接入控制面,但 Redis 共享调度、独立执行器与代表性负载验证仍在后续实施范围。
所有时间值使用 Go duration例如 `500ms`、`30s`、`5m`。示例中的
`${TOKEN}`、`${PASSWORD}`、`${POSTGRES_URL}` 等由加载器从同名环境变量
展开;这些值不得写入日志、指标、配置转储或错误响应。生产配置优先使用
环境变量或 Secret 文件,不提交明文值。
加载与热更新必须遵循同一顺序:
```text
读取文件 -> 严格解析 -> 字段校验 -> 引用/正则校验
-> 构建不可变快照 -> 计算差异 -> 原子替换
```
新配置任何一步失败时保留旧快照。删除或禁用 Upstream 只停止新 Fetch 和新
分配,已有连接进入 Drain不强制中断。
当前 Controller 启动时使用同一份不可变快照完成存储、入口和提取策略装配。
Admin 重载会原子提交新配置、审计并发布到配置 Store监听地址、存储连接和
已构造的安全/提取策略尚未自动重建,这些字段变更后需要重启 Controller。
## 2. 根结构
```yaml
version: 1
defaults: {}
security: {}
gateway: {}
distribution: {}
admin: {}
controlPlane: {}
metrics: {}
storage: {}
routing: []
upstreams: {}
```
- `defaults`Fetch 与 Check 的公共建议值。生产配置仍建议在每个启用的
Upstream 显式写出关键限制,避免继承关系不清。
- `security`:跨入口启动保护。
- `gateway`HTTP/HTTPS CONNECT 数据面入口。
- `distribution`:一次性独占提取入口。
- `admin`:运维管理入口,必须与 Distribution 分端口。
- `controlPlane`Worker 注册、Snapshot ACK 和 Runtime 心跳的 gRPC 控制面,默认关闭。
- `metrics`Prometheus 入口。
- `storage`Controller 使用的 PostgreSQL 与 Redis 地址。
- `routing`:有序 Routing 列表,自上而下首条命中停止。
- `upstreams`:全局共享的 Upstream 运行时定义。
## 3. 安全与监听器
```yaml
security:
requireProtectionOnPublicListen: true
```
启用严格保护时,只要 Gateway、Distribution 或 Admin 满足以下全部条件,
启动即失败:
1. 入口已启用且监听地址不是回环地址。
2. `auth.mode: none`
3. `access.allowCIDRs` 为空。
监听器公共字段:
```yaml
enabled: true
listen: 0.0.0.0:8081
access:
allowCIDRs: [10.0.0.0/8]
trustedProxies: [10.10.0.10/32]
auth:
mode: apiKey
header: X-API-Key
token: "${DISTRIBUTION_API_KEY}"
limits:
maxConcurrentConnections: 100000
requestsPerMinute: 6000
requestsPerMinutePerClient: 600
```
`trustedProxies` 只决定何时接受 `Forwarded``X-Forwarded-For`,不能替代
`allowCIDRs`。来自非可信代理的转发头必须忽略。
`requestsPerMinute``requestsPerMinutePerClient` 为非负整数,最大值为
`2^53-1`Distribution 的非零额度由 Redis Lua 计数,因此配置校验统一限制在
Lua 可精确表示的整数范围内。
### 3.1 认证模式
- `none`:无身份认证,访问控制与限流仍生效。
- `usernamePassword`:使用 `username``password`
- `apiKey`:使用 `header``token`
- `bearer`:使用 `Authorization: Bearer TOKEN`Gateway 对应
`Proxy-Authorization: Bearer TOKEN`
- `ipWhitelist`:使用 `cidrs`
- `any``methods` 中任一方法成功即可Bearer 方法的 Secret 使用
`value`/`valueFile`。
### 3.2 端点权限
`auth.permissions` 是普通认证方式Basic、API Key、Bearer、IP 白名单)的固定权限
集合。未配置时保留历史全权限行为;显式配置后只允许下列有限值:
- `admin:read`:读取 Admin Status 与审计页。
- `admin:write`:执行 Upstream/Routing/配置变更。
- `distribution:extract`:调用 Distribution 独占提取。
- `*`:所有权限,且必须单独出现。
`mode: any` 必须将 `permissions` 写在各 `methods` 项中,权限随实际命中的凭据
返回,顶层 `auth.permissions` 不允许同时配置。权限值不接受空白、重复或未知项。
缺少所需权限的已认证请求返回 `403`,并在进入 JSON 解码、提取或管理 mutation 前
结束。`mode: none` 不允许配置权限,避免把匿名访问误配置为受控授权。
```yaml
admin:
auth:
mode: any
methods:
- mode: bearer
valueFile: /run/secrets/admin-reader-token
permissions: [admin:read]
- mode: bearer
valueFile: /run/secrets/admin-writer-token
permissions: [admin:read, admin:write]
```
### 3.3 Distribution Client 提取约束
认证凭据可在 auth.client 中设置固定的 Client 约束mode: any 则必须在实际
命中的 auth.methods 项下配置。当前 Client 约束按入口明确分工,避免出现只声明
不执行的访问控制。
- maxExtractCountDistribution 单次提取的额外上限0 表示不追加上限。
- allowedUpstreams允许访问的 Upstream 名称集合。省略请求过滤条件时,服务端
自动使用这个集合;请求携带集合外名称会返回 403。
- allowedRegions允许访问的地区集合行为与 allowedUpstreams 相同。
空 client 配置保留旧版兼容语义不会限制已认证凭据。Upstream 名称必须引用
当前配置中已有的 Upstream。
~~~yaml
distribution:
auth:
mode: any
methods:
- mode: apiKey
header: X-API-Key
valueFile: /run/secrets/tenant-a-key
permissions: [distribution:extract]
client:
maxExtractCount: 20
allowedUpstreams: [provider-a, provider-b]
allowedRegions: [shanghai, beijing]
~~~
### 3.4 Gateway Client 路由约束
Gateway 的 auth.client 支持 allowedRoutings、requestsPerMinute 和
maxConcurrentConnections。认证成功后
Gateway 先完成目标地址策略和本地 Routing 匹配,再于本地 Dispatcher 前检查
Routing 集合;不匹配返回 403不会尝试选择或预留 Proxy。速率和并发限制均以
认证 Client 为计数键,在每个 Gateway Worker 内执行,超过限制返回 429。三项
检查都只读取请求上下文和当前本地状态,不访问 Redis、PostgreSQL 或 Provider。
allowedRoutings 中的每个名称必须对应一个启用的 Gateway Routing。Gateway 上的
maxExtractCount、allowedUpstreams 和 allowedRegions以及 Distribution 上的
allowedRoutings 和 requestsPerMinute 均会被配置校验拒绝。Admin 不支持任何
client 约束。
~~~yaml
gateway:
auth:
mode: any
methods:
- mode: bearer
valueFile: /run/secrets/checkout-gateway-token
client:
allowedRoutings: [checkout]
requestsPerMinute: 600
maxConcurrentConnections: 20
~~~
Gateway、Distribution、Admin 与 Provider API 是独立认证边界。改变其中一套
不得连带改变其他入口。
### 3.5 Gateway 粘性会话
gateway.stickySession 让调用方使用一个会话头尽量固定出口 Proxy。绑定键是认证
Client、已匹配 Routing 和会话标识的组合,因此不同 Client 或不同 Routing 使用
相同会话值也不会共享出口。该功能要求 Gateway 认证开启。
~~~yaml
gateway:
auth:
mode: bearer
tokenFile: /run/secrets/gateway-token
stickySession:
enabled: true
header: X-Proxy-Session
ttl: 20s
maxEntries: 100000
~~~
- header 必须是规范 HTTP Header 名;一个请求只能携带一个值,值由字母、数字、
点、下划线、连字符组成且最长 128 字节。
- ttl 是绑定的最长时长;实际到期时间还会被 Proxy 的 usableUntil、代理过期时间
和 allocation safety margin 截短。
- maxEntries 是当前 Gateway Worker 的硬上限。缓存只保存 Proxy ID 和过期时间,
不保存 Proxy 地址、凭据或原始会话标识。
- 会话头仅用于本地选路Gateway 在转发 HTTP 请求前删除它。Proxy 不再符合当前
Snapshot 或一次代理尝试失败时,绑定会被清除并按正常 Routing 重选。
- 该版本不把 Redis、PostgreSQL 或 gRPC 引入 Gateway 热路径;跨 Worker 粘性需要
上游负载均衡保持 Worker 亲和,后续由控制面快照协议扩展共享恢复能力。
## 4. Worker 控制面
控制面默认关闭;默认配置中的 `8443` 端口预留不表示服务已监听。控制面 Session、
Snapshot ACK 和 Runtime 报告是 Redis 的短效运行时状态,**不写入 PostgreSQL**。
仅本机开发可以使用回环明文:
```yaml
controlPlane:
enabled: true
listen: 127.0.0.1:8443
protocolVersion: 1
heartbeatInterval: 10s
sessionTTL: 30s
maxStaleAge: 10s
maxMessageBytes: 1048576
maxRuntimeCounters: 100000
maxConcurrentStreams: 128
tls:
mode: disabled
```
任何非回环监听地址都必须使用 mTLS
```yaml
controlPlane:
enabled: true
listen: 0.0.0.0:8443
protocolVersion: 1
heartbeatInterval: 10s
sessionTTL: 30s
maxStaleAge: 10s
maxMessageBytes: 1048576
maxRuntimeCounters: 100000
maxConcurrentStreams: 128
tls:
mode: mtls
certFile: /run/secrets/controller-cert.pem
keyFile: /run/secrets/controller-key.pem
clientCAFile: /run/secrets/worker-ca.pem
trustDomain: proxy.example
environment: production
gatewayTLS:
certFile: /run/secrets/gateway-cert.pem
keyFile: /run/secrets/gateway-key.pem
serverCAFile: /run/secrets/controller-ca.pem
checkerTLS:
certFile: /run/secrets/checker-cert.pem
keyFile: /run/secrets/checker-key.pem
serverCAFile: /run/secrets/controller-ca.pem
```
- `protocolVersion` 当前固定为 `1`
- `sessionTTL` 至少为 `3 * heartbeatInterval``maxStaleAge` 不得短于心跳间隔。
- `maxMessageBytes` 范围为 `1..67108864``maxRuntimeCounters` 范围为
`1..1000000``maxConcurrentStreams` 必须为正数。
- `tls.mode` 只能为 `disabled``mtls`。`disabled` 只允许回环监听;`mtls` 必须
同时配置证书、私钥、客户端 CA、全小写 DNS `trustDomain` 和单 URI 路径段
`environment`
- `gatewayTLS` 是 Gateway 的客户端证书、私钥和 Controller CA`tls` 的服务端
证书和 Worker CA 分离。三项可以同时省略(未运行 Gateway配置任一项时必须完整提供。
- `checkerTLS``gatewayTLS` 有相同字段和完整性校验,但必须使用独立的 Checker
证书。Controller 分别验证 `.../worker/<worker-id>`
`.../checker/<checker-id>` SPIFFE URI不能跨角色复用证书。
Gateway 连接 Controller 时使用独立启动参数而非 `controlPlane.listen`。至少设置
`PROXY_POOL_CONTROL_PLANE_ADDRESS`、`PROXY_POOL_CLUSTER_ID`、
`PROXY_POOL_WORKER_ID`、`PROXY_POOL_INSTANCE_ID` 和 `PROXY_POOL_ZONE`,详见
[控制面协议](../api/control-plane.md#9-gateway-启动参数)。基础 Compose/Kubernetes
模板保持 `controlPlane.enabled: false`,环境 Overlay 挂载 mTLS 证书并启用后才可启动
Gateway。
Checker 同样使用独立的可拨号地址:`proxy-checker` 的 `-control-plane`
`-checker-id`、`-instance-id` 和 `-max-in-flight` 可由对应的
`PROXY_POOL_*` 环境变量提供。mTLS 模式下该命令读取 `checkerTLS`,明文 fixture
模式只接受回环 Controller 地址。Checker 只从 gRPC 领取任务并批量上报事实,不读取
Redis/PostgreSQLController 在生产启动拓扑中装配 Redis 共享任务队列,当前调度
HTTP/HTTPS/SOCKS5 BASIC、EGRESS 和 TARGET 检查。调度监督器在每轮从已发布配置读取启用的
上游与 RoutingAdmin reload 发布后,上游/路由启停、有效 `check` 策略和目标列表会在下一轮生效,
新启用的上游无需重启 Controller。
`maxRuntimeCounters` 同时限制单个 Runtime 报告和单个 Outcome 批次的条目数。Gateway
在本地维护容量为 `65536` 的非阻塞 Outcome 队列,默认微批上限为 `512`,实际取二者中
较小值;该队列与其序列确认状态仅存在于 Gateway 进程内。Controller 的 Redis 状态只保存
当前 Worker session 的最后确认序列和摘要PostgreSQL 不保存 Outcome、代理明细或逐请求记录。
## 5. Gateway
```yaml
gateway:
enabled: true
listen: 127.0.0.1:8080
auth: {mode: none}
limits:
maxConcurrentConnections: 50000
transport:
dialTimeout: 10s
handshakeTimeout: 15s
responseHeaderTimeout: 30s
idleConnTimeout: 90s
maxIdleConns: 20000
maxIdleConnsPerHost: 32
maxConnsPerHost: 32
tunnelBufferBytes: 32768
tunnelIdleTimeout: 5m
retry:
maxAttempts: 2
retryMethods: [GET, HEAD]
stickySession:
enabled: true
header: X-Proxy-Session
ttl: 20s
maxEntries: 100000
destinationPolicy:
denyPrivateNetworks: true
denyLoopback: true
denyLinkLocal: true
denyCIDRs: [169.254.169.254/32]
allowedPorts: [80, 443]
```
- `retryMethods` 默认只应包含幂等方法。POST、PUT、PATCH、DELETE 不自动重试。
- CONNECT 向 Client 写出 `200 Connection Established` 后不透明重放。
- 目的地址策略必须在 DNS 解析前后都执行,防止 DNS Rebinding。
- 三个 `deny*` 字段省略时均按 `true` 处理;只有显式配置为 `false` 才放行
对应类别,部分配置不会改变其他类别的安全默认值。
- `allowedPorts` 省略时安全默认值为 `[80, 443]`;配置空列表不表示开放全部端口。
- 保留地址、CGNAT 与云元数据端点始终拒绝,不能通过私网/链路本地开关放行。
- `maxConcurrentConnections` 是入口准入上限,不是 Proxy 容量上限。
### 5.1 Gateway 本地传输层
**gateway.transport** 只配置每个 Gateway Worker 的本地 HTTP Transport 和 CONNECT
隧道 I/O不读取或写入 Redis、PostgreSQL、Provider也不参与热路径分配。修改
这些字段需要滚动重启对应 Gateway Worker 后生效。
- **dialTimeout**、**handshakeTimeout**、**responseHeaderTimeout**、
**idleConnTimeout**:分别限制拨号、上游 TLS/CONNECT 握手、HTTP 响应头和空闲
HTTP 连接的时长。
- **maxIdleConns**:单个本地 HTTP Transport 的总空闲连接上限。Gateway 分别为代理
转发和 direct fallback 建立 Transport最坏情况下两者都可能保有空闲连接。
- **maxIdleConnsPerHost**:单个上游 Host 的空闲 HTTP 连接上限。
- **maxConnsPerHost**:单个上游 Host 的 HTTP 活跃加空闲连接上限。它不替代
**limits.maxConcurrentConnections**,也不限制已经建立的 CONNECT 隧道。
- **tunnelBufferBytes**:每个活跃 CONNECT 隧道的每个复制方向使用一个缓冲区,内存
预算至少按 活跃隧道数 * 2 * tunnelBufferBytes 计算。
- **tunnelIdleTimeout**CONNECT 隧道双向没有数据活动时的最长存活时间。
所有字段均为可选项;数值 0 表示沿用进程默认值:拨号 10s、握手 15s、
响应头 30s、空闲 HTTP 连接 90s、总空闲连接 1024、每 Host 空闲连接
64、隧道缓冲 32768 字节、隧道空闲 5m。计数和时长不得为负数
maxIdleConnsPerHost 不得大于显式配置的 maxIdleConns。示例中的 20000/32
是高并发 HTTP 转发的起点,不是每秒请求量的换算公式,应同时按可用文件描述符、
上游限额和观测到的复用率调节。
## 6. Distribution
```yaml
distribution:
enabled: true
listen: 127.0.0.1:8081
auth: {mode: none}
clientIdentification: {mode: sourceIP}
limits:
requestsPerMinute: 60
requestsPerMinutePerClient: 30
extraction:
fulfillment: partial
maxCountPerRequest: 20
minRemainingTTL: 30s
maxHealthCheckAge: 15s
reserveForGateway: 5
idempotencyTTL: 5m
```
Extraction 是固定的一次性独占行为,**没有** `mode`、`leaseDuration`、
`release``renew` 配置。Redis 在一个原子操作内选择候选、执行
`AVAILABLE -> EXTRACTED` 并短期保存幂等结果;成功后该 Proxy 不再由 Gateway
或 Distribution 在当前 TTL 生命周期中分配。
- `fulfillment``partial` 或 `allOrNothing`,默认语义为 `partial`
- `maxCountPerRequest`:单次请求硬上限。
- `minRemainingTTL`:剩余寿命低于此值时不参与提取。
- `maxHealthCheckAge`:最近检查早于此窗口时不参与提取。
- `reserveForGateway`:提取后必须留给 Gateway 的最低符合条件库存数量。
- `idempotencyTTL`Redis 幂等结果的最长保留时间;省略时为 5 分钟,实际
保留时间不会超过本次返回代理中最早的 `expiresAt`
`partial` 会原子提取实际可得数量;`allOrNothing` 数量不足时不改变任何候选,
一个也不提取。认证关闭时仍应使用 `sourceIP` 识别匿名 Client 并执行全局/
来源限流。
`clientIdentification.mode` 支持:
- `sourceIP`:使用可信代理链解析后的来源地址;省略配置时采用此模式。
- `authenticatedClient`:使用 Basic 用户名或 Token 的稳定不可逆摘要;要求
启用认证。
- `authenticatedClientOrSourceIP`:优先认证主体,无主体时回退来源地址。
## 7. Routing
```yaml
routing:
- name: api-post
enabled: true
purpose: gateway
match:
hostRegex: '^api\\.example\\.com$'
methods: [POST]
pathRegex: '^/v1/'
headers: {X-Tenant: premium}
upstreams: [provider-a, provider-b]
strategy:
type: sequential
switchAfterEmptyFetch: 5
endBehavior: stop
onUnavailable:
action: reject
waitTimeout: 0s
check:
targets: [https://api.example.com/health]
```
- Routing 列表有序,首条匹配后停止。
- `purpose``gateway``extract`
- `strategy.type` 支持 `sequential`、`random`、`roundRobin`、`weighted`、
`leastConnections`
- `weighted` 使用 `weights` 映射,键必须引用本 Routing 的 Upstream。
- `sequential` 至少引用两个 Upstream并设置大于零的
`switchAfterEmptyFetch``endBehavior` 省略时默认为 `stop`,也可显式设置
`loop``stayLast`
- `onUnavailable.action``reject`、`wait` 或 `direct`;默认建议 `reject`
- `check.targets` 是 Routing 级 HTTP/HTTPS 探测目标。每个 Routing 最多 16 个 URL且一个
启用 Upstream 被其引用的 TARGET Profile 总数最多 64 个。每个 Profile 的身份是
`(routing.name, target URL)`;失败只影响该 Profile不改变 Proxy 的全局健康状态。
Sequential 的空计数属于 Upstream当前索引属于 Routing。只有 Provider 响应
成功、模板成功且合法候选为零时才增加空计数。错误不改变空计数;重复候选
会重置空计数但增加独立 duplicate 指标。
Controller 在连续空结果达到阈值时按顺序切换当前 Upstream到达最后一个可用
Upstream 且 `endBehavior: stop` 时,会以当前 Upstream 的 CAS 条件原子停用该
Routing。停用后的 Gateway 规则按既有 `onUnavailable.action` 执行 `reject`、`wait`
`direct`,直到一次配置重载提交新的管理快照。
## 8. Upstream
```yaml
upstreams:
provider-a:
enabled: true
exposure: [gateway, extract]
provider:
billingMode: fetch
protocols: [http, https]
api:
url: https://provider-a.example/proxies
method: GET
auth:
type: apiKey
location: header
name: X-Provider-Key
value: "${PROVIDER_API_KEY}"
headers: {}
query: {count: '100'}
body: {type: json, value: {}}
template: '{{.}}'
proxyAuth: {type: response}
pool: {maxSize: 1000, shrinkDelay: 30s}
capacity: {maxConcurrencyPerProxy: 10}
refill:
reconcileInterval: 1s
minimumAvailableSlots: 2000
targetAvailableSlots: 5000
lifecycle: {ttl: 2m, allocationSafetyMargin: 15s}
fetch:
estimatedIPsPerCall: 100
requestInterval: 1s
timeout: 3s
maxAttempts: 3
maxInFlight: 1
maxTotal: 100000
maxResponseBytes: 1048576
templateTimeout: 100ms
retry: {initial: 500ms, max: 30s, jitter: 20}
check:
interval: 30s
jitter: 20
maxInFlight: 100
timeout: 2s
maxAttempts: 2
maxConsecutiveFailures: 3
unhealthyRemoveAfter: 0s
urls: [http://connect.rom.miui.com/generate_204]
```
### 8.1 Provider 与代理认证
- `api.auth` 用于系统访问 Provider API。
- `proxyAuth` 用于最终连接被获取的 Proxy。
- Provider `api.auth.type` 支持 `none`、`basic`、`bearer`、`apiKey`。
- `apiKey` 使用 `location: header|query``name`、`value`,程序负责 Header
设置或 Query URL 编码。
- `basic` 使用 `username`、`password``bearer` 使用 `token`
- `proxyAuth.type: response` 表示凭据来自 Provider 响应。
- `proxyAuth.type: static` 使用配置中的 `username`、`password`。
- `proxyAuth.type: ipWhitelist` 表示 Provider 按出口 IP 放行,不配置账号密码。
Provider API 认证**不使用**入口的 `auth.mode`,代理连接认证也不使用
`mode`。三者的字段和凭据不可互相回退:
```yaml
# 对外 Distribution/Gateway/Admin
auth:
mode: apiKey
header: X-API-Key
token: "${CLIENT_API_KEY}"
# 请求 Provider API
auth:
type: bearer
token: "${PROVIDER_API_TOKEN}"
# 连接 Provider 返回的 Proxy
proxyAuth:
type: static
username: "${PROVIDER_PROXY_USER}"
password: "${PROVIDER_PROXY_PASSWORD}"
```
### 8.2 Pool 与累计额度
- `pool.maxSize`:当前系统维护且尚未 EXTRACTED 的 Proxy 硬上限,包括
FETCHED、CHECKING、AVAILABLE、SUSPECT、DRAINING 和 pending expected。
- `fetch.maxTotal`:当前 Redis generation 内的累计获取停止阈值;`0` 表示不设置。
Redis 在调用前原子校验 `fetched total + pending expected + expected`,并在达到
阈值后停止发起新调用。
`fetch.maxTotal` 不得小于 `pool.maxSize`。提取一个 Proxy 会释放当前库存位置,
但不会恢复累计获取额度。Provider 调用结果不确定、响应无法解析或 Permit 过期时,
系统按 `estimatedIPsPerCall` 保守记账避免故障或换主造成额度低估。Redis 全量
状态丢失会创建新 generation因此需要由外部计费系统提供跨 generation 的长期额度。
`estimatedIPsPerCall` 是预留估值,不是通用的 Provider 响应硬限制。如果某次实际
合法返回量超过估值,系统只保留本地池容量允许的数量,但累计账本按实际合法数量
记账并停止后续调用;该次可能越过停止阈值。需要绝对硬上限时,必须同时在 Provider
请求参数中配置供应商支持的批量上限,并保证其不超过剩余额度。
当前实现限制单个 `pool.maxSize <= 1,000,000`、配置内 Upstream 总数不超过
`4,096`并要求单代理并发、Refill 双水位、理论总槽位及其他传入 Redis Lua 的
累计/并发计数不超过 `2^53-1`。这些边界在配置加载和 Admin reload 提交前校验,
不会等到 Provider Runtime 启动后才失败。
`proxyAuth.type: response` 的用户名/密码是 Parser 到 Redis Activity Adapter 之间的
临时凭据。每次 Parser handoff 使用独立、幂等释放的 lease避免并发 Fetch 互相
删除或轮换版本覆盖;成功复制到 TTL 活动记录、候选被截断或解析失败后都会释放。
内存 lease 上限按所有配置 Upstream 的 `pool.maxSize * fetch.maxInFlight` 汇总,
配置 reload 只提高上限,不预分配对应内存。
### 8.3 Fetch 限制
- `estimatedIPsPerCall`:冷启动时每次 Provider 调用预计返回的合法 Proxy 数,
同时用于 `pool.maxSize` 的 pending 预占;不得从任意 Query 或 Body 字段推断。
- `requestInterval`:同一 Provider 请求间隔。
- `timeout`:单次调用超时。
- `maxAttempts`:单次补池动作最大尝试次数。
- `maxInFlight`:同一 Provider 同时在途请求数。
- `maxResponseBytes`:读取响应的硬上限。
- `templateTimeout`:模板解析执行上限。
- `retry`错误退避HTTP 429 还必须尊重 `Retry-After`
大量缺池信号必须合并成 singleflight 或容量为 1 的通知,不能按 Gateway 请求
数量线性触发 Provider API。
### 8.4 Refill 水位
- `reconcileInterval`:无事件时重新读取库存的兜底周期,启动时仍立即检查一次。
- `minimumAvailableSlots`:可用并发槽位低于该值时进入补池。
- `targetAvailableSlots`:进入补池后持续补到该目标,再退出补池状态。
三个字段均为必填正值,且 `targetAvailableSlots > minimumAvailableSlots`
目标不得超过 `pool.maxSize * capacity.maxConcurrencyPerProxy` 的理论上限。
`requestInterval` 只限制外部 API 调用不能兼任库存复核周期。pending Proxy
`estimatedIPsPerCall * maxConcurrencyPerProxy` 折算槽位,避免并发补池超量。
### 8.5 生命周期与健康
- 明确绝对过期时间优先于响应 TTL响应 TTL 优先于配置 `lifecycle.ttl`
- 距离过期不足 `allocationSafetyMargin` 时停止新分配。
- `check.jitter` 为调度抖动百分比,避免所有 Proxy 同时探测。
- `check.urls` 最多 16 个规范化 HTTP/HTTPS URL作为 EGRESS 检查的有界目标集;
BASIC 检查不依赖该字段。EGRESS 成功响应必须包含合法 IP支持纯文本 IP或 JSON 的
`ip`、`ip_address`、`address`、`origin`、`query` 字段。
- 启用 Routing 的 `check.targets` 会为其引用的每个启用 Upstream 创建 TARGET 检查组。BASIC、
EGRESS 与 TARGET 使用固定批次和轮转顺序,并共享该 Upstream 的 `check.maxInFlight`,避免
配置多个目标后产生无界检查流量。
- 第一次有意义失败进入 SUSPECT达到 `maxConsecutiveFailures` 后才进入
UNHEALTHY。
- `unhealthyRemoveAfter` 控制 UNHEALTHY 持续多久后可由 Controller 回收;`0s`
(默认)表示只保留异常状态,永不因该策略删除。首次进入 UNHEALTHY 的时间会跨
CHECKING 重试保持不变,成功恢复 AVAILABLE 时清除。
- 回收器只运行在 Controller 后台,使用 Redis 有序索引和固定批次,不进入 Gateway
请求热路径。没有 Worker ownership 的 Proxy 在同一清理边界删除;仍属于 Worker 的候选
先在有界回执中携带 worker、assignment epoch 和首次异常时间,再由条件式 Drain 原子
复核 `UNHEALTHY` 状态与归属未变化后创建 Ticket。后续必须等待排除快照 ACK 与 Runtime
的 Active/Reserved 归零,才会释放 ownership 并由下一轮回收,避免删除仍可能出现在
Gateway Snapshot 中的代理。
## 9. 存储、Admin 与 Metrics
```yaml
admin:
enabled: true
listen: 127.0.0.1:8082
auth: {mode: none}
metrics:
enabled: true
listen: 127.0.0.1:9090
storage:
postgresURL: "${POSTGRES_URL}"
redisURL: "${REDIS_URL}"
```
Proxy 明细不写入 PostgreSQL。Redis 是短效 Proxy 活动池的运行时事实源,使用
TTL 保存 Proxy 状态、所有权和过期时间,并原子执行独占提取及短期幂等结果
读写Redis 丢失后由 Provider 重新补池,不从 PostgreSQL 恢复原 Proxy。
PostgreSQL 只保存配置版本、Upstream/Routing 管理状态、Admin 审计与 Outbox
以及可选的无 Proxy 明细聚合指标。Distribution 提取不依赖 PostgreSQL因而
PostgreSQL 故障本身不应使 Redis 中可完成的 Extract 返回 `503`。Metrics 标签
禁止 Proxy IP、Client ID、Session、完整 URL 和 Request ID。
启用 Admin 的多 Controller 部署必须让所有副本读取同一版本化配置源、Secret
版本和 `PROXY_POOL_CONFIG_FINGERPRINT_KEY`。Supervisor 每秒比较本地完整配置的
HMAC-SHA-256 与 PostgreSQL 权威值:管理状态暂时不可读时沿用 last-known
Runtime指纹已更新但本地源尚未同步时停止旧 Provider待源匹配并通过预检后
按全局 revision 恢复,避免旧凭据换主或迟到旧配置回写。
Metrics 启用时 `listen` 必须是合法 `host:port`。该入口固定提供 `/livez`
`/readyz``/metrics`,不复用 Distribution/Admin 的认证边界;外部访问必须由
网络策略限制。当前 `/metrics` 包含 Go/进程基础指标,以及 Checker 的
`proxy_pool_checker_tasks_dispatched_total{level}`
`proxy_pool_checker_observations_total{level,result}`。`level` 固定为 BASIC、EGRESS、TARGET
`result` 固定为 accepted、rejected。Gateway 另暴露
`proxy_pool_gateway_outcomes_total{stage,result}`
`proxy_pool_gateway_outcome_queue_dropped_total``stage` 固定为 DIAL、PROXY_HANDSHAKE、
RESPONSE_HEADERS、TUNNEL`result` 固定为 success、failure。Provider、Extraction、
Capacity 和 Drain 业务指标也已接入,所有标签均为固定枚举;绝不包含 Proxy、IP、Checker、
Client、路由、目标 URL 或凭据标签。Capacity 从既有 Provider 库存对账循环聚合,
不进入 Gateway 热路径。Distribution 启用时 `/readyz` 只以 Redis 活动池为
服务流量门槛PostgreSQL 故障由 Admin 接口独立报告。Metrics 开关或监听地址
变更需要重启 Controller。Controller、Gateway 和 Checker 的进程级致命错误以 JSON
结构化日志输出包含组件和稳定错误类型不输出错误原文敏感属性、URL 用户信息和
查询 Secret 在写出前统一替换为 `[REDACTED]`
Gateway 还按每个 Worker 暴露 HTTP/CONNECT 请求总数、在途请求数和活跃 CONNECT
隧道数;协议标签固定为 HTTP、CONNECT。请求数在 Handler 接受请求时增加,在途数在
全部拒绝、转发或隧道关闭后归零;活跃隧道只覆盖成功建立并开始 relay 的连接。这些
指标是连接池、入口并发、文件描述符和长连接排空的本地观测,不携带 Proxy、路由、
目标、Client 或凭据。
## 10. 启动前校验清单
1. `version` 必须为 `1`,未知字段拒绝。
2. 所有启用监听器具有合法 `host:port`
3. 非回环监听器满足认证或来源 CIDR 保护。
4. Routing 名称唯一,正则可编译,引用的 Upstream 存在。
5. Sequential 至少引用两个 Upstream、阈值大于零`onUnavailable.action` 明确。
6. 启用的 Upstream 有正数 `pool.maxSize`、并发、Refill 水位和 Fetch 估值/限制。
7. `allocationSafetyMargin < ttl`
8. `fetch.maxTotal == 0``fetch.maxTotal >= pool.maxSize`
9. `minimumAvailableSlots < targetAvailableSlots`,且目标不超过理论并发容量。
10. Distribution 的 fulfillment 合法,单次数量大于零。
11. Secret 未写入日志可见配置转储。