跳到主要内容

远程调用

问题

微服务之间如何进行远程调用?OpenFeign 的原理是什么?

答案

远程调用方式对比

方式特点适用场景
RestTemplate编程式,需手动拼接 URL简单调用
OpenFeign声明式,接口 + 注解微服务间调用(推荐)
WebClient响应式,非阻塞WebFlux 项目
gRPC高性能二进制协议内部高性能调用
DubboRPC 框架阿里生态

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 的工作原理?

答案

  1. @EnableFeignClients 扫描所有 @FeignClient 接口
  2. 为每个接口创建 JDK 动态代理
  3. 调用方法时,代理将方法签名(注解、参数)解析为 HTTP 请求
  4. 通过 LoadBalancer 从注册中心选择实例
  5. 使用 HTTP 客户端发送请求,将响应反序列化后返回

Q2: Feign 和 RestTemplate 的区别?

答案

  • RestTemplate:编程式,需要手动拼接 URL、设置参数、处理响应。代码冗长,维护困难
  • OpenFeign:声明式,定义接口 + 注解即可。代码简洁,自动集成负载均衡和熔断

Q3: Feign 的超时配置?

答案

Feign 的超时受两层控制:

  1. Feign 自身connect-timeoutread-timeout
  2. Ribbon/LoadBalancer:也有超时配置

取较小值生效。建议统一在 Feign 层配置,关闭 Ribbon 的超时控制避免混淆。

Q4: 如何在 Feign 调用中传递请求头?

答案

通过 RequestInterceptor 拦截器,在每次 Feign 调用前从 RequestContextHolder 获取当前请求的 Header(如 Authorization),添加到 Feign 请求中。注意在异步调用中 RequestContextHolder 不可用,需要手动传递。

相关链接