重试策略怎么配置?

wen python案例 3

本文目录导读:

重试策略怎么配置?

  1. 核心原则(无论哪种工具都要考虑)
  2. 常见工具/框架的配置方法
  3. 一个完整的通用配置示例(伪代码)
  4. 避坑指南

重试策略的配置通常取决于你使用的具体工具、框架或云服务(如 HTTP 客户端、消息队列、数据库连接池、云 SDK 等),不同的场景下,配置方式差异很大。

为了给你最准确的信息,我将最常见的几种场景分类说明,你可以根据你的技术栈对号入座。

核心原则(无论哪种工具都要考虑)

在配置重试策略前,建议先明确以下三个参数:

  1. 最大重试次数 (Max Retries):允许重试的总次数,防止无限重试耗尽资源。
  2. 重试间隔策略 (Backoff Strategy)
    • 固定间隔:每次等待相同时间(如 2 秒),容易造成“惊群效应”。
    • 增量间隔:每次递增等待时间(如 2s, 4s, 6s)。
    • 指数退避:每次翻倍(如 1s, 2s, 4s, 8s),最常用且对系统最友好。
    • 随机抖动:在间隔基础上加一个随机值,强烈推荐用于避免多个请求同时重试导致服务雪崩。
  3. 重试条件 (Retryable Conditions)不要对所有错误都重试。
    • 应该重试:网络超时(Timeout)、服务端错误(5xx,尤其是 500/503/502)、连接被拒绝 (Connection Refused)。
    • 不应该重试:客户端错误(4xx,尤其是 400 坏请求、401 未授权、403 禁止、404 不存在)、资源冲突(409 重复提交)或你想手动终止的请求。

常见工具/框架的配置方法

HTTP 客户端(最常用)

