声明式 HTTP 客户端的技术原理 · 限流 / 熔断 / 降级三大保护机制的可视化演示
OpenFeign 是一个声明式 HTTP 客户端:你只需要定义一个 Java 接口并加上注解,调用远程 HTTP 服务就像调用本地方法一样。它把「拼 URL、序列化参数、发 HTTP、解析响应」这些模板代码全部消灭掉。
// ① 定义接口 —— 不写任何实现 @FeignClient(name = "order-service", fallbackFactory = OrderFallback.class) public interface OrderClient { @GetMapping("/orders/{id}") OrderDTO getOrder(@PathVariable Long id); } // ② 业务代码 —— 像调本地方法一样用 @Autowired private OrderClient orderClient; OrderDTO order = orderClient.getOrder(42L);
接口 + 注解即契约。@FeignClient 声明目标服务名,Spring MVC 注解(@GetMapping 等)描述 HTTP 语义,零实现代码。
name = "order-service" 不是 URL,而是注册到 Nacos / Eureka 的服务名,配合负载均衡自动挑选实例。
Encoder / Decoder / Contract / Client / RequestInterceptor 全部可替换,可接入 OkHttp、Apache HttpClient,可统一注入鉴权头。
启动时,Spring 为每个 @FeignClient 接口生成动态代理对象。点击下方按钮,观察一次 getOrder(42) 调用的完整生命周期。
orderClient
.getOrder(42)
JDK Proxy
InvocationHandler
Contract + Encoder
+ Interceptor
服务名 → 实例
LoadBalancer 选择
GET /orders/42
真实网络 IO
Decoder → DTO
ErrorDecoder 处理异常
解析注解的规范。Spring Cloud 用 SpringMvcContract,因此 Feign 接口可以直接复用 Spring MVC 注解。
请求体序列化与响应反序列化,默认 SpringEncoder + Jackson。换成 Gson/Protobuf 只需替换 Bean。
把 HTTP 错误码翻译成异常。远程服务返回 500 → 抛出 FeignException → 被熔断器统计 → 触发降级。
限流回答的问题是「流量太大怎么办」。它不关心下游健不健康,只按固定节奏放行请求。下面是一个令牌桶:每 600ms 生成 1 个令牌,桶容量 5,有令牌才放行。试试快速连点「发送请求」。
FlowRule(QPS / 并发线程数两种维度)、Gateway 的 RequestRateLimiter(Redis 令牌桶)。Feign 侧触发限流抛 FlowException,同样会进入 fallbackFactory。熔断回答的问题是「下游已经病了,还要不要继续打」。它统计最近 N 次调用的失败率,超过阈值就「跳闸」:一段时间内直接拒绝调用,给下游恢复时间,之后进入半开状态试探。下面是一个完整的状态机模拟器。
正常放行,统计失败率
跳闸 5s,请求直接短路
放行 3 个试探请求
降级回答的问题是「实在调不通,给用户返回什么」。无论是被限流、被熔断短路,还是真的调用失败,Feign 都会把异常交给 fallbackFactory,由它返回一个兜底结果——缓存数据、空列表或友好的默认值,保证调用方不崩。
@Component public class OrderFallback implements FallbackFactory<OrderClient> { @Override public OrderClient create(Throwable cause) { return id -> { log.error("降级: {}", cause.getMessage()); return OrderDTO.cached(); // 兜底 }; } }
工厂方法能拿到 Throwable cause——是超时、500、被限流还是被熔断短路,日志里一清二楚,排障效率完全不同。
① 真实调用失败(超时 / 5xx)② 熔断器 OPEN,请求被短路(CallNotPermittedException)③ 被限流(FlowException)。三种异常殊途同归,都进 fallback。
用「有损但可用」换「系统不雪崩」。上面模拟器里关掉「启用降级」开关再压测一次,对比日志就能直观看到差异。
三者不是互相替代,而是同一条调用链上的三道防线。请求按顺序经过它们:
事前预防。按固定节奏放行,防止流量洪峰把下游打挂。
不管下游病没病,超量就拒。
事中保护。发现下游持续失败就跳闸,快速失败、给下游喘息时间,并防止故障向上游蔓延。
下游病了,就不再打它。
事后兜底。前两道防线拦下的请求、真实失败的请求,最终都由它给出兜底响应。
无论如何,给调用方一个交代。
| 维度 | 限流 | 熔断 | 降级 |
|---|---|---|---|
| 触发条件 | 流量超过设定阈值(QPS/并发) | 失败率 / 慢调用比例超阈值 | 调用失败、被熔断、被限流 |
| 关注对象 | 自身 / 入口流量 | 下游依赖的健康度 | 调用方的体验 |
| 行为 | 拒绝多余请求(429) | 短路请求,停止真实调用 | 返回兜底数据 |
| 恢复方式 | 流量回落即恢复 | 冷却 → 半开试探 → 关闭 | 随调用恢复自动退出 |
| 典型实现 | Sentinel FlowRule、Gateway Redis 限流 | Resilience4j、Sentinel DegradeRule | @FeignClient fallbackFactory |