16 KiB
Proxy Pool 总体架构设计
1. 范围
Proxy Pool 是多供应商代理聚合平台,同时提供:
- Gateway:接受 HTTP/HTTPS CONNECT 请求,选择上游代理并代转发。
- Distribution API:一次性独占发放真实代理地址,发放后不再管理其使用。
- Admin API:查询状态、启停 Upstream、切换 Routing、触发重载。
- Control Plane:Provider 获取、健康、生命周期、容量、切换、持久化与分发。
首要容量目标是集群峰值 100,000 QPS。该数字是设计目标,必须通过后续 容量测试证明,不能由文档直接宣称实现。
2. 选择的方案
采用“独立数据面 + 集中控制面 + 独立健康执行器”:
flowchart LR
C[Clients] --> LB[HAProxy / Envoy / LVS]
LB --> G1[Gateway Worker]
LB --> GN[Gateway Worker N]
G1 --> PX[Owned Proxies]
GN --> PX
E[Extract Clients] --> D[Distribution API]
A[Operators] --> ADM[Admin API]
D --> CP[Controller]
ADM --> CP
CP --> PG[(PostgreSQL)]
CP --> R[(Redis)]
CP --> F[Provider APIs]
CP --> CK[Checker Workers]
CP --> G1
CP --> GN
2.1 为什么不用单体
Provider、数据库和健康探测是不可控 I/O。把它们和 Gateway 放在一个进程, 会让供应商抖动、配置重载和数据库故障直接污染 100k QPS 热路径。
2.2 为什么不全面微服务化
Provider、Pool、Routing、Distribution 在首版需要共享事务和一致性规则。 先保持 Controller 模块化单体,避免提前引入事件顺序和分布式事务;Checker 因负载特征不同独立扩缩,Gateway 因热路径独立部署。
3. 进程与职责
3.1 proxy-gateway
- Client 认证、CIDR 访问控制和租户限流。
- HTTP 正向代理与 HTTPS CONNECT。
- 首条命中 Routing、Upstream 选择和 Proxy least-connections 选择。
- 本地原子容量预留、建连、Active 计数和结果上报。
- 本地不可变 Snapshot;热路径无数据库/Redis/Provider API。
- SSRF 与 DNS Rebinding 防护。
3.2 proxy-controller
- 配置加载、校验、热更新和版本管理。
- Upstream/Provider fetch Leader、独立限流、singleflight 和退避。
- Proxy 解析、去重、TTL、状态机、pool.maxSize 与 fetch.maxTotal。
- Routing Sequential 当前选择和原子切换。
- Worker 所有权/容量切片、Snapshot 发布、ACK 和重同步。
- Distribution 与 Admin HTTP 接口。
- PostgreSQL 权威状态和 Redis 短期协调。
3.3 proxy-checker
- 消费检查任务,执行基础连接、出口和目标级探测。
- 使用 jitter、maxInFlight、超时和分级复检。
- 只上报 Observation,不直接修改最终状态。
- Controller 的确定性 reducer 根据 Observation 更新状态。
3.4 proxy-loadgen
- 分别生成 HTTP QPS、CONNECT 活跃连接、建连速率和 Extract 并发。
- 输出环境、场景、延迟、错误、CPU、RSS、句柄和网络结果。
4. 模块边界
domain/
proxy Proxy、状态机、TTL、唯一键、容量
routing 规则、匹配、策略与 Sequential 状态
upstream Provider 能力、Fetch 分类与 Pool 需求
extraction 独占提取命令、结果和审计事实
client 认证主体、权限与限制
gateway/
ingress HTTP/CONNECT 接入
dispatch Routing、Proxy 选择、容量预留、重试资格
transport 上游连接复用、握手、隧道和失败阶段
snapshot 版本校验、后台构建和原子切换
controller/
provider Fetch 计划、限流、singleflight、错误分类
pool Pool reconcile、生命周期和所有权
health 任务计划和 Observation reducer
routing Sequential 原子切换和恢复
extraction 批量原子提取与审计
distribution Worker 注册、Snapshot/Delta 与结果上报
adapters/
postgres, redis, grpc, http, provider_template
领域模块不导入 HTTP、gRPC、SQL、Redis 或模板引擎。Adapter 依赖领域接口,
进程装配只发生在 cmd。
5. Proxy 模型
type Proxy struct {
ID ProxyID
Scheme Scheme
Host string
Port uint16
Username string
CredentialVersion string
SecretRef string
SourceUpstream UpstreamID
CreatedAt time.Time
ExpiresAt *time.Time
LastCheckedAt *time.Time
LastSuccessAt *time.Time
Latency time.Duration
MaxConcurrency int64
State ProxyState
Tags map[string]string
}
密码不参与日志可见唯一键;CredentialVersion 区分同一用户名的凭据轮换。
运行态 active 与 reserved 存在 Worker 本地、按 Proxy ID 分片,不写入
不可变 Snapshot。
6. Proxy 状态机
stateDiagram-v2
[*] --> FETCHED
FETCHED --> CHECKING
CHECKING --> AVAILABLE: basic check passed
CHECKING --> UNHEALTHY: check exhausted
AVAILABLE --> SUSPECT: first meaningful failure
SUSPECT --> AVAILABLE: recheck passed
SUSPECT --> UNHEALTHY: consecutive failures
AVAILABLE --> DRAINING: TTL margin / disable / ownership revoke
DRAINING --> EXPIRED: no active or reserved capacity
AVAILABLE --> EXTRACTED: atomic exclusive extraction
UNHEALTHY --> REMOVED
EXTRACTED --> EXPIRED: expires
EXPIRED --> REMOVED
ACTIVE 与 BUSY 不是持久状态,而是容量计数。一个 AVAILABLE Proxy 可以
同时承载多个 Gateway 请求,直到有效并发上限。
7. Gateway 热路径
sequenceDiagram
participant C as Client
participant G as Gateway
participant D as Dispatch
participant T as Transport
participant P as Upstream Proxy
C->>G: HTTP / CONNECT
G->>G: auth + access + admission
G->>D: RouteRequest
D->>D: first-match route + local candidate filter
D->>D: CAS reserved +1
D-->>G: Allocation
G->>T: execute allocation
T->>P: dial / proxy handshake
alt connected
T->>D: reserved -1, active +1
T-->>C: response / tunnel
T->>D: active -1 + outcome
else failed before commit
T->>D: reserved -1 + failure
G->>D: optional safe retry with exclusion
end
7.1 Dispatch 接口
type Dispatcher interface {
Acquire(context.Context, RouteRequest) (*Allocation, error)
Commit(*Allocation) error
Release(*Allocation, Outcome)
}
不变量:
Acquire只读本地 Snapshot 与本地分片运行态。Allocation创建前必须 CAS 预留成功。- Commit 将 Reserved 恰好一次转换为 Active。
- 未 Commit 的失败释放 Reserved;已 Commit 的结束释放 Active。
- Release 重复调用安全但产生错误指标。
7.2 重试提交点
- HTTP:响应头写给 Client 前可按方法与失败阶段重试。
- CONNECT:上游 CONNECT 成功且向 Client 写 200 后不可重试。
- GET/HEAD 默认最多尝试 2 个不同 Proxy。
- POST/PUT/PATCH/DELETE 默认不自动重试。
8. Provider Fetch
flowchart TD
S[Capacity signal] --> SF{singleflight running?}
SF -->|yes| W[Coalesce signal]
SF -->|no| R[Read current demand]
R --> L[Acquire Provider leader]
L --> C{Under maxSize/maxTotal?}
C -->|no| X[Stop]
C -->|yes| I[Wait requestInterval]
I --> M[Acquire maxInFlight]
M --> API[Call Provider API]
API --> CL{Classify}
CL -->|error| BO[Backoff + fetchErrorCount]
CL -->|empty| EC[emptyCount++]
CL -->|valid| DD[Deduplicate + reset empty]
DD --> HC[Create FETCHED and schedule check]
8.1 Empty、Duplicate 与 Error
- Empty:HTTP/认证成功、模板执行成功,解析后合法 Proxy 数为 0。
- Duplicate-only:合法 Proxy 数大于 0,但去重后新增为 0;不计 Empty, 重置 Empty 连续计数并增加 duplicate 指标。
- Error:超时、DNS、非预期 HTTP、认证、响应超限、模板或解析异常; Empty 计数保持不变,增加 Error 并进入退避。
- Success:至少一个合法候选;Empty 计数归零。是否最终入池由去重、 maxSize、TTL 和健康结果决定。
9. Sequential 切换
sequenceDiagram
participant F as Provider Controller
participant U as Upstream Runtime
participant R as Routing Runtime
participant DB as PostgreSQL
F->>U: EmptyFetch
U->>U: consecutiveEmpty++
alt below threshold
U-->>F: continue current
else threshold reached
U->>DB: persist depleted generation
DB-->>R: notify affected routings
R->>DB: CAS current index A -> B
DB-->>R: switched once
R->>R: old A proxies drain naturally
end
Upstream 的 Empty 事实全局共享;每条 Routing 独立 CAS 当前索引。多个并发 协程只能有一个成功从 A 切到 B,其他协程读取新版本,不会再切到 C。
10. Exclusive Extraction
10.1 核心流程
sequenceDiagram
participant C as Extract Client
participant API as Distribution API
participant CP as Controller
participant W as Gateway Worker
participant DB as PostgreSQL
C->>API: POST extract(count, filters)
API->>API: auth + access + limits
API->>CP: Extract command
CP->>DB: select eligible unowned proxies
alt candidate owned by Worker
CP->>W: revoke and drain
W-->>CP: active=0, reserved=0, ownership released
end
CP->>DB: transaction AVAILABLE -> EXTRACTED
DB->>DB: insert extraction records
DB-->>CP: committed rows
CP-->>API: proxy + expiry
API-->>C: requested/returned/proxies
10.2 原子批量提取
PostgreSQL Adapter 使用事务和 FOR UPDATE SKIP LOCKED 选择满足以下条件的
行,并在同一事务更新状态与写审计记录:
- State 为 AVAILABLE。
- 未分配 Worker 所有权,Reserved/Active 为 0。
- 剩余 TTL 不低于
minRemainingTTL。 - 健康检查时间不早于
maxHealthCheckAge。 - 提取后仍保留
reserveForGateway。 - 符合 protocol、region、carrier 与 allowedUpstreams。
partial 提交实际可得数量;allOrNothing 在锁定数量不足时回滚。
10.3 与 Gateway 共池
Controller 优先维护两类库存:Worker-owned Gateway 容量和 unowned Extract 库存。Distribution 只直接提取 unowned Proxy。需要从 Gateway 回收时,先 发布 DRAINING、等待 Worker ACK 和容量归零,再清除所有权并提取。这样避免 Snapshot 传播延迟造成同一 Proxy 同时被 Gateway 新分配和 API 发放。
11. Cluster 所有权
flowchart LR
CP[Controller] -->|capacity lease| W1[Worker 1]
CP -->|capacity lease| W2[Worker 2]
P1[Proxy set A] --> W1
P2[Proxy set B] --> W2
U[Unowned extract reserve] --> CP
- 同一个 Proxy 同一时刻只归一个 Worker 所有。
- Worker 仅对自身 Proxy 做本地原子计数。
- 所有权租约有 epoch 与过期时间;Worker 失联后先等待租约过期,再分配给 其他 Worker,避免双主。
- Snapshot 包含 Worker 专属 Proxy 集、Routing、策略和版本校验和。
- Worker 断开控制面后在
maxStaleAge内使用最后快照;超限停止接收新流量, 已有隧道排空。
12. 存储与一致性
12.1 PostgreSQL
权威保存:配置版本、Upstream、Proxy 生命周期、Routing 当前选择、Worker 所有权、Extraction Record、Client、审计与 outbox。
12.2 Redis
保存可重建短期状态:Provider Leader 租约、分布式限流、singleflight 信号、 短期 Client 限流和 Worker 心跳。Redis 不保存唯一权威业务状态。
12.3 Outbox
任何需要发布 Snapshot/事件的 PostgreSQL 状态更新同时写 outbox。发布成功 后标记;Worker 使用 epoch/version 幂等应用,缺口触发完整 Snapshot。
13. 健康模型
Health Observation 包含 Proxy、检查层级、Routing/目标组、阶段、结果、延迟、 出口信息与时间。Reducer 规则:
- 新 Proxy 必须通过基础检查才可 AVAILABLE。
- 第一次有意义失败进入 SUSPECT,不立即删除。
- 连续失败达到配置阈值进入 UNHEALTHY。
- SUSPECT 复检成功恢复 AVAILABLE。
- 目标级失败只影响对应 Routing/Target Profile,不直接全局删除。
- 任务按稳定哈希分散,并对 interval 加 jitter。
14. 配置热更新
flowchart LR
F[Read file] --> P[Parse]
P --> V[Validate references and regex]
V --> B[Build immutable config]
B --> D[Diff old/new]
D --> S[Atomic swap]
S --> G[Drain removed tasks/resources]
热更新规则:
- 删除/禁用 Upstream:停止 Fetch 和新分配,已有 Proxy/连接 Drain。
- 减小 maxSize:不强杀连接,停止补池并按策略自然缩容。
- Routing 立即对新请求生效;旧请求持有旧 Snapshot 完成。
- 修改 API 地址或凭据版本会重建 Provider Adapter,但不会把错误计成 Empty。
- 新配置任何校验失败时,保留旧版本并报告完整错误。
15. 安全
- Gateway、Distribution、Admin 认证相互独立。
auth.mode支持 none、usernamePassword、apiKey、bearer、ipWhitelist 与组合 any。access.allowCIDRs独立于认证;代理头只在来源属于 trustedProxies 时接受。- 严格模式下,非回环监听且 auth=none、allowCIDRs 为空时启动失败。
- 目的地址解析前后都拒绝 loopback、private、link-local、metadata 和配置禁区。
- Secret 使用环境变量或文件引用;日志统一 Secret 类型脱敏。
- Provider 模板只能使用白名单纯函数,禁止文件、网络和环境变量读取。
- Distribution 返回真实凭据属于 Raw 弱控制模式,文档必须明确其风险。
16. 可观测性
关键指标:
- Gateway 请求、连接、建连、字节、延迟、重试和拒绝。
- Routing 当前 Upstream、切换、无可用和策略选择。
- Upstream Proxy 状态、Available Slots、Active、Reserved、Pending Fetch。
- Provider Fetch 成功、Empty、Duplicate、Error、429、退避和耗时。
- Extraction requested、returned、insufficient、atomic conflict 和审计失败。
- Snapshot epoch/version、陈旧时长、应用耗时和重同步。
Prometheus 标签不包含 Proxy IP、Client ID、session、完整 URL 或 request ID。 这些信息进入受控、脱敏、可采样日志。
17. 性能与容量
17.1 集群容量公式
worker_replicas =
ceil(peak_qps / (tested_worker_qps * target_utilization))
+ largest_failure_domain_replicas
默认 peak_qps=100000、target_utilization<=0.60。单 Worker 能力必须在相同
CPU、内存、网络、Go 版本、配置和上游响应模型下测得。
17.2 热路径预算
- Dispatch 无 I/O、无全局锁;100k Proxy Snapshot 下的设计预算为 p99 小于 100 微秒,仍需分位数基准验证。
- 所有队列、buffer、重试和日志均有界。
- Listener、Client、Routing、Worker 和 Proxy 均有独立准入限制。
- 过载在路由/建连前快速拒绝,不允许请求堆积耗尽内存。
- 结果上报批量、异步、有界,不反压请求热路径。
17.3 独立场景
必须分别测试:10k 稳态 QPS、100k 峰值 QPS、活跃 CONNECT、建连速率、 大 Snapshot 更新、Provider 故障、Controller 断线和 Worker 故障域丢失。
18. 故障行为
| 故障 | 行为 |
|---|---|
| Provider 超时/500 | 计 Error、退避;不计 Empty,不影响已有 Proxy |
| Provider 合法空响应 | Empty++;达到阈值触发相关 Routing 原子切换 |
| Redis 不可用 | Gateway 不受影响;Controller 使用本地退化并停止高风险写操作 |
| PostgreSQL 不可用 | Gateway 使用最后快照;停止 Extract 和权威状态变更 |
| Controller 断线 | Worker 在 maxStaleAge 内继续;超限拒绝新流量并排空 |
| Worker 崩溃 | 所有权租约过期后重新分配;期间不双重所有 |
| Checker 积压 | 降低普通复检频率,优先新 Proxy 与 SUSPECT,不无限排队 |
| Snapshot 缺版本 | 丢弃增量并请求完整 Snapshot |
| 所有 Upstream 不可用 | 执行显式 onUnavailable,默认 reject |
19. 交付边界
项目架构必须包含四个命令、领域模块、Gateway/Controller/Checker 模块、 存储/协议 Adapter、OpenAPI/Proto、配置样例、Compose/Kubernetes、监控、 迁移、测试 fixture、负载场景和开发文档。目录存在但没有契约或测试不算完成。