第 4 章 · OpenFeign 声明式调用、超时与重试
本章目标:在 order-svc 中使用 OpenFeign 声明式调用 user-svc、product-svc;理解 Feign 与 Spring Cloud LoadBalancer、Nacos 的协作;配置连接/读超时、重试策略及错误解码;传递 Gateway 下发的 Header(如 X-Trace-Id);避免 Feign 常见坑(循环依赖、大报文、GET 幂等)。
学时建议:5~6 小时(含 2 小时下单链路联调)
前置:spring-cloud-web ch01~ch03;spring-boot-web ch07 REST 与 Result<T>;HTTP 客户端基础。
4.1 服务间调用为何用 OpenFeign
order-svc 创建订单时需要:
- 校验用户是否存在(user-svc)
- 查询商品单价与上架状态(product-svc)
若手写 RestTemplate/WebClient,每个 API 都要拼 URL、处理序列化、异常映射,冗长易错。
order-svc product-svc
│ │
│ Feign 接口 ProductClient │
│ @GetMapping("/api/v1/products/{id}")
└──────────────────────────────────►│
| 方式 | 优点 | 缺点 |
|---|---|---|
| RestTemplate | 简单、可控 | 样板代码多 |
| WebClient | 响应式、非阻塞 | 学习曲线陡 |
| OpenFeign | 接口 + 注解,接近本地调用 | 需理解负载均衡与超时 |
4.2 依赖与启用
在 svc-order 模块:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
启动类:
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients(basePackages = "com.example.svc.order.client")
public class OrderSvcApplication {
public static void main(String[] args) {
SpringApplication.run(OrderSvcApplication.class, args);
}
}
4.3 定义 Feign Client 接口
4.3.1 调用 product-svc
@FeignClient(name = "product-svc", path = "/api/v1/products")
public interface ProductClient {
@GetMapping("/{id}")
Result<ProductDto> getById(@PathVariable("id") Long id);
@GetMapping
Result<PageResult<ProductDto>> list(
@RequestParam("page") int page,
@RequestParam("size") int size);
}
name 对应 Nacos 的 spring.application.name,Feign 通过 LoadBalancer 解析为 http://host:port。
4.3.2 调用 user-svc
@FeignClient(name = "user-svc", path = "/api/v1/users")
public interface UserClient {
@GetMapping("/{id}")
Result<UserDto> getById(@PathVariable("id") Long id);
}
DTO 放在 svc-common 模块,字段与 ch12 单体保持一致,避免重复定义。
4.4 OrderService 编排示例
@Service
@RequiredArgsConstructor
@Slf4j
public class OrderCreateService {
private final ProductClient productClient;
private final UserClient userClient;
private final OrderRepository orderRepository;
@Transactional
public OrderDto create(CreateOrderCommand cmd) {
Result<UserDto> userRes = userClient.getById(cmd.getUserId());
if (!userRes.isSuccess() || userRes.getData() == null) {
throw new BusinessException(ErrorCode.USER_NOT_FOUND);
}
Result<ProductDto> productRes = productClient.getById(cmd.getProductId());
ProductDto product = productRes.getData();
if (product == null || !product.isOnSale()) {
throw new BusinessException(ErrorCode.PRODUCT_UNAVAILABLE);
}
Order order = new Order();
order.setUserId(cmd.getUserId());
order.setProductId(cmd.getProductId());
order.setAmount(product.getPrice().multiply(BigDecimal.valueOf(cmd.getQuantity())));
order.setStatus(OrderStatus.PENDING_PAY);
orderRepository.save(order);
log.info("Order created id={} amount={}", order.getId(), order.getAmount());
return OrderMapper.toDto(order);
}
}
4.5 Feign 与 LoadBalancer 工作流程
ProductClient.getById(1)
│
▼
Feign 动态代理
│
▼
LoadBalancer 从 Nacos 取 product-svc 实例列表
│
▼
选择实例 192.168.1.10:8082
│
▼
HTTP GET http://192.168.1.10:8082/api/v1/products/1
| 组件 | 职责 |
|---|---|
| Nacos | 实例注册与推送 |
| LoadBalancer | 客户端选择实例(轮询等) |
| Feign | HTTP 编码、解码、接口映射 |
不要在 Feign URL 写死 http://127.0.0.1:8082,否则失去扩缩容能力。
4.6 超时配置
默认超时过短或过长都不合适,在 order-svc-dev.yaml:
spring:
cloud:
openfeign:
client:
config:
default:
connectTimeout: 3000
readTimeout: 5000
loggerLevel: BASIC
product-svc:
readTimeout: 8000 # 商品搜索可略长
| 参数 | 建议 |
|---|---|
connectTimeout | 3s 内应建立 TCP |
readTimeout | 视下游 P99 延迟设定,通常 5~10s |
| 全局 vs 单客户端 | 核心路径单独调优 |
注意:读超时包含下游业务执行时间;过短导致误杀,过长占用线程。
4.7 重试策略
Feign 默认不重试;需引入 spring-retry 并配置:
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
spring: