[toc]
| 单词 | 词性 | 音标 | 含义 |
|---|---|---|---|
| declarative | adj. | /dɪˈklærətɪv/ | 声明式的(只声明"调哪个接口",不用手写 HTTP 细节) |
| client | n. | /ˈklaɪənt/ | 客户端(这里指 OpenFeign 生成的远程调用代理) |
| timeout | n. | /ˈtaɪmaʊt/ | 超时(调用超过设定时间没响应就放弃) |
| retry | v./n. | /ˈriːtraɪ/ | 重试(调用失败后再次尝试) |
| circuit breaker | n. | /ˈsɜːkɪt ˈbreɪkə(r)/ | 熔断器(连续失败后快速打开开关,保护下游) |
| fallback | n. | /ˈfɔːlbæk/ | 降级(调用失败时执行的兜底逻辑,返回默认值而非抛异常) |
| interface | n. | /ˈɪntəfeɪs/ | 接口(Java 接口,OpenFeign 用它声明远程调用) |
Lecture05-SpringCloud-OpenFeign服务调用
目标:用 OpenFeign 把"服务间调用"变成"调用一个本地接口",并配置超时、重试、日志与服务降级(Fallback),替代第 03 讲的手写 HTTP 代码。
1、为什么用 OpenFeign
第 03 讲我们手写了"发现实例 + 拼 URL + RestTemplate 调用",问题:
- 模板代码多:每个接口都要重复写。
- 不参与负载均衡:每次都取第一个实例。
- 不好维护:远程接口一变,调用处要跟着改。
OpenFeign 的做法:定义一个 Java 接口 + 注解,声明"我要调用哪个服务的哪个路径",框架自动生成调用代理,还默认接入负载均衡。
2、接入 OpenFeign
1、加依赖
<!-- order-service/pom.xml -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- 为什么需要 loadbalancer? -->
<!-- 因为 OpenFeign 底层根据微服务名称查找对应微服务实例信息,这部分功能实际上是 loadbalancer 在底层做的 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
2、启用 Feign
在启动类上加 @EnableFeignClients:
@SpringBootApplication
@EnableFeignClients // 扫描并生成所有 @FeignClient 代理
public class OrderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
}
}
3、定义远程调用接口
注意:必须准确设置 provider 的微服务名称!因为这个名称就是 OpenFeign 底层获取 provider 微服务实例信息的依据!OpenFeign 底层会根据微服务名称到 Nacos 注册中心查找对应实例信息
// 声明:调用名为 user-service 的服务
@FeignClient(name = "user-service")
public interface UserClient {
// 对应 user-service 的 GET /user/{id}
@GetMapping("/user/{id}")
String getUser(@PathVariable("id") Long id);
}
4、业务里直接"注入调用"
@RestController
public class OrderController {
@Autowired
private UserClient userClient; // 注入 Feign 代理
@GetMapping("/order/user/{id}")
public String getOrderUser(@PathVariable Long id) {
// 像调用本地方法一样调用远程服务,无需手写 HTTP
return userClient.getUser(id);
}
}
底层:Feign 自动从 Nacos 发现
user-service的所有实例,配合 LoadBalancer 选一个,发出真实 HTTP 请求。
3、参数与返回值
Feign 用 Spring MVC 注解声明远程接口的参数:
@FeignClient(name = "user-service")
public interface UserClient {
// GET + 路径参数
@GetMapping("/user/{id}")
User getUser(@PathVariable("id") Long id);
// GET + 查询参数
@GetMapping("/user/list")
List<User> listUsers(@RequestParam("page") int page,
@RequestParam("size") int size);
// POST + 请求体
@PostMapping("/user")
User createUser(@RequestBody User user);
// 请求头(如携带 token)
@GetMapping("/user/info")
User getInfo(@RequestHeader("Authorization") String token);
}
远程接口的返回类型要能反序列化:复杂对象请让两端共用一个
DTO类(可放独立模块),字段名一致即可。
4、超时与重试配置
远程调用必须有超时保护,否则下游卡住会拖垮调用方。
spring:
cloud:
openfeign:
client:
config:
default: # 对所有 FeignClient 生效;也可写服务名
connect-timeout: 2000 # 连接超时(毫秒)
read-timeout: 3000 # 读取超时(毫秒)
httpclient:
enabled: true # 用 Apache HttpClient(比默认更稳)
提示:LoadBalancer 自带重试能力(第 06 讲配置);调用失败后的兜底处理见下一节"服务降级",熔断器原理在 L09(Sentinel)概念课展开。
5、服务降级:Fallback
1、为什么需要降级
超时和重试管的是"这一次调用怎么办"。可一旦下游彻底挂了(服务没启动、网络不通、一直超时),调用方不能跟着崩——用户点下单,不能因为用户服务挂了就返回一个 500 报错页。
**降级(Fallback)**就是在消费端给自己准备一条"退路":失败时不抛异常,而是执行一段兜底逻辑,返回一个"凑合能用"的结果。
正常:order-service → user-service → 返回真实用户信息
糟糕:order-service → user-service(挂了)→ 连带 order-service 崩溃 → 返回 500 错误信息
降级:order-service → user-service(挂了)→ 走兜底方法 → 返回默认值
降级的好处:
- order-service 能够得到一个和正常情况相同类型的数据、对象,不至于因为调用 user-service 失败而崩溃,order-service 这边代码可以继续执行
- order-service 可以给用户返回一个不太糟糕的响应结果,最起码不是抛异常或 500 这样的错误
2、加依赖 + 打开开关
Feign 自己不做熔断降级,要借助 Spring Cloud CircuitBreaker(本课用 Resilience4j 实现):
<!-- order-service/pom.xml -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
</dependency>
spring:
cloud:
openfeign:
circuitbreaker:
enabled: true # 关键开关:不打开的话 fallback 不生效(默认 false)
⚠️ 最容易踩的坑:
spring.cloud.openfeign.circuitbreaker.enabled默认是false。不打开它,@FeignClient(fallback = ...)会被静默忽略——不报错、不生效,调用失败照样抛异常,白白排查半天。
3、写降级类
降级类实现 Feign 接口,并声明成 Spring Bean:
@Component // 必须注册成 Bean
public class UserClientFallback implements UserClient {
@Override
public String getUser(Long id) {
// 兜底逻辑:返回安全的默认值
return "用户服务暂时不可用,请稍后重试";
}
}
注意:降级方法里不要再抛异常,也不要再去调别的远程服务——它本身就是最后一道防线。
4、挂到 Feign 接口上
@FeignClient(name = "user-service", fallback = UserClientFallback.class)
public interface UserClient {
@GetMapping("/user/{id}")
String getUser(@PathVariable("id") Long id);
}
5、验证降级效果
- 启动
order-service,故意不启动user-service; - 访问
http://localhost:8083/order/user/1; - 期望:不出现 500 报错页,而是返回兜底文案"用户服务暂时不可用,请稍后重试"。
对比实验:把
fallback属性删掉重启,同样的访问会直接抛异常(500)。这就是降级省下来的东西。
6、想知道"为什么失败"?用 fallbackFactory
fallback 拿不到异常原因,要打日志、区分失败类型时改用 fallbackFactory:
@FeignClient(name = "user-service", fallbackFactory = UserClientFallbackFactory.class)
public interface UserClient {
@GetMapping("/user/{id}")
String getUser(@PathVariable("id") Long id);
}
@Component
public class UserClientFallbackFactory implements FallbackFactory<UserClient> {
@Override
public UserClient create(Throwable cause) {
// cause 就是触发降级的异常,可打日志、可区分原因
System.out.println("调用 user-service 失败,触发降级:" + cause.getMessage());
// 接口只有一个方法,可用 Lambda 简写
return id -> "用户服务暂时不可用,请稍后重试";
}
}
| 方式 | 能拿到异常原因 | 适用场景 |
|---|---|---|
fallback | ❌ | 只要兜底返回值,简单场景 |
fallbackFactory | ✅ | 需要记日志、区分失败原因 |
7、两个注意点
① 容器里会出现两个同类型 Bean
加了 fallback 后,UserClient 在容器里有两个实现(Feign 代理 + 降级类)。
Feign代理负责发起远程调用;降级类负责返回兜底结果。
Spring Cloud OpenFeign 默认把 Feign 代理标为 @Primary,所以 @Autowired UserClient 注入的仍是代理,行为正常。
确有需要时可用 @FeignClient(primary = false) 关掉。
② TimeLimiter 默认只等 1 秒,比你的 read-timeout 更早触发
- 每个 Feign 方法对应一个熔断器,名字是
接口名#方法名(参数类型),如UserClient#getUser(Long)。 - Resilience4j 的 TimeLimiter 默认超时是 1 秒。也就是说,即便上一节配了
read-timeout: 3000,下游卡住 1 秒就会走降级——读超时根本没机会生效。要一起调整:
resilience4j:
timelimiter:
configs:
default:
timeout-duration: 3s # 与 read-timeout 对齐
circuitbreaker:
configs:
default:
failure-rate-threshold: 50 # 失败率超 50% 触发"熔断"
sliding-window-size: 10
降级 ≠ 熔断,两个概念放一起理解:
- 降级:调用失败就执行兜底逻辑,每次失败都会走;
- 熔断:失败率超过阈值后"跳闸",后续请求不再真正发出,直接走兜底;过一段时间再"半开"试探恢复。
熔断的完整内容在 L09(Sentinel)概念课展开。
8、延伸:生产里用 Resilience4j 还是 Sentinel
先说结论:Resilience4j 不是临时方案,它是 Spring Cloud 官方 CircuitBreaker 抽象下的标准实现之一(Hystrix 退役后,纯 Spring Cloud 体系就用它)。但 SCA 体系(本课程这套 Nacos + Gateway + Sentinel + Seata)里,生产更常用 Sentinel:
| 维度 | Resilience4j | Sentinel |
|---|---|---|
| 规则存放 | 配置文件 / 代码 | 专门启动的服务的控制台界面,可持久化到 Nacos |
| 改阈值 | 要重启发布 | 动态下发,不重启 |
| 能力范围 | 熔断、重试、限流、舱壁 | 熔断、QPS 限流、热点参数、系统自适应保护、网关限流 |
| 可视化 | 自己接监控 | 自带 Dashboard |
好消息:切换几乎不用改代码——降级写法一个字都不用动:
@FeignClient(name = "user-service", fallback = UserClientFallback.class)
只换依赖(spring-cloud-starter-alibaba-sentinel)和开关(feign.sentinel.enabled=true)。
-
更换依赖:把 spring-cloud-starter-circuitbreaker-resilience4j 换成 spring-cloud-starter-alibaba-sentinel
-
更换开关配置:把 spring.cloud.openfeign.circuitbreaker.enabled 换成 feign.sentinel.enabled
一句话记住:降级代码是稳定的,变的只是"谁来触发降级"。本讲先用 Resilience4j 把降级跑通,Sentinel 在 L09 展开。
6、日志与调试
1、为什么需要日志
从前端浏览器上发送请求给后端时,可以借助浏览器开发者工具查看请求报文、响应报文等等这些详细信息,那现在通过 OpenFeign 调用其它服务,没有浏览器开发者工具可以使用了,怎么看报文呢?
此时就需要在日志中打印请求和响应的相关信息
2、开启 Feign 日志级别
提问:为什么是 debug 级别?
回答:因为很多具体详细信息在 info 级别不打印,想看到这些详细信息就必须使用 debug 级别
提问:为什么不是针对微服务全局统一设置 debug 级别?
回答:微服务全局默认是 info 级别,如果全局改成 debug 那么微服务启动和运行期间打印的日志就过于多了;一方面可能由于打印日志太多导致微服务启动超时失败,另一方面调试时看到日志太多也很难找到我们自己关心的内容
logging:
level:
com.example.order.feign.UserClient: debug # 打印该接口的请求/响应
3、配置 Feign 日志输出级别(代码)
import feign.Logger; // 注意是 feign.Logger,不是 java.util.logging.Logger,也不是 slf4j
@Configuration
public class FeignLogConfig {
@Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL; // NONE / BASIC / HEADERS / FULL
}
}
NONE:不打印(默认)。BASIC:只打印 URL 与方法。HEADERS:在 BASIC 基础上,再打印请求/响应的头信息(不打印 body)。FULL:打印请求/响应头与体(排查问题常用)。

