第三方接口调用规范
超时、重试、熔断、降级
[Requirement] 所有第三方调用必须设置连接超时和读取超时,禁止使用默认无限超时。
| 超时类型 | 建议值 | 说明 |
|---|---|---|
| 连接超时(Connect) | 1~3 秒 | 建立 TCP 连接的时间 |
| 读取超时(Read) | 3~10 秒 | 等待响应数据的时间 |
| 整体超时(总) | 不超过 10 秒 | 单次调用总耗时上限 |
[Standard] 超时时间按接口 SLA 配置,慢接口可适当放宽但必须有上限;禁止为”调通”而设置超大超时。
import httpx
# 正确:分层超时
client = httpx.Client(
timeout=httpx.Timeout(
connect=2.0,
read=5.0,
write=2.0,
pool=2.0,
),
limits=httpx.Limits(max_connections=20, max_keepalive_connections=5),
)// OkHttp 示例
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(2, TimeUnit.SECONDS)
.readTimeout(5, TimeUnit.SECONDS)
.writeTimeout(2, TimeUnit.SECONDS)
.callTimeout(10, TimeUnit.SECONDS) // 总超时
.build();# 反模式:无超时
requests.get(url) # 可能永久阻塞
requests.get(url, timeout=300) # 超时过大,拖垮线程池[Standard] 仅对幂等接口和瞬时错误重试,写操作(POST/PUT/PATCH)默认不重试,除非接口支持幂等键。
| 错误类型 | 是否重试 | 说明 |
|---|---|---|
| 网络超时、连接拒绝 | 是 | 瞬时网络问题 |
| HTTP 5xx | 是 | 服务端故障 |
| HTTP 408 / 429 | 是(带退避) | 超时/限流 |
| HTTP 4xx(除 408/429) | 否 | 参数/权限错误,重试无意义 |
| 业务错误码 | 否 | 业务逻辑问题 |
[Requirement] 重试必须使用指数退避 + 抖动(Exponential Backoff with Jitter),禁止固定间隔重试。
重试间隔 = min(base * 2^retry_count, max_interval) + random_jitter[Default] 默认重试 3 次,初始间隔 100ms,最大间隔 2s,总耗时不超过 5s。
import random, time
def call_with_retry(func, max_retries=3, base_delay=0.1, max_delay=2.0):
for attempt in range(max_retries + 1):
try:
return func()
except (TimeoutError, ConnectionError) as e:
if attempt == max_retries:
raise
delay = min(base_delay * (2 ** attempt), max_delay)
delay += random.uniform(0, delay * 0.1) # 抖动
time.sleep(delay)[Caution] 对写接口重试必须携带幂等键(Idempotency-Key),由调用方生成唯一 UUID,服务端据此去重。
POST /api/v1/orders HTTP/1.1
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000[Standard] 使用熔断器(Circuit Breaker)防止级联故障,状态机三态:Closed → Open → Half-Open。
| 状态 | 行为 | 触发条件 |
|---|---|---|
| Closed(关闭) | 正常调用 | 默认状态 |
| Open(打开) | 直接拒绝,快速失败 | 错误率超阈值(如 50%,近 10s 内 ≥20 次请求) |
| Half-Open(半开) | 放行少量探测请求 | Open 持续冷却时间(如 30s)后 |
[Default] 熔断器配置:错误率阈值 50%、最小请求数 20、统计窗口 10s、冷却时间 30s、半开探测请求数 3。
# pybreaker 示例
import pybreaker
breaker = pybreaker.CircuitBreaker(
fail_max=10, # 连续失败次数
reset_timeout=30, # 冷却时间(秒)
state_storage=pybreaker.CircuitMemoryStorage(),
)
@breaker
def call_remote_payment(order):
return payment_client.pay(order)[Standard] 熔断打开时调用方应捕获 CircuitBreakerError,走降级逻辑,不应向上抛出导致整个请求失败(除非核心链路)。
[Requirement] 非核心依赖必须有降级方案,核心依赖降级后需保证最终一致(通过消息队列异步补偿)。
| 降级类型 | 适用场景 | 实现 |
|---|---|---|
| 兜底数据 | 查询类 | 返回缓存/默认值 |
| 异步补偿 | 写操作 | 失败入 MQ,后台重试 |
| 功能开关 | 非核心功能 | 关闭该功能,主流程继续 |
| 限流排队 | 高并发 | 返回”稍后重试”,削峰 |
[Standard] 查询接口优先使用缓存兜底,缓存未命中再调用第三方;第三方失败时返回缓存(即使略旧)。
def get_user_profile(user_id):
cache_key = f"user:profile:{user_id}"
cached = redis.get(cache_key)
if cached:
return json.loads(cached)
try:
profile = user_client.get(user_id, timeout=2.0)
redis.setex(cache_key, 300, json.dumps(profile))
return profile
except Exception as e:
logger.warning("user service down, returning empty profile: %s", e)
return {"user_id": user_id, "nickname": "用户", "avatar": default_avatar}[Standard] 对核心写操作(如支付通知、订单同步),第三方失败时写入本地 MQ/数据库,由后台任务异步重试,保证最终一致。
def sync_order_to_erp(order):
try:
erp_client.create_order(order)
except Exception:
# 写入补偿表,后台扫描重试
CompensationTask.objects.create(
type="erp_order_sync",
payload=json.dumps(order),
retry_count=0,
next_retry_at=datetime.now() + timedelta(minutes=1),
)[Requirement] 每次第三方调用必须记录:调用方、被调方接口、HTTP 状态码、耗时、成功/失败、异常类型。
[Standard] 指标维度:service_name、endpoint、status_code、is_success,上报到 Prometheus。
[Standard] 告警规则:
| 指标 | 阈值 | 级别 |
|---|---|---|
| 错误率 | > 5%(5 分钟窗口) | 警告 |
| 错误率 | > 20%(1 分钟窗口) | 紧急 |
| P99 耗时 | > SLA 的 2 倍 | 警告 |
| 熔断器打开 | 任一熔断器 | 警告 |
[Standard] 调用日志必须包含 trace_id、第三方接口名、耗时、请求参数(脱敏)、响应码、异常堆栈。
[Caution] 日志中禁止打印第三方返回的敏感信息(如用户隐私、密钥、完整身份证)。
[Default] 对关键第三方依赖提供 /health/upstream 端点,聚合各依赖的连通性与延迟,供运维监控。