远程调用
问题
微服务之间如何进行远程调用?OpenFeign 的原理是什么?
答案
远程调用方式对比
| 方式 | 特点 | 适用场景 |
|---|---|---|
| RestTemplate | 编程式,需手动拼接 URL | 简单调用 |
| OpenFeign | 声明式,接口 + 注解 | 微服务间调用(推荐) |
| WebClient | 响应式,非阻塞 | WebFlux 项目 |
| gRPC | 高性能二进制协议 | 内部高性能调用 |
| Dubbo | RPC 框架 | 阿里生态 |
OpenFeign 使用
OpenFeign 让远程调用就像调用本地方法一样简单:
Feign 客户端接口
// 声明远程调用接口
@FeignClient(
name = "user-service", // 服务名(从注册中心获取)
path = "/api/users", // 基础路径
fallbackFactory = UserClientFallback.class // 降级工厂
)
public interface UserClient {
@GetMapping("/{id}")
User getById(@PathVariable("id") Long id);
@PostMapping
User create(@RequestBody UserDTO dto);
@GetMapping
List<User> list(@RequestParam("ids") List<Long> ids);
}
使用 Feign 客户端
@Service
public class OrderService {
@Autowired
private UserClient userClient; // 注入后直接调用
public OrderVO getOrderDetail(Long orderId) {
Order order = orderRepository.findById(orderId);
// 远程调用,像本地方法一样
User user = userClient.getById(order.getUserId());
return new OrderVO(order, user);
}
}
启动类开启 Feign
@SpringBootApplication
@EnableFeignClients // 扫描 @FeignClient 接口
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
OpenFeign 原理
核心组件:
| 组件 | 作用 |
|---|---|
| Encoder | 将方法参数序列化为 HTTP 请求体(默认 Jackson) |
| Decoder | 将 HTTP 响应反序列化为 Java 对象 |
| Client | 发送 HTTP 请求(默认 HttpURLConnection) |
| RequestInterceptor | 请求拦截器(添加 Header 等) |
| ErrorDecoder | 错误响应处理 |
| Logger | 日志记录 |
降级处理
降级工厂
@Component
public class UserClientFallback implements FallbackFactory<UserClient> {
@Override
public UserClient create(Throwable cause) {
return new UserClient() {
@Override
public User getById(Long id) {
// 调用失败时返回默认值
log.warn("获取用户失败: {}", cause.getMessage());
return new User(id, "未知用户");
}
@Override
public User create(UserDTO dto) {
throw new ServiceException("用户服务不可用");
}
@Override
public List<User> list(List<Long> ids) {
return Collections.emptyList();
}
};
}
}
常用配置
application.yml
spring:
cloud:
openfeign:
client:
config:
default: # 全局配置
connect-timeout: 5000 # 连接超时 5s
read-timeout: 10000 # 读取超时 10s
logger-level: BASIC # 日志级别
user-service: # 对特定服务的配置
connect-timeout: 3000
read-timeout: 5000
compression:
request:
enabled: true # 开启请求压缩
response:
enabled: true # 开启响应压缩
请求拦截器(传递 Token)
Feign 请求拦截器
@Component
public class FeignAuthInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
// 从当前请求上下文获取 Token,传递给下游服务
ServletRequestAttributes attrs = (ServletRequestAttributes)
RequestContextHolder.getRequestAttributes();
if (attrs != null) {
String token = attrs.getRequest().getHeader("Authorization");
if (token != null) {
template.header("Authorization", token);
}
}
}
}
HTTP 客户端优化
默认的 HttpURLConnection 不支持连接池,推荐替换:
pom.xml — 使用 OkHttp
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
</dependency>
application.yml
spring:
cloud:
openfeign:
okhttp:
enabled: true
常见面试问题
Q1: OpenFeign 的工作原理?
答案:
@EnableFeignClients扫描所有@FeignClient接口- 为每个接口创建 JDK 动态代理
- 调用方法时,代理将方法签名(注解、参数)解析为 HTTP 请求
- 通过 LoadBalancer 从注册中心选择实例
- 使用 HTTP 客户端发送请求,将响应反序列化后返回
Q2: Feign 和 RestTemplate 的区别?
答案:
- RestTemplate:编程式,需要手动拼接 URL、设置参数、处理响应。代码冗长,维护困难
- OpenFeign:声明式,定义接口 + 注解即可。代码简洁,自动集成负载均衡和熔断
Q3: Feign 的超时配置?
答案:
Feign 的超时受两层控制:
- Feign 自身:
connect-timeout和read-timeout - Ribbon/LoadBalancer:也有超时配置
取较小值生效。建议统一在 Feign 层配置,关闭 Ribbon 的超时控制避免混淆。
Q4: 如何在 Feign 调用中传递请求头?
答案:
通过 RequestInterceptor 拦截器,在每次 Feign 调用前从 RequestContextHolder 获取当前请求的 Header(如 Authorization),添加到 Feign 请求中。注意在异步调用中 RequestContextHolder 不可用,需要手动传递。
相关链接
- Spring Cloud OpenFeign 文档
- 负载均衡 - Feign 内置负载均衡
- 熔断与降级 - Feign 降级处理