proxy-pool/docs/design/architecture.md

16 KiB
Raw Blame History

Proxy Pool 总体架构设计

1. 范围

Proxy Pool 是多供应商代理聚合平台,同时提供:

  • Gateway:接受 HTTP/HTTPS CONNECT 请求,选择上游代理并代转发。
  • Distribution API:一次性独占发放真实代理地址,发放后不再管理其使用。
  • Admin API:查询状态、启停 Upstream、切换 Routing、触发重载。
  • Control PlaneProvider 获取、健康、生命周期、容量、切换、持久化与分发。

首要容量目标是集群峰值 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 区分同一用户名的凭据轮换。 运行态 activereserved 存在 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

ACTIVEBUSY 不是持久状态,而是容量计数。一个 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

  • EmptyHTTP/认证成功、模板执行成功,解析后合法 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=100000target_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、负载场景和开发文档。目录存在但没有契约或测试不算完成。