Reactor 瞬态错误
在响应式编程中,retryWhen
操作符通过 RetrySignal
接口提供了对重试行为的精细控制,特别是在处理 瞬态错误(transient errors) 时。瞬态错误是指那些在一段时间内发生,但随后会自行恢复的错误,例如网络请求失败后服务器短暂不可用,但随后恢复正常。在这种情况下,我们希望每个错误“爆发”(burst)都能独立处理,而不是将前一次的重试状态带入下一次。
1. 瞬态错误的定义与场景
瞬态错误通常表现为短暂的失败,随后系统会恢复。例如,一个 HTTP 请求源可能会在某些条件下连续失败两次,然后恢复正常。这种模式在长期运行的流(如 Kafka 消费者、HTTP 请求等)中非常常见。
2. RetrySignal
的作用
RetrySignal
是 retryWhen
操作符中用于表示重试状态的接口。它提供了两个关键方法:
totalRetries()
:返回到目前为止的总重试次数(单调递增)。totalRetriesInARow()
:返回当前连续失败的次数。如果在重试中成功恢复(即接收到onNext
而不是onError
),这个值会被重置为 0。
这个 totalRetriesInARow()
的值是处理瞬态错误的关键。它允许我们区分“连续失败”和“独立失败”,从而实现更合理的重试策略。
3. transientErrors(boolean)
配置的作用
当在 RetrySpec
或 RetryBackoffSpec
中设置 transientErrors(true)
时,重试策略将使用 totalRetriesInARow()
来计算重试次数。这意味着:
- 每次重试失败后,如果成功恢复(即接收到
onNext
),则totalRetriesInARow()
会被重置为 0。 - 每次“爆发”(即连续失败)都会被独立处理,重试次数不会累积。
这种配置特别适用于处理瞬态错误,例如网络请求失败后服务器短暂不可用的情况。
4. 示例代码解析
// 用于生成数据和控制流的辅助变量
final AtomicInteger transientHelper = new AtomicInteger();
// 模拟HTTP请求的Flux数据流
Supplier<Flux<Integer>> httpRequest = () ->Flux.generate(sink -> {int i = transientHelper.getAndIncrement();if (i == 10) {sink.next(i);sink.complete();}else if (i % 3 == 0) {sink.next(i);}else {sink.error(new IllegalStateException("Transient error at " + i));}});
// 用于统计错误次数的变量
AtomicInteger errorCount = new AtomicInteger();
// 添加错误处理逻辑的Flux
Flux<Integer> transientFlux = httpRequest.get().doOnError(e -> errorCount.incrementAndGet());// 使用retryWhen进行重试,最多重试2次,并且认为所有错误都是暂时性的
transientFlux.retryWhen(Retry.max(2).transientErrors(true)).blockLast();
assertThat(errorCount).hasValue(6);
doOnError
:用于统计错误次数。retryWhen(Retry.max(2).transientErrors(true))
:Retry.max(2)
表示最多重试 2 次。transientErrors(true)
表示启用瞬态错误处理模式。
blockLast()
:等待整个流完成。assertThat(errorCount).hasValue(6)
:验证总共发生了 6 次错误,说明重试机制成功处理了 6 次错误。
5. 关键区别:启用 transientErrors(true)
与不启用
-
启用
transientErrors(true)
:- 每次重试失败后,如果成功恢复,
totalRetriesInARow()
会被重置为 0。 - 每次“爆发”(连续失败)都会被独立处理,重试次数不会累积。
- 最终成功完成,错误次数为 6 次。
- 每次重试失败后,如果成功恢复,
-
不启用
transientErrors(true)
:- 重试次数是单调递增的,不会重置。
- 如果第二次“爆发”导致重试次数超过 2 次,整个序列将失败。
6. 总结
retryWhen
通过RetrySignal
提供了对重试行为的精细控制。totalRetriesInARow()
是处理瞬态错误的关键,它允许我们区分“连续失败”和“独立失败”。transientErrors(true)
配置使得每次“爆发”都能独立处理,重试次数不会累积,从而避免了因前一次失败而放弃后续重试的问题。- 这种机制特别适用于处理网络请求、数据库连接等可能遇到瞬态错误的场景。