8.4.3 故障转移
故障转移(也称为回退)是当主要操作失败时执行的替代操作。目标是为用户提供某种形式的服务,即使不是完整的服务。这比完全失败要好。
在 Order Service 的上下文中,如果 Catalog Service 不可用或超时,你可以定义一个故障转移来返回默认值或缓存值。这允许用户仍然提交订单,即使书籍详细信息暂时不可用。
定义故障转移
Project Reactor 提供了多种定义故障转移的方式:
- defaultIfEmpty()——当流为空时返回默认值
- switchIfEmpty()——当流为空时切换到另一个流
- onErrorReturn()——发生错误时返回默认值
- onErrorResume()——发生错误时恢复到另一个流
更新 BookClient 类中的 getBookByIsbn() 方法,添加故障转移:
代码清单 8.26 为 HTTP 交互定义故障转移
// ... 其他代码
public Mono<Book> getBookByIsbn(String isbn) {
return webClient
.get()
.uri(BOOKS_ROOT_API + isbn)
.retrieve()
.bodyToMono(Book.class)
.timeout(Duration.ofSeconds(3))
.retryWhen(Retry.backoff(3, Duration.ofMillis(100)))
.onErrorResume(e -> {
// 记录错误
log.error("Failed to fetch book details for ISBN: {}", isbn, e);
// 返回空的 Mono,导致订单被拒绝
return Mono.empty();
});
}
// ... 其他代码
这个配置意味着:
- 尝试从 Catalog Service 获取书籍信息
- 如果超时,重试最多 3 次(指数退避)
- 如果所有重试都失败,执行故障转移(记录错误并返回空结果)
- 空结果导致订单被拒绝
故障转移策略
根据你的业务需求,你可以采用不同的故障转移策略:
- 返回默认值——例如,返回一个价格为 0 的默认书籍对象
- 返回缓存值——例如,返回上次成功获取的书籍信息
- 返回空结果——导致操作被拒绝
- 抛出自定义异常——让调用者决定如何处理
完整示例
将所有内容结合起来,下面是 BookClient 的完整实现:
package com.polarbookshop.orderservice.book;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
import reactor.retry.Retry;
import java.time.Duration;
@Component
public class BookClient {
private static final Logger log = LoggerFactory.getLogger(BookClient.class);
private static final String BOOKS_ROOT_API = "/books/";
private final WebClient webClient;
public BookClient(WebClient webClient) {
this.webClient = webClient;
}
public Mono<Book> getBookByIsbn(String isbn) {
return webClient
.get()
.uri(BOOKS_ROOT_API + isbn)
.retrieve()
.bodyToMono(Book.class)
.timeout(Duration.ofSeconds(3))
.retryWhen(Retry.backoff(3, Duration.ofMillis(100))
.filter(throwable -> throwable instanceof java.util.concurrent.TimeoutException ||
throwable instanceof org.springframework.web.reactive.function.client.WebClientResponseException))
.onErrorResume(e -> {
log.error("Failed to fetch book details for ISBN: {}", isbn, e);
return Mono.empty();
});
}
}
这个实现提供了:
- 超时保护——3 秒后超时
- 自动重试——最多 3 次,指数退避
- 故障转移——失败时返回空结果
- 错误日志——记录所有失败
这样,Order Service 在 Catalog Service 暂时不可用时仍然可以运行,只是会拒绝订单而不是完全失败。