Python(使用 requestsurllib3

# 使用 retry 库(需安装:pip install retry)
from retry import retry
import requests
@retry(exceptions=requests.exceptions.RequestException, tries=3, delay=2, backoff=2, jitter=(1, 3))
def fetch_data(url):
    response = requests.get(url, timeout=5)
    response.raise_for_status()  # 触发重试
    return response.json()
# 参数:tries=3 次尝试,delay=2 秒初始等待,backoff=2 指数因子,jitter 加入随机抖动
# 或使用 urllib3 的 Retry 类
from urllib3.util.retry import Retry
retry_strategy = Retry(
    total=3,                    # 总重试次数
    backoff_factor=0.5,         # 间隔基数(公式:{backoff factor} * (2 ** ({number of retries} - 1)))
    status_forcelist=[500, 502, 503, 504],  # 只对服务端错误重试
    allowed_methods=["GET", "POST"]          # 只对幂等方法重试
)
# 然后配置到 requests.Session 中
from requests.adapters import HTTPAdapter
adapter = HTTPAdapter(max_retries=retry_strategy)
session = requests.Session()
session.mount("http://", adapter)
session.mount("https://", adapter)

Java(使用 Spring Retry 或 OkHttp)

// Spring Retry(基于注解)
@Service
public class MyService {
    @Retryable(
        value = {TimeoutException.class, ServiceUnavailableException.class},
        maxAttempts = 3,
        backoff = @Backoff(delay = 2000, multiplier = 2.0, jitter = 1000) // 2s, 4s, 8s
    )
    public MyResponse callExternalApi() {
        // ...
    }
    @Recover
    public MyResponse recover(TimeoutException e) {
        // 所有重试失败后的降级逻辑
        return new MyResponse("fallback");
    }
}
// OkHttp
OkHttpClient client = new OkHttpClient.Builder()
    .retryOnConnectionFailure(true)
    .connectTimeout(10, TimeUnit.SECONDS)
    .readTimeout(10, TimeUnit.SECONDS)
    .addInterceptor(new RetryInterceptor(3)) // 自定义或使用 okhttp-retry
    .build();

Go(使用特定库)

import (
    "time"
    "github.com/avast/retry-go/v4"
)
func FetchData(url string) error {
    return retry.Do(
        func() error {
            resp, err := http.Get(url)
            if err != nil {
                return err
            }
            defer resp.Body.Close()
            if resp.StatusCode >= 500 {
                return fmt.Errorf("server error: %d", resp.StatusCode)
            }
            return nil
        },
        retry.Attempts(3),
        retry.Delay(1 * time.Second),
        retry.DelayType(retry.BackOffDelay), // 指数退避
        retry.RetryIf(func(err error) bool {
            // 只对 5xx 和网络错误重试
            return true
        }),
    )
}

消息队列 (Kafka, RabbitMQ)

Kafka(消费者端)

# consumer.properties 配置
# 自动重试(内部机制,不要和手动提交混合使用)
enable.auto.commit=false        # 建议手动提交
max.poll.interval.ms=300000     # 消费处理超时时间,超时自动重平衡
retry.backoff.ms=100            # 发送消息失败后的重试间隔
# 手动重试:抛出异常或显式调用 nack 让消息回到队列

RabbitMQ(消费者端)

// Spring AMQP 重试配置
@Bean
public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(
        ConnectionFactory connectionFactory) {
    SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
    factory.setConnectionFactory(connectionFactory);
    // 配置重试
    RetryInterceptorBuilder<?> builder = RetryInterceptorBuilder.stateless();
    builder.maxAttempts(3);                 // 重试次数(包含第一次)
    builder.backOffOptions(1000, 2.0, 10000); // 初始间隔1s,倍数2,最大间隔10s
    // 设置拒绝策略:重试完仍失败则进入死信队列(推荐)
    factory.setAdviceChain(builder.build());
    // 死信队列配置:在队列参数中配置 x-dead-letter-exchange
    factory.setDefaultRequeueRejected(false); // 拒绝消息死信而不是丢弃
    return factory;
}

数据库连接池 (HikariCP, DBCP2)

// HikariCP(Spring Boot 默认连接池)
spring.datasource.hikari.connection-timeout=10000    # 等待连接池分配连接的超时时间
spring.datasource.hikari.max-lifetime=1800000        # 连接最大存活时间
spring.datasource.hikari.leak-detection-threshold=60000
// 注意:数据库操作的重试通常不在连接池层配置,而是在框架层(如 MyBatis 或 JPA 的重试插件)
// 连接池本身不提供 SQL 级别的重试

云服务 SDK (AWS, Azure, Google Cloud)

大多数云SDK内置了重试机制,只需配置参数即可。

AWS SDK v2 for Java / Python

# Java SDK 2.x (通过 credentials profile 或 builders 配置)
aws.retry.mode=adaptive    # 或 legacy (standard)
aws.maxAttempts=3
# 自适应重试模式会根据服务端节流动态调整
# Python boto3
import boto3
from botocore.config import Config
config = Config(
   retries = {
      'max_attempts': 10,
      'mode': 'adaptive'  # 或 'standard'
   }
)
client = boto3.client('s3', config=config)

Azure SDK for .NET/Java

// .NET
var retryOptions = new RetryOptions
{
    Mode = RetryMode.Exponential,
    Delay = TimeSpan.FromSeconds(0.8),
    MaxRetries = 3,
    MaxDelay = TimeSpan.FromSeconds(10),
    NetworkTimeout = TimeSpan.FromSeconds(100)
};
var options = new BlobClientOptions();
options.Retry = retryOptions;

一个完整的通用配置示例(伪代码)

# 通用重试策略配置(适合用配置文件管理)
retry:
  enabled: true
  max_attempts: 3          # 总尝试次数(第1次+2次重试)
  interval_strategy: "exponential_backoff_with_jitter"  # 指数退避+抖动
  initial_interval_ms: 1000    # 第一次重试前等待1秒
  multiplier: 2.0              # 翻倍
  max_interval_ms: 10000       # 最长等待10秒
  jitter_range_ms: [500, 2000] # 随机抖动范围
  only_retry_on:
    - 5xx              # 服务端错误
    - TIMEOUT          # 网络超时
    - CONNECTION_ERROR
  do_not_retry_on:
    - 4xx              # 客户端错误
    - 403              # 禁止访问
    - 409              # 冲突(幂等性问题)

避坑指南

  1. 幂等性:只有你的操作是幂等的(重复执行结果相同)才能放心重试,例如支付扣款接口绝对不能无脑重试,否则可能重复扣款。
  2. 熔断配合:如果服务已经崩溃(连续返回 503),单纯的指数退避依然会打满重试次数,建议配合 熔断器 (Circuit Breaker) — 达到一定错误率后直接快速失败。
  3. 最终一致性:对于关键任务,重试有上限,超出上限后请写入死信队列、日志或告警,由人工或后续补偿流程处理。
  4. 不要重试太快:最小间隔建议 500ms,太短的重试等于 DDoS 攻击。

配置重试策略时,核心是选对重试条件(只对临时性错误重试)、用指数退避+抖动(保护下游)、设置最大次数上限(防无限循环)、结合熔断器(防雪崩),没有一种配置适合所有场景,最好在测试环境中压测调优。

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