478 lines
18 KiB
Markdown
478 lines
18 KiB
Markdown
# Proxy Pool 总体架构设计
|
||
|
||
## 1. 范围
|
||
|
||
Proxy Pool 是多供应商代理聚合平台,同时提供:
|
||
|
||
- **Gateway**:接受 HTTP/HTTPS CONNECT 请求,选择上游代理并代转发。
|
||
- **Distribution API**:一次性独占发放真实代理地址,发放后不再管理其使用。
|
||
- **Admin API**:查询状态、启停 Upstream、切换 Routing、触发重载。
|
||
- **Control Plane**:Provider 获取、健康、生命周期、容量、切换、持久化与分发。
|
||
|
||
首要容量目标是集群峰值 100,000 QPS。该数字是设计目标,必须通过后续
|
||
容量测试证明,不能由文档直接宣称实现。
|
||
|
||
## 2. 选择的方案
|
||
|
||
采用“独立数据面 + 集中控制面 + 独立健康执行器”:
|
||
|
||
```mermaid
|
||
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. 模块边界
|
||
|
||
```text
|
||
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 模型
|
||
|
||
```go
|
||
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 状态机
|
||
|
||
```mermaid
|
||
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 热路径
|
||
|
||
```mermaid
|
||
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 接口
|
||
|
||
```go
|
||
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
|
||
|
||
```mermaid
|
||
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{Below refill target and 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]
|
||
```
|
||
|
||
每个 Upstream 的 Leader、最小请求间隔和在途 Permit 由同一个 Redis 原子协调
|
||
模块维护。Leader 租约使用 generation + 单调 epoch fence;Redis 状态整体丢失后
|
||
生成新 generation 并重新竞选。任何续租不确定、记录损坏或 Redis 断连均
|
||
fail-closed,不回退为本地 Leader。补池使用 minimum/target 双水位迟滞,库存
|
||
复核期间若仍有 pending Fetch,则等待下一轮再同步 Managed,避免重复计数。
|
||
|
||
### 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 切换
|
||
|
||
```mermaid
|
||
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。
|
||
Sequential 至少配置两个 Upstream;列表耗尽后的默认行为是 `stop`,`loop` 和
|
||
`stayLast` 必须显式配置。disabled Upstream 不参与新分配,其运行时跳过与权威
|
||
游标持久化仍由后续 Routing Runtime 完成。
|
||
|
||
## 10. Exclusive Extraction
|
||
|
||
### 10.1 核心流程
|
||
|
||
```mermaid
|
||
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 所有权
|
||
|
||
```mermaid
|
||
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. 配置热更新
|
||
|
||
```mermaid
|
||
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 集群容量公式
|
||
|
||
```text
|
||
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、负载场景和开发文档。目录存在但没有契约或测试不算完成。
|