网络编程如何处理重试?

访客 网络编程 1

本文目录导读:

  1. 核心原则
  2. 常见重试策略
  3. 重试边界条件:哪些错误值得重试?
  4. 高级重试模式
  5. 工程实现示例(Python)

网络编程中处理重试是一个常见的挑战,因为网络环境不可靠(如超时、丢包、服务暂时不可用),设计良好的重试机制可以显著提高系统的健壮性,但设计不当(如无限制重试)可能导致雪崩效应资源浪费

以下是处理网络重试的核心原则、常见策略及代码示例:

核心原则

  1. 幂等性:这是重试的前提条件,如果操作本身不是幂等的(创建订单、扣款),重试可能导致业务数据错误,对于非幂等操作,需要在上层设计去重机制(如请求ID)。
  2. 退避策略:不要立即重试,要有一定的等待时间,给服务恢复的时间。
  3. 有限次重试:设定最大重试次数,避免无限循环。
  4. 抖动:在退避时间上增加随机性,防止所有客户端在同一时间重试,导致服务器再次崩溃(惊群效应)。
  5. 超时管理:每一次重试都应该有独立的超时时间,且总耗时不能过长。

常见重试策略

固定间隔重试

最简单,但效果较差。

  • 逻辑:每次重试等待相同的时间(如 1秒)。
  • 缺点:如果服务需要较长时间恢复,固定间隔会浪费客户端和服务器资源。

指数退避

最常用且经典。

  • 逻辑:等待时间随重试次数指数增长,例如第1次等 100ms,第2次等 200ms,第3次等 400ms... (2^n * baseInterval)。

  • 代码示例 (Go 语言)

    import (
        "fmt"
        "time"
        "net/http"
    )
    func doRequestWithRetry(url string, maxRetries int) (*http.Response, error) {
        var resp *http.Response
        var err error
        for i := 0; i < maxRetries; i++ {
            resp, err = http.Get(url) // 实际中建议复用Client
            if err == nil {
                return resp, nil
            }
            // 指数退避并添加抖动
            sleepDuration := time.Duration(1 << uint(i)) * time.Second // 2^i 秒
            jitter := time.Duration(rand.Intn(100)) * time.Millisecond
            fmt.Printf("Attempt %d failed: %v. Retrying in %v...\n", i+1, err, sleepDuration+jitter)
            time.Sleep(sleepDuration + jitter)
        }
        return nil, fmt.Errorf("all %d retries failed: %w", maxRetries, err)
    }

指数退避 + 随机抖动 (Jitter)

在指数退避基础上增加了随机延迟,避免惊群。

  • 逻辑sleep = min(cap, base * 2^attempt) + random(0, jitter_base)

  • 示例 (Python with tenacity 库)

    import tenacity
    import requests
    import random
    @tenacity.retry(
        stop=tenacity.stop_after_attempt(3),  # 最多重试3次
        wait=tenacity.wait_exponential(multiplier=1, min=1, max=10) + tenacity.wait_random(min=0, max=1),
        retry=tenacity.retry_if_exception_type(requests.exceptions.ConnectionError)
    )
    def fetch_data(url):
        response = requests.get(url, timeout=5)
        response.raise_for_status()
        return response.json()

增量退避

  • 逻辑:每次增加一个固定步长(如 100ms, 200ms, 300ms...),较少使用,适用于网络抖动但服务器能快速恢复的场景。

重试边界条件:哪些错误值得重试?

不是所有错误都适合重试,需要区分错误类型:

✅ 应该重试的错误(临时性、可恢复):

  • 网络超时Timeout
  • 连接被拒绝Connection refused,通常是服务刚重启或重启中)
  • DNS 解析失败DNS lookup failed
  • HTTP 5xx 状态码 (服务器错误,如 503 Service Unavailable, 502 Bad Gateway, 500 Internal Server Error)

❌ 不应该重试的错误(需要人工介入或逻辑错误):

  • HTTP 4xx 状态码 (客户端错误):4xx 意味着请求本身有问题(如 401 未授权, 403 禁止访问, 404 不存在, 422 参数校验失败),重试 100 次还是 404,不会改变结果。
  • 连接已断开broken pipeconnection reset 在特定业务下可能重试,但需谨慎)。

高级重试模式

断路器模式 (Circuit Breaker)

防止在服务明显不可用时,客户端仍不断重试导致雪崩(重试雪崩)。

  • 状态Closed(正常) -> Open(故障率高,立即拒绝请求) -> Half-Open(尝试放行一个请求看是否恢复)。
  • 实现:在累加连续失败次数超过阈值后,直接失败,不再发起网络请求。

基于预算的重试 (Retry Budget)

限制在一个时间窗口内(1 分钟)允许的总重试次数(如最多重试总请求数的 10%),避免重试流量淹没系统。

异步重试

  • 同步重试:调用方等待重试结果(适合短时故障)。
  • 异步重试(如消息队列):将请求放入队列,由后台 Worker 负责重试,业务方无需阻塞等待,适合长时间跨度的重试(如支付宝支付失败后,系统会在 30 分钟内自动重试若干次)。

工程实现示例(Python)

使用 tenacity 库实现一个健壮的重试辅助函数:

import requests
import tenacity
from tenacity import stop_after_attempt, wait_exponential, retry_if_exception, before_sleep_log
import logging
logging.basicConfig(level=logging.INFO)
# 定义哪些异常需要重试
def is_retryable_exception(exception):
    # 只重试连接错误和超时,不重试4xx
    if isinstance(exception, requests.exceptions.ConnectionError):
        return True
    if isinstance(exception, requests.exceptions.Timeout):
        return True
    if isinstance(exception, requests.exceptions.HTTPError):
        if exception.response.status_code in (502, 503, 504):
            return True
    return False
@tenacity.retry(
    stop=stop_after_attempt(3),  # 重试3次
    wait=wait_exponential(multiplier=1, min=1, max=10),  # 1, 2, 4秒后重试
    retry=retry_if_exception(is_retryable_exception),  # 只有可重试异常才重试
    before_sleep=before_sleep_log(logging.getLogger("retry"), logging.WARNING),
    reraise=True  # 重试耗尽后抛出原始异常
)
def fetch_critical_data(url, headers):
    response = requests.get(url, headers=headers, timeout=(3, 10))  # connect_timeout=3s, read_timeout=10s
    response.raise_for_status()
    return response.json()
维度 推荐做法
重试前提 必须幂等或设计去重机制(请求ID)
错误判断 只重试网络超时、连接拒绝、5xx;不重试4xx
等待策略 指数退避 + 随机抖动 (最通用)
最大次数 2-5 次(取决于业务容忍度),通常不超过 3 次
并发保护 结合断路器重试预算,避免雪崩
幂等实现 请求体中包含 idempotency_key(幂等键),服务端校验去重

最后一点建议:在设计重试时,请始终反问自己:“如果这次重试触发了服务器故障恢复的高负载峰值,系统能否承受?” 如果能加上熔断和限流,重试机制会更安全。

标签: 超时重试

抱歉,评论功能暂时关闭!