7、课堂练习
- 给
order-service接入 OpenFeign,定义UserClient调用user-service的/user/{id}。 - 加
connect-timeout: 2000、read-timeout: 3000,验证配置不报错。 - 把 Feign 日志调到
FULL,观察一次调用的完整请求响应。 - 给
UserClient加fallback(或fallbackFactory):停掉 user-service,验证接口返回兜底文案而不是 500。 - (挑战)给
UserClient加一个 POST 创建用户的方法,测试@RequestBody传参。
8、本讲小结
- OpenFeign 用"接口 + 注解"声明远程调用,替代手写 HTTP。
- 三步:加依赖 → @EnableFeignClients → @FeignClient 定义接口,业务注入调用。
- 参数用 Spring MVC 注解:
@PathVariable / @RequestParam / @RequestBody / @RequestHeader。 - 务必配置超时;Feign 默认自动接入 Nacos 注册中心与 LoadBalancer 负载均衡。
- 调用失败要有降级兜底:加 circuitbreaker 依赖 +
spring.cloud.openfeign.circuitbreaker.enabled=true+@FeignClient(fallback = ...);需要异常原因就用fallbackFactory。
9、本节验证
- 理解 OpenFeign 声明式调用的价值(不手写 HTTP 细节)✅
- 会加依赖、启用 Feign、定义远程调用接口并注入调用 ✅
- 会处理参数与返回值 ✅
- 会配置超时与重试,开启 Feign 日志调试 ✅
- 会配置 fallback 降级:知道
circuitbreaker.enabled不打开就静默失效、fallback 与 fallbackFactory 的区别、TimeLimiter 默认 1 秒会提前触发降级 ✅ - 知道生产选型:SCA 体系用 Sentinel、纯 Spring Cloud 用 Resilience4j,切换时 fallback 代码不用改 ✅