17 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 TTL 活动池。
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 需求
activitypool 短效 Proxy、TTL、所有权和独占消费
extraction 独占提取命令与结果
client 认证主体、权限与限制
gateway/
ingress HTTP/CONNECT 接入
dispatch Routing、Proxy 选择、容量预留、重试资格
transport 上游连接复用、握手、隧道和失败阶段
snapshot 版本校验、后台构建和原子切换
controller/
provider Fetch 计划、限流、singleflight、错误分类
pool Pool reconcile、生命周期和所有权
health 任务计划和 Observation reducer
routing Sequential 原子切换和恢复
extraction Redis 活动池批量原子提取与短期幂等
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
UsableUntil *time.Time
LastCheckedAt *time.Time
LastSuccessAt *time.Time
Latency time.Duration
MaxConcurrency int64
State ProxyState
Tags map[string]string
}
密码不参与日志可见唯一键;CredentialVersion 区分同一用户名的凭据轮换。
唯一键不包含 Upstream:多个供应商返回同一唯一键时,当前生命周期的首个来源
保持归属,其他来源只计重复且不得覆盖 TTL;原来源硬过期淘汰后可重新归属。
运行态 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 R as Redis Activity Pool
C->>API: POST extract(count, filters)
API->>API: auth + access + limits
API->>CP: Extract command
CP->>R: atomic select eligible unowned proxies
alt candidate owned by Worker
CP->>W: revoke and drain
W-->>CP: active=0, reserved=0, ownership released
end
CP->>R: AVAILABLE -> EXTRACTED + bounded idempotency result
R-->>CP: consumed proxies
CP-->>API: proxy + expiry
API-->>C: requested/returned/proxies
10.2 原子批量提取
Redis Adapter 使用单个 Lua 脚本或等价原子命令,选择满足以下条件的活动池 条目,并同时完成状态迁移和带 TTL 的幂等结果写入:
- State 为 AVAILABLE。
- 未分配 Worker 所有权,Reserved/Active 为 0。
- 剩余 TTL 不低于
minRemainingTTL。 - 健康检查时间不早于
maxHealthCheckAge。 - 提取后仍保留
reserveForGateway。 - 符合 protocol、region、carrier 与 allowedUpstreams。
partial 原子消费实际可得数量;allOrNothing 数量不足时不改变任何条目。
Proxy 地址、凭据和逐次提取明细不写 PostgreSQL。
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、策略、
ExpiresAt、UsableUntil和版本校验和。Worker 在UsableUntil到达后立即停止新分配, 不等待供应商硬过期时间ExpiresAt。 - Worker 断开控制面后在
maxStaleAge内使用最后快照;超限停止接收新流量, 已有隧道排空。
12. 存储与一致性
12.1 PostgreSQL
持久化配置版本、Upstream/Routing 管理状态、Admin 审计和管理事件 outbox, 以及可选的不含 Proxy 明细的聚合指标。PostgreSQL 不保存 Proxy 地址、凭据、 生命周期、Worker 所有权或逐次提取记录。
12.2 Redis
保存带 TTL 的短效 Proxy 活动池、生命周期、Worker 所有权、独占提取状态和 短期幂等结果,并承载 Provider Leader、分布式限流、singleflight 信号、 短期 Client 限流和 Worker 心跳。Redis 丢失后由 Provider 重新获取代理重建, 不会从 PostgreSQL 恢复旧 Proxy。
12.3 Outbox
管理状态变更在 PostgreSQL 事务内同时写 outbox。活动池变更通过 Redis 事件流 通知 Controller 构建 Snapshot,不跨 PostgreSQL/Redis 双写 Proxy;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 暂用未过期快照;停止 Fetch 入池、Extract 和所有权变更 |
| PostgreSQL 不可用 | Gateway 与 Redis Extract 不受影响;停止管理状态变更和 Admin 审计 |
| Controller 断线 | Worker 在 maxStaleAge 内继续;超限拒绝新流量并排空 |
| Worker 崩溃 | 所有权租约过期后重新分配;期间不双重所有 |
| Checker 积压 | 降低普通复检频率,优先新 Proxy 与 SUSPECT,不无限排队 |
| Snapshot 缺版本 | 丢弃增量并请求完整 Snapshot |
| 所有 Upstream 不可用 | 执行显式 onUnavailable,默认 reject |
19. 交付边界
项目架构必须包含四个命令、领域模块、Gateway/Controller/Checker 模块、 存储/协议 Adapter、OpenAPI/Proto、配置样例、Compose/Kubernetes、监控、 迁移、测试 fixture、负载场景和开发文档。目录存在但没有契约或测试不算完成。