最近这些年,微服务盛行。服务拆分越来越细、越来越多。为方便微服务之间的调用,各种 RPC 框架层出不穷,目的就是让开发者像调用本地方法一样调用微服务。
微服务之间交流的核心是网络请求,这个过程涉及到应用层和网络层:
一般情况下,我们想要实现网络通信,有两种选择:
两种方式的应用都十分广泛,各有各的好处。本文介绍的 Feign 组件便是直接使用现成的应用层通信协议 http 。
服务间通信伴随着很多问题,比如负载均衡、容错降级等,这些属于基础组件,与业务不相关,可以抽出来作独立存在。
有了基础组件,我们可以按需进行整合。这些整合逻辑有时也可以抽出来公用,比如 feign 与 hystrix 整合后的 Feign-Hystrix,SpringCloud 与 feign 的整合后的 SpringCloud-OpenFeign 等等。
整合的东西越来越多,很容易让人迷惑,你想过如何辨别吗?
破解的关键在于:先了解基础组件的使用方式,自己尝试去做整合
。因为原理具有相似性,清楚了基础组件的使用方式,再去看其他整合组件,这个时候往往能看到本质。
我们先来看看官方介绍:
看大标题,Feign 使得用 Java 写 Http 客户端更加容易
,这是 Feign 的核心能力!!!
怎么个简单法?
你想想,平时自己写 http 请求是不是需要自己构建请求头、请求体,然后发送请求、解析响应这些重复的过程?
好,现在这个过程不需要你来做了,Feign 将其抽象为一套通用逻辑,类似于写普通方法调用一样,Feign 做一层代理,封装这些冗余操作。
feign 大致提供了这些核心能力:
maven jar 包依赖:
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-core</artifactId>
<version>11.9.1</version>
</dependency>
先定义调用接口:
interface GitHub {
@RequestLine("GET /repos/{owner}/{repo}/contributors")
List<Contributor> contributors(@Param("owner") String owner, @Param("repo") String repo);
@RequestLine("POST /repos/{owner}/{repo}/issues")
void createIssue(Issue issue, @Param("owner") String owner, @Param("repo") String repo);
}
public static class Contributor {
String login;
int contributions;
}
public static class Issue {
String title;
String body;
List<String> assignees;
int milestone;
List<String> labels;
}
再进行调用:
public class MyApp {
public static void main(String... args) {
// 构建接口实现,本质是自动包装 http 能力
GitHub github = Feign.builder()
.decoder(new GsonDecoder()) // 解码器
.target(GitHub.class, "https://api.github.com");
// 像使用本地方法一样,直接调用(底层是 http 请求)
List<Contributor> contributors = github.contributors("OpenFeign", "feign");
for (Contributor contributor : contributors) {
System.out.println(contributor.login + " (" + contributor.contributions + ")");
}
}
}
以上是使用 Feign 最原始的方式,你可以进一步抽象,将 构建接口实现 抽象出来,使其成为公用逻辑。
比如通过注解 + 启动扫描的方式,在 Spring Cloud 体系下的 feign 整合便是做了这层处理。
先看看官方介绍:
你看,hystrix 专注于解决分布式系统延迟和容错问题
。换句话说,hystrix 主要解决服务通信间的网络延时、异常等,提供降级和熔断等能力。
还不够通俗?当你在服务 A 调用服务 B 的接口时,可能出现超时、异常的情况,很有可能是服务 B 压力过大,已不堪重负。这时,服务 A 该如何做?
我们可以考虑一些策略,比如,请求失败了,我们返回默认结果;失败率超过多少多少,直接短暂停用接口调用。
Hystrix 便是致力于解决这类问题,实现以上的各种处理策略,熔断、降级等。
遗憾的是,官方已经声明不在维护
:
不过,Hystrix 在 Netflix 稳定运行多年,足够稳健,你可以放心在生产环境使用。
hystrix 采用命令模式,每一类请求都需要实现抽象类 HystrixCommand
,并实现 run 方法(具体的业务逻辑):
public class CommandHelloWorld extends HystrixCommand<String> {
private final String name;
public CommandHelloWorld(String name) {
super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));
this.name = name;
}
@Override
protected String run() {
// a real example would do work like a network call here
return "Hello " + name + "!";
}
}
然后调用:
@Test
public void testSynchronous() {
assertEquals("Hello World!", new CommandHelloWorld("World").execute());
assertEquals("Hello Bob!", new CommandHelloWorld("Bob").execute());
}
看到了吧,就是这么简单,只不过在生产上需要结合业务做一些参数调整。
值得注意的是:hystrix 可不只是用于 feign,只要有熔断降级的场景,都可以考虑使用
。
比如:你想给数据库查询做熔断降级,在查询语句外包装一层 hystrix 即可,至于如何包装,可以参考开源的组件,比如 feign-hystrix。
Feign 是一套独立的组件,负责服务间通信;hystrix 也是一套独立的组件,负责服务间通信的熔断降级。
当 feign 也想要熔断降级能力时,直接整合 hystrix 即可。
业界有个不成文的习惯,对于整合其他组件,一般重新定义一个模块,命名为 xxx-xxx,所以你会看到,feign-hystrix 也是这样来的。
我们看看 feign-hystrix 官方定义:
feign 整合 hystrix 是这样的:直接在 feign 的 http 请求之上包装一层 hystrix,使其拥有熔断降级能力
。
你也可以这样理解,熔断降级能力是通过统计 http 请求的超时率、错误率来计算并选择处理行为,我们在 feign 的 http 之上包装一层 hystrix 的逻辑,本质就是统计超时、错误等指标,并根据这些指标作出一些反映。
我们看看 feign-hystrix
模块的实现:
代码量非常少,从实现上看,基本上和你自己使用 hystrix 没啥区别(不过,可能 feign 考虑的更加完善以及通用),主要有:
定义接口:
interface GitHub {
@RequestLine("GET /repos/{owner}/{repo}/contributors")
List<String> contributors(@Param("owner") String owner, @Param("repo") String repo);
}
定义 fallback 方法:
// This instance will be invoked if there are errors of any kind.
GitHub fallback = (owner, repo) -> {
if (owner.equals("Netflix") && repo.equals("feign")) {
return Arrays.asList("stuarthendren"); // inspired this approach!
} else {
return Collections.emptyList();
}
};
包裹 Http 请求:
GitHub github = HystrixFeign.builder()
...
.target(GitHub.class, "https://api.github.com", fallback);
就是这样几个操作,就让你的 feign 调用拥有了熔断降级的能力。
再近一步看,如果你生产上习惯使用 Spring 大家族,那就更加方便了。SpringCloud OpenFeign
内部帮你封装了 Hystrix 包裹
这层,它对外提供注解 @FeignClient
,通过扫描该注解,自动帮你完成这些动作。
Spring 是一个大家族,从内需(IOC、AOP …)到组件整合(Feign、Ribbon …),给开发者带来了极大的便利。
Spring 的终极使命是,让配置尽可能简洁、让开发者尽可能专注于业务,到了 SpringBoot 时代,已经有了质的提升。
对于一些优秀开源的外部组件,Spring 也想要纳入麾下,希望像使用自身组件一样使用外部组件,这就是 Spring 组件整合之路。
整合是好事,但可能让人越来越懵。为啥?比如与 feign 相关常见的组件有:Feign、Feign-Hystrix、SpringCloud-OpenFeign …
多了之后,你可能就会疑惑,这些组件之间到底都是啥关系?耐心的拨开这神秘的面纱,你就能搂到底浆!!!
SpringCloud-OpenFeign 本质也是将 Spring
与 Feign
进行整合,底层能力仍然由 Feign 提供。如果 Feign 需要熔断降级能力,就将 Feign-Hystrix 整合组件依赖进来;如果需要负载均衡,就将 Ribbon 整合进来。
看到了吧,Feign 本身是解决 Http 通信问题,这过程要考虑 负载均衡、熔断降级 等等,而这些问题有现成的优秀组件,直接组合在一起就可以了,这就是 SpringCloud-OpenFeign,看起来像粘合剂。
引入 jar 包:
implementation("org.springframework.cloud:spring-cloud-starter-openfeign:2.2.7.RELEASE")
启动 Feign:
@SpringBootApplication
@EnableFeignClients
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
定义 FeignClient:
@FeignClient("stores")
public interface StoreClient {
@RequestMapping(method = RequestMethod.GET, value = "/stores")
List<Store> getStores();
@RequestMapping(method = RequestMethod.GET, value = "/stores")
Page<Store> getStores(Pageable pageable);
@RequestMapping(method = RequestMethod.POST, value = "/stores/{storeId}", consumes = "application/json")
Store update(@PathVariable("storeId") Long storeId, Store store);
}
最小可用版的 SpringClound OpenFeign 案例算是完成了。在 Spring 体系下,StoreClient 就是一个 bean,直接调用方法即可。
当然,如果你想使用熔断降级能力,那就打开相应配置:
feign.hystrix.enabled=true
然后,我们再定义降级方法 fallback:
@Component
static class HystrixClientFallbackFactory implements FallbackFactory<HystrixClient> {
@Override
public HystrixClient create(Throwable cause) {
return new HystrixClient() {
@Override
public Hello iFailSometimes() {
return new Hello("fallback; reason was: " + cause.getMessage());
}
};
}
}
最终展现是这样:
@FeignClient(name = "hello", fallbackFactory = HystrixClientFallbackFactory.class)
protected interface HystrixClient {
@RequestMapping(method = RequestMethod.GET, value = "/hello")
Hello iFailSometimes();
}
就这几个步骤,让你的 feign 调用拥有熔断降级能力!
最后提一下,软件在不断升级,在 SpringClound-OpenFeign 的 3.x.x
版本已经将整合组件 feign-hystrix
剔除依赖。
因为 SpringClound-OpenFeign 自身对熔断降级做了一层抽象,并自己写 hystrix
的整合组件,同时也支持其他的熔断降级组件,比如 resilience4j
。
本文致力于讲述组件的核心能力、功能边界、整合方式和基本的使用
。
feign 和 hystrix 作为基础组件,分别解决 http 和 熔断降级问题。通常情况下,这两种会结合使用,考虑到这层因素,feign 提供了子模块 feign-hystrix 去整合两个组件。
多数情况下,我们不需要自己去做整合,引入 feign-hytrix 模块即可,早期的 springcloud-openfeign 也是如此。
市场上开源的熔断降级组件也不止 hystrix,Springcloud-openfeign
从 2.2.7.RELEASE 版本开始抽象熔断降级能力,并逐渐剔除 feign-hystrix 依赖,转而提供自身的抽象实现(整合):
花样越来越多,学会抓住问题的本质!
相关参考: