Skip to content

Feign 远程调用

一、Feign 概述

为什么需要 Feign

┌─────────────────────────────────────────────────────────────────┐
│                    RestTemplate vs Feign                          │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  RestTemplate:                                                 │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │ String url = "http://user-service/api/user/" + id;    │   │
│  │ User user = restTemplate.getForObject(url, User.class);│   │
│  └─────────────────────────────────────────────────────────┘   │
│  问题:URL 拼接麻烦、缺乏类型安全                               │
│                                                                  │
│  Feign(声明式):                                             │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │ @FeignClient("user-service")                            │   │
│  │ public interface UserClient {                           │   │
│  │     @GetMapping("/api/user/{id}")                      │   │
│  │     User getUser(@PathVariable Long id);                │   │
│  │ }                                                       │   │
│  └─────────────────────────────────────────────────────────┘   │
│  优势:像调用本地方法一样调用远程服务                          │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

二、快速开始

Maven 依赖

xml
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

启用 Feign

java
@SpringBootApplication
@EnableFeignClients  // 启用 Feign 客户端
public class OrderApplication { }

定义客户端

java
@FeignClient(name = "user-service", url = "http://localhost:8080")
public interface UserClient {
    
    @GetMapping("/api/user/{id}")
    User getUser(@PathVariable("id") Long id);
    
    @PostMapping("/api/user")
    User createUser(@RequestBody User user);
    
    @GetMapping("/api/user")
    List<User> getUsers(@RequestParam("ids") List<Long> ids);
}

使用

java
@Service
public class OrderService {
    
    @Autowired
    private UserClient userClient;
    
    public Order getOrderWithUser(Long orderId) {
        Order order = orderRepository.findById(orderId);
        User user = userClient.getUser(order.getUserId());
        order.setUser(user);
        return order;
    }
}

三、负载均衡

集成 Ribbon

yaml
spring:
  cloud:
    openfeign:
      client:
        config:
          default:
            logger-level: full
          user-service:
            logger-level: basic
java
// Ribbon 自动生效(需要引入 spring-cloud-starter-ribbon)
@FeignClient(name = "user-service")  // name = 服务名,从 Nacos 获取
public interface UserClient {
    // 自动负载均衡
}

负载均衡策略

yaml
user-service:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule  # 轮询
    # 其他策略:
    # RandomRule                    # 随机
    # WeightedResponseTimeRule     # 权重
    # BestAvailableRule            # 最小连接数

四、熔断与降级

Maven 依赖

xml
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-circuitbreaker-feign</artifactId>
</dependency>

启用熔断

yaml
spring:
  cloud:
    openfeign:
      circuitbreaker:
        enabled: true

定义降级

java
@Component
public class UserClientFallback implements UserClient {
    
    @Override
    public User getUser(Long id) {
        return User.builder().id(id).name("默认用户").build();
    }
    
    @Override
    public List<User> getUsers(List<Long> ids) {
        return Collections.emptyList();
    }
}

@FeignClient(name = "user-service", fallback = UserClientFallback.class)
public interface UserClient {
    @GetMapping("/api/user/{id}")
    User getUser(@PathVariable Long id);
}

五、请求拦截器

自定义拦截器

java
@Component
public class FeignAuthInterceptor implements RequestInterceptor {
    
    @Override
    public void apply(RequestTemplate template) {
        // 添加 Token
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        if (auth != null && auth.getCredentials() != null) {
            template.header("Authorization", auth.getCredentials().toString());
        }
        
        // 添加租户 ID
        template.header("X-Tenant-Id", TenantContext.getTenantId());
    }
}

日志拦截器

java
@Configuration
public class FeignLogConfig {
    
    @Bean
    Logger.Level feignLoggerLevel() {
        return Logger.Level.FULL;  // NONE/BASIC/HEADERS/FULL
    }
}

六、配置优化

超时配置

yaml
spring:
  cloud:
    openfeign:
      client:
        config:
          default:
            connect-timeout: 5000    # 连接超时
            read-timeout: 10000     # 读取超时
          user-service:
            connect-timeout: 3000
            read-timeout: 5000

GZIP 压缩

yaml
spring:
  cloud:
    openfeign:
      compression:
        request:
          enabled: true
        response:
          enabled: true

七、面试高频问题

Q1: Feign 和 Ribbon 关系?

Feign 默认集成 Ribbon,实现负载均衡

Q2: Feign 如何实现熔断?

集成 CircuitBreaker,配置 fallback 类,失败时返回降级结果

Q3: @FeignClient 的 name 是什么?

服务名,会从 Nacos/Eureka 获取实例列表

八、下一章预告

下一章我们将学习 Sentinel 熔断限流

  • Sentinel 概述
  • 流控规则
  • 熔断策略
  • Sentinel + Gateway

基于 MIT 许可发布