TsgcWSCircuitBreaker 为客户端调用 HTTP API 实现断路器弹性模式。
TsgcWSCircuitBreaker 为客户端调用 HTTP API 实现了断路器弹性模式。当远程端点不可用时,它通过在滚动窗口中跟踪失败、在达到可配置阈值后打开断路器、并在冷却期间立即拒绝调用,防止级联故障。它支持失败次数和失败率阈值、慢速调用检测、半开试探调用、异常分类(记录/忽略)、按端点覆盖、回退响应以及实时指标。
断路器通过 CircuitBreaker 属性自动与 TsgcHTTPAPI_client 的所有子类(OpenAI、Anthropic、Gemini、Grok、DeepSeek、Mistral、Ollama、Stripe、Binance、Bybit、Coinbase、Kraken 及其他 API 包装器)集成。一经赋值,每个 Get / Post / Put / Patch / Delete 调用都会自动被包装。每个唯一主机名维护一个独立的断路器。
1.在表单上放置一个 TsgcWSCircuitBreaker 组件。
2. 配置阈值(何时打开)和恢复(如何恢复):
sgcWSCircuitBreaker1.Thresholds.FailureCount := 5;
sgcWSCircuitBreaker1.Recovery.CooldownSec := 30;
3. 将断路器分配给 HTTP API 客户端(任何 TsgcHTTPAPI_client 的子类):
sgcAI_OpenAI1.CircuitBreaker := sgcWSCircuitBreaker1;
4. 在客户端上调用 Get/Post/Delete/Patch/Put。断路器会自动记录成功和失败,并在断路器 Open 时拒绝新的调用。
功能说明:断路器是一个三状态机。每个主机(键)拥有独立的状态,控制出站调用是被允许、拒绝还是作为试探性请求发送。
| State | 行为 | Transition |
| csClosed | 所有调用均通过。失败计入滚动窗口。 | 当达到 FailureCount、FailureRatePercent 或 SlowCallRatePercent 时,移至 csOpen 状态。 |
| csOpen | 每次调用都被立即拒绝。每次尝试都会触发 OnCallRejected。 | 在距开路时刻经过 Recovery.CooldownSec 秒后,转移至 csHalfOpen 状态。 |
| csHalfOpen | 允许最多 Recovery.HalfOpenTrialCalls 次试验调用以探测恢复状态。 | 当试验成功时转为 csClosed;当任何试验失败时退回到 csOpen。 |
功能说明: 组件的主开关,以及在未提供每次调用的密钥时使用的两个默认密钥名称。客户端集成自动使用目标主机名作为密钥,DefaultKey 和 ServerKey 仅适用于手动调用 Execute/RecordSuccess/RecordFailure 以及可选的服务器集成辅助方法。
使用时机:在受控负载测试或维护窗口期间在运行时禁用熔断器,而无需取消其与 HTTP 客户端的关联。当您在自定义代码内手动驱动 Execute 时,请设置一个有意义的 DefaultKey(例如 'openai-api')。
| 属性 | 类型 | 默认 | 描述 |
| 已启用 | Boolean | True | 主开关。当为 False 时,每个公共方法都会短路为"允许",且不更新任何计数器。 |
| DefaultKey | string | 'default' | 无参数重载的 Execute、RecordSuccess 和 RecordFailure 使用的密钥。 |
| ServerKey | string | 'server' | 供可选服务器集成钩子(IsConnectionAllowed、IsMessageAllowed、RecordMessageError)使用的密钥。 |
sgcWSCircuitBreaker1.Enabled := True;
sgcWSCircuitBreaker1.DefaultKey := 'openai-api';
sgcWSCircuitBreaker1.ServerKey := 'server';
功能说明:定义使断路器从 Closed 变为 Open 的条件。当达到 FailureCount 个原始失败次数,或窗口内 FailureRatePercent 的调用失败(超过 MinCalls 后),或 SlowCallRatePercent 的调用超过 SlowCallDurationMs 时,断路器将打开。
使用场景:您的服务器调用 OpenAI 的 API。当 60 秒内出现 5 次失败(或在观察到至少 10 次调用后有 50% 的调用失败),则断开电路并停止轰击 OpenAI,让远端自行恢复,而不是继续堆积重试。
| 属性 | 类型 | 默认 | 描述 |
| 已启用 | Boolean | True | 启用基于阈值的开路功能。当值为 False 时,电路不会自动断开。 |
| FailureCount | Integer | 5 | 滚动窗口内触发断路器的绝对失败次数。 |
| FailureRatePercent | Integer | 50 | 触发断路器的失败百分比(0-100),在达到 MinCalls 后生效。 |
| SlowCallDurationMs | Integer | 3000 | 成功调用中超过该阈值(毫秒)的调用也将被计为"慢速"调用。 |
| SlowCallRatePercent | Integer | 0 | 触发断路器的慢速调用百分比(0-100)。0 禁用慢速调用检测。 |
| MinCalls | Integer | 10 | 在评估速率阈值之前所需观测到的最少调用次数(避免在小样本上误触发)。 |
// Open after 5 failures, or 50% failure rate on at least 10 calls
sgcWSCircuitBreaker1.Thresholds.Enabled := True;
sgcWSCircuitBreaker1.Thresholds.FailureCount := 5;
sgcWSCircuitBreaker1.Thresholds.FailureRatePercent := 50;
sgcWSCircuitBreaker1.Thresholds.MinCalls := 10;
// Also treat any call slower than 3 seconds as a "slow" call
sgcWSCircuitBreaker1.Thresholds.SlowCallDurationMs := 3000;
sgcWSCircuitBreaker1.Thresholds.SlowCallRatePercent := 60;
功能说明: 定义用于阈值评估的滚动窗口。窗口被划分为 BucketCount 个子桶,以便旧计数器随时间平滑过期。阈值始终根据有效桶的总和评估。
使用场景:使用 30 秒滚动窗口和 6 个桶,使瞬时峰值不会触发电路,但持续失败会触发。较长的窗口可以平滑噪声;较短的窗口对中断的反应更快。
| 属性 | 类型 | 默认 | 描述 |
| RollingWindowSec | Integer | 60 | 滚动窗口的总宽度(秒)。超出此范围的样本将被丢弃。 |
| BucketCount | Integer | 10 | 窗口被划分的子桶数量。每个桶保存成功/失败/慢调用计数器。 |
// 30-second window split into 6 buckets (5 seconds each)
sgcWSCircuitBreaker1.TimeWindow.RollingWindowSec := 30;
sgcWSCircuitBreaker1.TimeWindow.BucketCount := 6;
功能:控制回路如何从 Open 状态恢复。在 CooldownSec 时间过后,断路器转换到 HalfOpen 状态,并允许 HalfOpenTrialCalls 次探测调用通过。如果探测成功,断路器关闭;如果失败,断路器重新打开,冷却计时器重新开始。
使用时机:打开后等待 30 秒,然后允许一次试验性调用通过以测试恢复情况,随后再重新关闭。较长的冷却时间可保护正在挣扎的上游服务免受新一波流量的冲击;较短的冷却时间则可使仅出现短暂故障的服务快速恢复。
| 属性 | 类型 | 默认 | 描述 |
| CooldownSec | Integer | 30 | 在过渡到 HalfOpen 状态前,在 Open 状态下等待的秒数。 |
| HalfOpenTrialCalls | Integer | 1 | HalfOpen 状态下决定关闭或重新打开之前允许的试验调用次数。 |
| AutoReset | Boolean | True | 当值为 True 时,计数器在状态转换时自动清零。 |
| MaxRetries | Integer | 3 | 供重试辅助程序使用的信息性上限。断路器本身不执行任何自动重试。 |
// After opening: wait 30s, then allow 1 trial call, then decide
sgcWSCircuitBreaker1.Recovery.CooldownSec := 30;
sgcWSCircuitBreaker1.Recovery.HalfOpenTrialCalls := 1;
sgcWSCircuitBreaker1.Recovery.AutoReset := True;
sgcWSCircuitBreaker1.Recovery.MaxRetries := 3;
功能说明:配置电路断路器处于 Open 状态时提供的备用载荷。当 Fallback.Enabled 为 True 且 Execute / ExecuteWithResult 拒绝调用时,OnFallback 事件触发,响应以 CachedResponse(或 CustomMessage)初始化。处理程序可在响应返回给调用方之前替换该字符串。
使用场景:当 OpenAI 出现故障时,返回已缓存的最近一次有效响应,使用户看到降级但可用的功能(例如之前生成的摘要),而不是错误提示。使用 CustomMessage 可设置静态的"维护模式"JSON 正文。
| 属性 | 类型 | 默认 | 描述 |
| 已启用 | Boolean | False | 启用回退路径。当为 False 时,被拒绝的调用直接返回而不触发 OnFallback。 |
| CachedResponse | string | '' | 回路处于 Open 状态时返回的静态负载。优先于 CustomMessage。 |
| CustomMessage | string | '' | CachedResponse 为空时使用的回退负载。 |
| UseLastSuccess | Boolean | False | 当值为 True 时,为密钥观察到的最后一次成功响应将作为备用内容提供。 |
// When api.openai.com is down, serve a safe JSON body instead of erroring
sgcWSCircuitBreaker1.Fallback.Enabled := True;
sgcWSCircuitBreaker1.Fallback.CachedResponse :=
'{"error":"service unavailable","retry_after":30}';
sgcWSCircuitBreaker1.Fallback.CustomMessage :=
'{"error":"ai service temporarily offline"}';
sgcWSCircuitBreaker1.Fallback.UseLastSuccess := True;
功能:将引发的异常分类为"记录为故障"或"已忽略"。只有记录的异常才会推进故障计数器。MatchMode 选择 RecordAsFailure/IgnoreExceptions 中的字符串与异常文本(ClassName: Message)进行比较的方式。
使用场景:将带有 500/502/503/504 的 EIdHTTPProtocolException 视为失败,但忽略 400/401/404 的 EIdHTTPProtocolException(业务错误不应触发断路器)。将 RecordAsFailure 保留为空意味着每个未被忽略的异常都会被记录。
| 属性 | 类型 | 默认 | 描述 |
| RecordAsFailure | TStringList | 空 | 与异常文本匹配的模式;匹配的异常计为失败。空列表表示记录所有未被忽略的内容。 |
| IgnoreExceptions | TStringList | 空 | 与异常文本匹配的模式;匹配的异常被丢弃(既不计为成功也不计为失败)。 |
| MatchMode | TsgcCircuitBreakerExceptionMatch | cemContains | 比较模式:cemExact、cemContains、cemStartsWith 或 cemWildcard(* / ?)。 |
// Record HTTP 5xx and timeouts as failures; ignore client-side 4xx
sgcWSCircuitBreaker1.Classification.MatchMode := cemContains;
sgcWSCircuitBreaker1.Classification.RecordAsFailure.Add('HTTP/1.1 500');
sgcWSCircuitBreaker1.Classification.RecordAsFailure.Add('HTTP/1.1 502');
sgcWSCircuitBreaker1.Classification.RecordAsFailure.Add('HTTP/1.1 503');
sgcWSCircuitBreaker1.Classification.RecordAsFailure.Add('HTTP/1.1 504');
sgcWSCircuitBreaker1.Classification.RecordAsFailure.Add('timeout');
sgcWSCircuitBreaker1.Classification.IgnoreExceptions.Add('HTTP/1.1 400');
sgcWSCircuitBreaker1.Classification.IgnoreExceptions.Add('HTTP/1.1 401');
sgcWSCircuitBreaker1.Classification.IgnoreExceptions.Add('HTTP/1.1 404');
功能说明:基于模式的覆盖规则集合。每个 TsgcCircuitBreakerEndpointItem 都有自己的 Pattern(支持通配符 * 和 ?)、OverrideThresholds 块和 OverrideRecovery 块。当活跃键(主机名)与某个已启用的条目匹配时,该条目的设置将在调用期间替换顶级 Thresholds / Recovery。
使用场景:为每个 API 设置独立的断路器:api.openai.com 的阈值设置得宽松一些(它偶尔在负载下变慢),而 api.stripe.com 涉及支付关键业务,应更快打开以快速失败。为每个主机添加一个端点条目并设置经过调整的阈值。
| 属性 | 类型 | 默认 | 描述 |
| 已启用 | Boolean | False | 启用端点覆盖集合。为 False 时不评估任何条目。 |
| 条目 | TCollection | 空 | TsgcCircuitBreakerEndpointItem 的集合。第一个启用的匹配项优先。 |
每个 TsgcCircuitBreakerEndpointItem 公开以下内容:
| 属性 | 类型 | 默认 | 描述 |
| 模式 | string | '' | 与密钥(主机名)匹配的通配符模式。支持 * 和 ?。 |
| 已启用 | Boolean | True | 禁用此条目而不删除它。 |
| OverrideThresholds | TsgcCircuitBreakerThresholds | defaults | 当模式匹配时应用的阈值。仅当其自身的 Enabled 为 True 时有效。 |
| OverrideRecovery | TsgcCircuitBreakerRecovery | defaults | 模式匹配时应用的恢复设置。 |
sgcWSCircuitBreaker1.PerEndpoint.Enabled := True;
// OpenAI - lenient: 10 failures, 60s cooldown
with sgcWSCircuitBreaker1.PerEndpoint.Items.Add as TsgcCircuitBreakerEndpointItem do
begin
Pattern := 'api.openai.com';
Enabled := True;
OverrideThresholds.Enabled := True;
OverrideThresholds.FailureCount := 10;
OverrideThresholds.FailureRatePercent := 70;
OverrideRecovery.CooldownSec := 60;
end;
// Stripe - payment critical: open fast, recover slow
with sgcWSCircuitBreaker1.PerEndpoint.Items.Add as TsgcCircuitBreakerEndpointItem do
begin
Pattern := 'api.stripe.com';
Enabled := True;
OverrideThresholds.Enabled := True;
OverrideThresholds.FailureCount := 3;
OverrideThresholds.FailureRatePercent := 30;
OverrideRecovery.CooldownSec := 120;
end;
功能说明:随着调用流经断路器,以原子方式更新的只读计数器。跨所有密钥/主机名聚合。CurrentOpenBreakers 在每次状态转换后刷新;AverageLatencyMs 是 Execute 观测到的持续时间的滚动平均值。
使用场景:实时仪表板,显示每台主机的电路状态、故障率和拒绝计数。通过 TTimer 轮询 Metrics 以为运营人员绘制仪表,或将其发送到 Prometheus / StatsD 以触发告警。
| 属性 | 类型 | 描述 |
| TotalCalls | Int64 | 已处理的总调用次数(成功次数 + 已记录的失败次数)。 |
| TotalFailures | Int64 | 所有密钥的失败总次数。 |
| TotalSuccesses | Int64 | 所有密钥记录的成功调用总数。 |
| TotalRejected | Int64 | 由于电路处于 Open 状态而被拒绝的总调用次数。 |
| CurrentOpenBreakers | Integer | 当前处于 Open 状态的密钥数量。 |
| AverageLatencyMs | Double | 围绕 Execute / ExecuteWithResult 调用测量的运行平均延迟(毫秒)。 |
// Poll from a TTimer and update a dashboard
procedure TForm1.MetricsTimer(Sender: TObject);
begin
LabelCalls.Caption := Format('Calls: %d (ok %d / fail %d / rejected %d)',
[sgcWSCircuitBreaker1.Metrics.TotalCalls,
sgcWSCircuitBreaker1.Metrics.TotalSuccesses,
sgcWSCircuitBreaker1.Metrics.TotalFailures,
sgcWSCircuitBreaker1.Metrics.TotalRejected]);
LabelOpen.Caption := Format('Open breakers: %d',
[sgcWSCircuitBreaker1.Metrics.CurrentOpenBreakers]);
LabelLatency.Caption := Format('Avg latency: %.1f ms',
[sgcWSCircuitBreaker1.Metrics.AverageLatencyMs]);
end;
OnStateChange:当电路在状态之间转换时触发。提供密钥、旧状态和新状态(csClosed、csOpen 或 csHalfOpen)。适用于日志记录、告警和更新实时 UI。
OnCallRejected:当由于电路处于 Open 状态或 HalfOpen 试验配额耗尽而拒绝调用时触发。提供键值和原因字符串(例如 Circuit Open)。
OnFallback:在返回回退响应之前触发。aResponse 参数(var string)从 Fallback.CachedResponse / CustomMessage 初始化,可由处理程序替换。
OnFailureRecorded:每次记录失败时触发。提供键值和异常文本(ClassName: Message)。用于结构化日志记录,或与上游事故追踪系统进行关联。
OnSlowCall: 当一次成功调用超过 Thresholds.SlowCallDurationMs 时触发。提供密钥和以毫秒为单位的测量持续时间。
| 方法 | 描述 |
| Execute(Key, Action) | 在给定键下运行 TProc。首先检查 IsCallAllowed,自动记录成功/失败/慢速,如果操作执行则返回 True。 |
| Execute(Action) | 使用 DefaultKey 的重载。 |
| ExecuteWithResult(Key, Action, out Result) | (D2009+)运行 TFunc<TObject> 并通过 out 参数返回其结果;与 Execute 的计费方式相同。 |
| RecordSuccess(Key) | 记录成功的调用。当试验成功时,推进 HalfOpen → Closed。 |
| RecordSuccess | 使用 DefaultKey 的重载。 |
| RecordFailure(Key, Exception) | 记录失败信息及可选的异常文本,并触发状态转换评估。 |
| RecordFailure(Exception) | 使用 DefaultKey 的重载。 |
| ForceOpen(Key) | 手动将电路强制切换到 Open 状态。 |
| ForceClose(Key) | 手动强制将断路器切换到关闭状态。 |
| Reset(Key) | 清除给定键的计数器和状态。 |
| ResetAll | 清除每个被跟踪的回路并重置 Metrics。 |
| GetState(Key) | 返回密钥的当前 TsgcCircuitState。 |
| IsCallAllowed(Key) | 若当前允许发起新的调用,则返回 True。在每次请求前由 HTTP API 客户端自动调用。 |
| GetFailureRate(Key) | 返回在滚动窗口中观察到的当前失败百分比(0.0 - 100.0)。 |
| GetTotalCalls(Key) | 返回该密钥在滚动窗口中观察到的调用次数。 |
| GetLastStateChange(Key) | 返回最后一次状态转换的 TDateTime。 |
| GetOpenBreakers | 返回当前处于 Open 状态的键的 TArray<string>。 |
| SaveStateToFile(FileName) | 将每个断路器的状态和计数器持久化到文件中。 |
| LoadStateFromFile(FileName) | 从之前保存的文件恢复电路状态。 |
| IsConnectionAllowed(IP) | 服务器端自我保护钩子,作为客户端主用途的辅助。当 ServerKey 断路器未处于 Open 状态时返回 True。 |
| IsMessageAllowed(IP, Message) | 服务器端自我保护钩子。当 ServerKey 电路未处于 Open 状态时返回 True。 |
| RegisterConnection(IP) | 保留用于未来按 IP 统计指标的服务器端钩子(目前为空操作)。 |
| UnregisterConnection(IP) | 保留用于未来按 IP 统计指标的服务器端钩子(目前为空操作)。 |
| RecordMessageError(IP, Exception) | 服务器端自我保护钩子。记录 ServerKey 上的失败。 |
| RecordMessageSuccess(IP) | 服务器端自我保护钩子。在 ServerKey 上记录一次成功。 |
当通过 CircuitBreaker 属性将对象分配给 TsgcHTTPAPI_client 后代时,客户端自动:
api.openai.com)派生密钥并调用 IsCallAllowed(host)。如果电路处于 Open 状态,调用将被拒绝,抛出异常 "Circuit breaker open for <host> - call rejected",并触发 OnCallRejected。api.openai.com、api.anthropic.com、api.stripe.com)的调用跟踪完全独立的回路。
// Enable the circuit breaker
sgcWSCircuitBreaker1.Enabled := True;
// Thresholds: open after 5 failures, or 50% failure rate on 10+ calls
sgcWSCircuitBreaker1.Thresholds.Enabled := True;
sgcWSCircuitBreaker1.Thresholds.FailureCount := 5;
sgcWSCircuitBreaker1.Thresholds.FailureRatePercent := 50;
sgcWSCircuitBreaker1.Thresholds.SlowCallDurationMs := 3000;
sgcWSCircuitBreaker1.Thresholds.MinCalls := 10;
// Rolling window: 60 seconds / 10 buckets
sgcWSCircuitBreaker1.TimeWindow.RollingWindowSec := 60;
sgcWSCircuitBreaker1.TimeWindow.BucketCount := 10;
// Recovery: wait 30s in Open, then 1 trial call in HalfOpen
sgcWSCircuitBreaker1.Recovery.CooldownSec := 30;
sgcWSCircuitBreaker1.Recovery.HalfOpenTrialCalls := 1;
// Classification: record HTTP 5xx and timeouts, ignore 4xx
sgcWSCircuitBreaker1.Classification.MatchMode := cemContains;
sgcWSCircuitBreaker1.Classification.RecordAsFailure.Add('HTTP/1.1 5');
sgcWSCircuitBreaker1.Classification.RecordAsFailure.Add('timeout');
sgcWSCircuitBreaker1.Classification.IgnoreExceptions.Add('HTTP/1.1 404');
// Fallback: serve a cached payload while the circuit is Open
sgcWSCircuitBreaker1.Fallback.Enabled := True;
sgcWSCircuitBreaker1.Fallback.CustomMessage :=
'{"error":"service unavailable"}';
// Per-endpoint overrides
sgcWSCircuitBreaker1.PerEndpoint.Enabled := True;
with sgcWSCircuitBreaker1.PerEndpoint.Items.Add as TsgcCircuitBreakerEndpointItem do
begin
Pattern := 'api.stripe.com';
OverrideThresholds.Enabled := True;
OverrideThresholds.FailureCount := 3;
OverrideRecovery.CooldownSec := 120;
end;
// Persist circuit state across restarts
sgcWSCircuitBreaker1.LoadStateFromFile('circuit.dat');
// Attach to an HTTP API client (any TsgcHTTPAPI_client descendant)
sgcAI_OpenAI1.CircuitBreaker := sgcWSCircuitBreaker1;
// Use the client normally; the breaker intercepts Get / Post / etc.
try
Response := sgcAI_OpenAI1.Get('https://api.openai.com/v1/models');
except
on E: Exception do
if Pos('Circuit breaker open', E.Message) > 0 then
Log('Circuit open for api.openai.com - using fallback')
else
Log('Call failed: ' + E.Message);
end;
// Handle state changes
procedure TForm1.CircuitStateChange(Sender: TObject; const aKey: string;
aOldState, aNewState: TsgcCircuitState);
begin
Log(Format('Circuit %s: %d -> %d', [aKey, Ord(aOldState), Ord(aNewState)]));
end;
// Handle fallback responses
procedure TForm1.CircuitFallback(Sender: TObject; const aKey: string;
var aResponse: string);
begin
Log('Fallback used for ' + aKey);
end;
电路断路器完全线程安全。所有公共方法使用内部临界区和线程安全列表来保护对每个键的状态、滚动窗口和指标计数器的并发访问。单个组件可以由多个 HTTP API 客户端共享,并可从任何线程(事件处理程序、定时器、后台工作线程)访问,无需外部同步。