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 → 恢复 → 成功