高并发系统设计-重试

一、概述

1.1 什么是重试

重试(Retry)是指:

当一次请求失败后,在满足条件的情况下再次发起请求,以提高成功率。

例如:

请求 → 失败 → 再请求 → 成功

1.2 为什么需要重试

在分布式系统中,失败是常态:

  • 网络抖动
  • RPC短暂超时
  • Redis瞬时不可用
  • MySQL连接波动
  • 依赖服务重启

例如:

请求支付服务 → 超时

但实际服务是正常的

如果不重试:用户支付失败

但如果允许重试:

第一次失败

第二次成功

1.3 重试解决的问题

重试主要解决:

1. 短暂故障

如: 网络抖动 服务瞬断 连接超时

2.依赖服务不稳定

如:

第三方支付 / 短信 / API

3.提高成功率

1.4 重试的误区

错误做法 : 失败 → 立即重试 → 再失败 → 无限循环

正确做法是:可控重试 + 退避策略 + 最大次数限制

二、原理

2.1 重试的核心思想

重试本质是:

用时间换成功率

但前提是:

  • 不能无限重试
  • 不能立即重试
  • 不能对所有错误重试

2.2 哪些情况可以重试

  • 网络超时
  • 连接失败
  • 503 / 502
  • RPC短暂异常

不可重试

  • 参数错误
  • 业务逻辑错误
  • 权限错误
  • 数据不存在

2.3 重试策略类型

1. 立即重试

失败 → 立即再试

2.固定间隔重试

1s → 1s → 1s

3. 指数退避 (核心策略,推荐)

1s → 2s → 4s → 8s → 16s

4. 随机抖动

1s + random(0~500ms)

2.4 Retry Storm(重试风暴)

需要注意的问题之一 场景:

服务A → 服务B失败

触发重试:

1000个请求 × 3次重试
= 3000请求

结果:

B 被打死
→ A继续重试
→ 系统崩溃

2.5 正确的重试模型

请求失败
是否可重试?
是 → 退避等待
再次请求
超过最大次数 → fallback

三、实现

3.1 PHP基础重试实现

function retry(callable $fn, int $maxAttempts = 3)
{
    $attempt = 0;

    begin:

    try {
        return $fn();
    } catch (\Throwable $e) {

        $attempt++;

        if ($attempt >= $maxAttempts) {
            throw $e;
        }

        sleep(1);

        goto begin;
    }
}

3.2 指数退避实现 (推荐)

function retryBackoff(callable $fn, int $maxAttempts = 3)
{
    $attempt = 0;

    begin:

    try {
        return $fn();
    } catch (\Throwable $e) {

        $attempt++;

        if ($attempt >= $maxAttempts) {
            throw $e;
        }

        $delay = pow(2, $attempt);

        sleep($delay);

        goto begin;
    }
}