Effect-TS错误处理韧性模式Skill effect-patterns-error-handling-resilience

此技能提供了Effect-TS中错误处理韧性的模式,专注于实现指数退避重试,以提高应用程序的容错性和恢复能力。适用于处理不稳定服务调用、防止惊群效应和级联故障。关键词:Effect-TS, 错误处理, 韧性, 指数退避, 重试, TypeScript, 函数式编程。

后端开发 0 次安装 0 次浏览 更新于 3/8/2026

name: effect-patterns-error-handling-resilience description: Effect-TS 错误处理韧性模式。在 Effect-TS 应用程序中处理错误处理韧性时使用。

Effect-TS 模式:错误处理韧性

此技能提供了 1 个精选的 Effect-TS 模式,用于错误处理韧性。 在以下任务中使用此技能:

  • 错误处理韧性
  • Effect-TS 应用程序中的最佳实践
  • 实际模式和解决方案

🟡 中级模式

调度模式 2:实现指数退避重试

规则: 使用带有抖动的指数退避进行重试,以防止压倒失败的服务,并通过智能计时提高成功可能性。

好例子:

此示例演示了使用抖动进行指数退避重试不稳定的 API 调用。

import { Effect, Schedule } from "effect";

interface RetryStats {
  readonly attempt: number;
  readonly delay: number;
  readonly lastError?: Error;
}

// 模拟不稳定的 API,前三次失败,第四次成功
let attemptCount = 0;

const flakyApiCall = (): Effect.Effect<{ status: string }> =>
  Effect.gen(function* () {
    attemptCount++;
    yield* Effect.log(`[API] 尝试 ${attemptCount}`);

    if (attemptCount < 4) {
      yield* Effect.fail(new Error("服务暂时不可用 (503)"));
    }

    return { status: "ok" };
  });

// 计算带有抖动的指数退避
interface BackoffConfig {
  readonly baseDelayMs: number;
  readonly maxDelayMs: number;
  readonly maxRetries: number;
}

const exponentialBackoffWithJitter = (config: BackoffConfig) => {
  let attempt = 0;

  // 计算此次尝试的延迟
  const calculateDelay = (): number => {
    const exponential = config.baseDelayMs * Math.pow(2, attempt);
    const withJitter = exponential * (0.5 + Math.random() * 0.5); // ±50% 抖动
    const capped = Math.min(withJitter, config.maxDelayMs);

    yield* Effect.log(
      `[退避] 尝试 ${attempt + 1}: ${Math.round(capped)}ms 延迟`
    );

    return Math.round(capped);
  };

  return Effect.gen(function* () {
    const effect = flakyApiCall();

    let lastError: Error | undefined;

    for (attempt = 0; attempt < config.maxRetries; attempt++) {
      const result = yield* effect.pipe(Effect.either);

      if (result._tag === "Right") {
        yield* Effect.log(`[成功] 在第 ${attempt + 1} 次尝试中成功`);
        return result.right;
      }

      lastError = result.left;

      if (attempt < config.maxRetries - 1) {
        const delay = calculateDelay();
        yield* Effect.sleep(`${delay} millis`);
      }
    }

    yield* Effect.log(
      `[失败] 所有 ${config.maxRetries} 次尝试已用尽`
    );
    yield* Effect.fail(lastError);
  });
};

// 使用指数退避运行
const program = exponentialBackoffWithJitter({
  baseDelayMs: 100,
  maxDelayMs: 5000,
  maxRetries: 5,
});

console.log(
  `
[开始] 使用指数退避重试不稳定 API
`
);

Effect.runPromise(program).then(
  (result) => console.log(`
[结果] ${JSON.stringify(result)}
`),
  (error) => console.error(`
[错误] ${error.message}
`)
);

输出演示了带有抖动的递增延迟:

[开始] 使用指数退避重试不稳定 API

[API] 尝试 1
[退避] 尝试 1: 78ms 延迟
[API] 尝试 2
[退避] 尝试 2: 192ms 延迟
[API] 尝试 3
[退避] 尝试 3: 356ms 延迟
[API] 尝试 4
[成功] 在第 4 次尝试中成功

[结果] {"status":"ok"}

原理:

重试失败操作时,使用带有抖动的指数退避:每次重试延迟加倍(带有随机抖动),直到最大值。这可以防止:

  • 惊群效应:所有客户端同时重试
  • 级联故障:压倒恢复中的服务
  • 资源耗尽:太多排队的重试尝试

公式:延迟 = min(最大延迟, 基础延迟 * 2^尝试次数 + 随机抖动)


天真重试策略在负载下失败:

立即重试

  • 所有失败立即重试
  • 在负载下失败服务(恢复需要更长时间)
  • 导致级联故障

固定退避(例如,总是 1 秒):

  • 恢复期间没有压力减少
  • 多个客户端导致惊群效应
  • 可预测 = 同步重试

指数退避

  • 给失败服务时间恢复
  • 每次重试等待时间逐渐增加
  • 没有抖动,同步重试仍然冲击服务

指数退避 + 抖动

  • 将重试尝试分散在时间上
  • 失败在客户端之间去相关
  • 正确利用服务恢复时间
  • 成功可能性随着每次重试增加

现实例子:100 个客户端同时失败

  • 立即重试:100 个请求在毫秒内 → 失败
  • 固定退避:100 个请求在正好 1 秒时 → 失败
  • 指数退避:100 个请求在 100ms, 200ms, 400ms, 800ms → 恢复 → 成功