proxy-pool/docs/api/distribution.md
youfak 4de3ffb85f
Some checks are pending
ci / test (ubuntu-latest) (push) Waiting to run
ci / test (windows-latest) (push) Waiting to run
ci / race (push) Waiting to run
feat: add ephemeral proxy activity pool
2026-07-29 12:51:18 +08:00

223 lines
8.1 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.

# Distribution API
## 1. 行为契约
Distribution API 只提供一次性独占提取:
```text
读取 Redis TTL 活动池中的 AVAILABLE 候选
-> 校验 Gateway 预留、TTL、健康、所有权与过滤条件
-> Redis 原子移出可分配池并写入短期幂等结果
-> 返回真实代理地址
```
Redis 原子操作成功前不得把地址写给 Client。返回成功后该 Proxy 在当前 TTL
生命周期中不再参与 Gateway、再次提取或可用库存统计。系统不跟踪 Client 是否
使用、使用并发或何时停止,也不提供 release、renew、status 或租约端点。短效
Proxy 明细只存在于 Redis TTL 活动池和节点内存PostgreSQL 不保存 Proxy 明细
或逐次提取记录。
HTTP 契约源文件:`api/openapi/proxy-pool.yaml`。
## 2. 提取请求
```http
POST /api/v1/proxies/extract HTTP/1.1
Host: 127.0.0.1:8081
Content-Type: application/json
X-API-Key: TOKEN
X-Request-ID: req_01J4EXAMPLE
Idempotency-Key: extract-01J4EXAMPLE
```
PowerShell 调用示例:
```powershell
$body = @{
count = 5
fulfillment = "partial"
filters = @{ protocols = @("http"); regions = @("shanghai") }
} | ConvertTo-Json -Depth 4
Invoke-RestMethod `
-Method Post `
-Uri http://127.0.0.1:8081/api/v1/proxies/extract `
-Headers @{ "X-API-Key" = "TOKEN"; "Idempotency-Key" = "extract-SERIAL" } `
-ContentType application/json `
-Body $body
```
请求约束:
- `count` 至少为 1且不超过服务端 `maxCountPerRequest`
- `fulfillment` 省略时使用服务端配置,默认 `partial`
- 所有过滤数组执行“数组内 OR、不同维度 AND”。空数组等同不限制。
- 每个过滤维度最多包含 64 个值,且数组内不得重复。
- `allowedUpstreams` 只能缩小 Client 可访问的 Upstream 集,不能扩大权限。
## 3. 成功响应
```json
{
"requestId": "req_01J4EXAMPLE",
"requested": 5,
"returned": 2,
"proxies": [
{
"id": "px_01J4A",
"protocol": "http",
"host": "192.0.2.10",
"port": 8080,
"username": "USER",
"password": "PASSWORD",
"url": "http://USER:PASSWORD@192.0.2.10:8080",
"region": "shanghai",
"carrier": "telecom",
"upstream": "provider-a",
"expiresAt": "2026-07-28T10:30:00Z",
"remainingTtlSeconds": 83,
"extractedAt": "2026-07-28T10:28:37Z"
}
]
}
```
`returned` 必须等于 `proxies` 数组长度。`partial` 模式中 `returned` 可以为
零;这仍表示请求语法有效,只是当前没有可提取库存。
返回的 `password``url` 含真实凭据。Client 必须限制日志、追踪和错误上报
对响应体的采集。服务端访问日志只记录数量、过滤摘要、Client、Upstream 和
Request ID不记录地址或凭据。
## 4. 数量语义
### 4.1 partial
符合条件数量少于 `count` 时,原子提交实际数量:
```text
requested=10, eligible=6, reserve=0 -> returned=6
```
### 4.2 allOrNothing
锁定数量不足时整个事务回滚:
```json
{
"type": "https://proxy-pool.local/problems/insufficient-proxies",
"title": "Insufficient proxies",
"status": 409,
"code": "INSUFFICIENT_PROXIES",
"detail": "requested 10 proxies but only 6 are currently eligible",
"requestId": "req_01J4EXAMPLE"
}
```
冲突响应后,候选 Proxy 仍在 Redis TTL 活动池中保持 AVAILABLE。
## 5. 资格过滤与 Gateway 预留
候选必须同时满足:
1. Redis TTL 活动池条目仍为 AVAILABLE且当前时间早于按供应商安全余量计算的
`usableUntil`
2. 没有 Worker 所有权,或已完成 Drain 且 Active/Reserved 均为零。
3. 剩余 TTL 不低于 `minRemainingTTL`
4. 最近健康检查不早于 `maxHealthCheckAge`
5. protocol、region、carrier、Upstream 满足过滤与 Client 权限。
6. 提取后符合条件的共享库存不低于 `reserveForGateway`
Worker-owned Proxy 不得直接提取。Controller 需要先发布 DRAINING等待 Worker
确认没有 Active/Reserved再清除 Redis 中的所有权并进入原子提取。快照延迟时
仍禁止 Gateway 与 Client 同时获得同一 Proxy。
## 6. 并发与原子性
Redis Adapter 应通过单个 Lua 脚本、Redis Function 或等价的原子原语完成候选
筛选、TTL/健康/所有权复核、Gateway 预留计算、满足模式判断、从可分配池移除
所选条目,并写入短期幂等结果。提取过程中不访问 PostgreSQL。
关键不变量:
- 两个并发成功响应的 Proxy ID 集合交集为空。
- 原子操作失败时整个批次不返回,也不得留下部分移除结果。
- `allOrNothing` 不足时零个条目退出活动池。
- Redis 活动池不可用时返回 503不以内存副本冒充成功。
- PostgreSQL 不可用不阻断提取;需要 PostgreSQL 的 Admin 管理写入单独降级。
## 7. 幂等
提取是消耗库存的写操作。客户端收到超时后盲目重试可能再次提取一批不同
Proxy因此自动重试应提供稳定的 `Idempotency-Key`
服务端在 Redis 中保存有界 TTL 的幂等记录,至少包含 Client ID、Key、请求体
摘要、提交结果和过期时间。同一 Client、同一 Key、相同摘要返回首次结果
摘要不同返回 409。幂等结果必须和活动池状态变更处于同一个 Redis 原子操作。
TTL 到期或 Redis 数据丢失后不再保证旧 Key 去重,系统不回退到 PostgreSQL
保存逐次提取事实。
## 8. 认证、识别与限制
认证由部署配置决定OpenAPI 同时声明 API Key、Basic、Bearer 和无认证场景。
无认证并不关闭来源 CIDR、Client 识别和限流:
- 直连请求使用来源 IP 形成匿名 Client。
- 只有来源属于 `trustedProxies` 时才接受转发头。
- 全局和每 Client 限流在查询库存前执行。
- 过滤条件、数量、请求体和 Header 都有长度/数量上限。
## 9. 错误模型
所有非 2xx 响应使用 `application/problem+json`
- `400`JSON、Header 或基本格式无效。
- `401`:认证失败。
- `403`:来源控制、权限或 Upstream 访问被拒绝。
- `409`allOrNothing 库存不足,或幂等 Key 冲突。
- `413`:请求体超过 Distribution 配置上限。
- `415`:请求体不是 `application/json`
- `422`:数量、枚举或过滤组合违反业务约束。
- `429`:全局或 Client 速率限制,响应 `Retry-After`
- `503`Redis 活动池不可用、原子提取不可执行或服务正在排空。
- `500`:未分类的内部错误;响应不包含底层错误文本。
错误响应不得包含 Provider Secret、Proxy 凭据、SQL 或内部拓扑。
## 10. 短期运行记录与数据最小化
Redis 幂等结果只在配置的 TTL 窗口内保留重放响应所需的数据:
```text
clientId, idempotencyKey, requestDigest, response, expiresAt
```
无认证时 `clientId` 使用可信代理链解析后的规范化来源 IP 稳定标识。应用日志
仅记录 Request ID、数量、结果码和过滤摘要不记录 Proxy 地址或凭据。需要长期
分析时只向 PostgreSQL 写入不含 Proxy 明细的可选聚合指标;系统不创建或归档
逐个 Proxy 的 Extraction Record。
## 11. 运行时实现边界
`distribution.Handler` 是薄 HTTP Adapter只负责严格解码、Header/DTO 校验、
身份结果注入、错误映射和健康探针。独占提取、TTL、Gateway 预留及幂等事务
继续由 `extraction.Service` 和 Redis 活动池 Adapter 承担。当前内存 Store 仅作为
原子行为的参考实现与测试替身,不是生产持久化层。
请求体解码、Request ID 与 Problem JSON 统一复用 `platform/httpapi`。认证、
可信代理、来源控制、Client ID 和准入限流由必需的 `IdentityResolver` 注入,
标准装配使用 `httpsecurity.Protection`;解析结果至少包含稳定 Client ID 或
Source IP且安全检查先于请求体解析。`controller/runtime` 将 Distribution
与 Admin 装配到不同 `net.Listener`,健康端点保持公开,提取端点执行独立认证。