缘由
Spring Cloud 作为微服务框架,工作当中必用。原因也很简单,Spring cloud 依托Springboot,背靠Spring Framework,又有Eureka 和 Alibaba 两个大厂的支持,大多数的互联网公司会优先选择以此作为后端开发框架。在服务调用方面,OpenFeign 和 Dubbo 都是十分优秀的组件,OpenFeign 由 Spring cloud 推出,其使用十分流行。
简述
Spring Cloud OpenFeign 是在feign的基础上,与Spring Cloud集成,做出了很多的改变,比如SpringMvcContract,融入服务发现、负载均衡等等。对常用技术进行原理分析,提升技术积累的同时,还能了解OpenFeign的瓶颈,为后续的调优工作做准备。
正文
OpenFeign 使用Http协议调用服务接口,提供简洁的RPC调用,使用少量的注解就可以达到接近本地方法调用的效果。
使用
引入pom 依赖
org.springframework.cloud spring-cloud-starter-openfeign 功能开启
@EnableFeignClients({"com.lake.feign.uaa"}) @SpringBootApplication public class ProductApplication { public static void main(String[] args) { SpringApplication.run(ProductApplication.class, args); } }
定义feign接口
@FeignClient(value = "uaa", fallback = UaaEchoFallback.class) public interface UaaEchoFeign { @GetMapping(value = "/api/feign/uaa/echo", consumes = MediaType.APPLICATION_JSON_VALUE) ResultFeignBean
echo(@RequestParam(value = "echo") String echo); } - 其他服务需要对提供对应的Http接口。
- 服务当中引入UaaEchoFeign接口,调用本地方法就可以完成RPC调用。
OpenFeign的使用非常的方便,相比于单体应用开发,仅仅是多使用了一些注解。但是背后的技术原理值得分析一番,如此便捷的技术背后的技术也很值得大家学习。
原理
OpenFeign 的大体工作流程如下:
- 开启OepnFeign,扫描Feign接口,注册到SpringIoc。
- Bean创建时生成代理类。
- RPC调用时,执行封装Http请求。
- 使用服务发现获取可以访问的服务实例。
- 使用负载均衡挑选服务实例地址。
- 发起调用。
- 对调用结果进行解析处理。
Feign功能开启
@EnableFeignClients 注解开启了Feign功能的序幕。
- 注释说明此注解会扫描@FeignClient注解的类。
- 使用@EnableFeignClients注解时可以指定需要扫描的package 或者 class。
- 指定自定义的配置类。
- 指定@FeignClient标注的类,不需要再去执行扫描。
- @Import(FeignClientsRegistrar.class),FeignClientsRegistrar 拿到开启的接力棒。
@EnableFeignClients 注解在classpath中,会在Springboot启动时,扫描到此注解并解析其中的@Import注解导入的类。
FeignClientsRegistrar 实现ImportBeanDefinitionRegistrar接口,重写registerBeanDefinitions()。
- 加载@EnableFeignClients中指定的配置类
- 扫描@FeignClient注解类,或者注册@EnableFeignClients中指定的FeignClient类
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
// 加载@EnableFeignClients中指定的配置类
registerDefaultConfiguration(metadata, registry);
// 扫描@FeignClient注解类 或 注册@EnableFeignClients中指定的FeignClient类
registerFeignClients(metadata, registry);
}
public void registerFeignClients(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
LinkedHashSet candidateComponents = new LinkedHashSet<>();
Map attrs = metadata
.getAnnotationAttributes(EnableFeignClients.class.getName());
AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
FeignClient.class);
// 获取@EnableFeignClients中指定的FeignClient类
final Class>[] clients = attrs == null ? null
: (Class>[]) attrs.get("clients");
if (clients == null || clients.length == 0) {
// 扫描@FeignClient注解类
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);
scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
Set basePackages = getBasePackages(metadata);
for (String basePackage : basePackages) {
candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
}
}
else {
for (Class> clazz : clients) {
candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));
}
}
for (BeanDefinition candidateComponent : candidateComponents) {
if (candidateComponent instanceof AnnotatedBeanDefinition) {
// verify annotated class is an interface
AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
Assert.isTrue(annotationMetadata.isInterface(),
"@FeignClient can only be specified on an interface");
Map attributes = annotationMetadata
.getAnnotationAttributes(FeignClient.class.getCanonicalName());
String name = getClientName(attributes);
// 注册FeignClient注解中指定的配置类
registerClientConfiguration(registry, name,
attributes.get("configuration"));
// 注册FeignClient类,beanDefinition中指定使用 FeignClientFactoryBean 类
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
Feign功能的开启仅通过一个FeignClientsRegistrar,扫描FeignClient类,解析FeignClient注解中指定的属性,封装到 BeanDefinition,作为一个FeignClientFactoryBean,注册到IOC当中。
将接力棒传到 FeignClientFactoryBean 中,Bean创建时执行FactoryBean的getObject获取Feign接口代理类。
生成Feign代理
FeignClientFactoryBean.getObject() 会生成Feign接口代理类。
spring cloud openFeign starter中的 spring.factories中,EnableAutoConfiguration接口的实现类 FeignAutoConfiguration,会注册一个 FeignContext Bean,FeignContext 继承 NamedContextFactory,NamedContextFactory 实现 ApplicationContextAware,应用执行Aware回调时会设置NamedContextFactory的 parent,创建FeignContext 是就会将当前应用设置为父context。
@FeignClient注解中的value值是服务名,应用会为每个服务创建一个FeignContext,Feign 配置时支持服务级别的配置,比如为用户服务设置一个超时时间之类的。
Feign代理类创建时,从FeignContext中获取Feign.Builder Encoder Decoder Contract Client Targetar... 单例Bean,然后根据全局配置或者服务级别的Feign配置对Feign.Builder进行定制。
client 和 targetar 有多个实现,LoadBalancerFeignClient 具备负载均衡功能,DefaultTargeter feign的默认实现。Hystrix 和 seata 引入的同时也会引入新的实现。
Feign.Builder.build() 完成对Feign对象ReflectiveFeign的创建。
public Feign build() {
// ... 省略代码 client 参数是 LoadBalancerFeignClient
SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
logLevel, decode404, closeAfterDecode, propagationPolicy, forceDecoding);
ParseHandlersByName handlersByName =
new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,
errorDecoder, synchronousMethodHandlerFactory);
return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
}
ReflectiveFeign.newInstance() 创建Feign接口的代理类。
public T newInstance(Target target) {
Map nameToHandler = targetToHandlersByName.apply(target);
Map methodToHandler = new LinkedHashMap();
List defaultMethodHandlers = new LinkedList();
// feign 接口类中的方法转换为 DefaultMethodHandler 或者 SynchronousMethodHandler
for (Method method : target.type().getMethods()) {
if (method.getDeclaringClass() == Object.class) {
continue;
} else if (Util.isDefault(method)) {
DefaultMethodHandler handler = new DefaultMethodHandler(method);
defaultMethodHandlers.add(handler);
methodToHandler.put(method, handler);
} else {
methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
}
}
// 创建代理对象 ReflectiveFeign.FeignInvocationHandler
InvocationHandler handler = factory.create(target, methodToHandler);
T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
new Class>[] {target.type()}, handler);
for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
defaultMethodHandler.bindTo(proxy);
}
return proxy;
}
Feign代理对象 ReflectiveFeign.FeignInvocationHandler 生成到此已完成,其中使用到 父子Context 、 builder 构造、jdk反射、 FactoryBean 技术。过程还是比较精彩的。
RPC调用过程
ReflectiveFeign.FeignInvocationHandler中的invoke() 就是RPC的工作过程。
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// ... 省略代码
// 方法名和 DefaultMethodHandler 的映射。
return dispatch.get(method).invoke(args);
}
将接力棒交给 SynchronousMethodHandler.invoke(),feign接口方法到Http请求的转化。
public Object invoke(Object[] argv) throws Throwable {
// template 构建
RequestTemplate template = buildTemplateFromArgs.create(argv);
Options options = findOptions(argv);
Retryer retryer = this.retryer.clone();
while (true) {
try {
return executeAndDecode(template, options);
} catch (RetryableException e) {
continue;
}
}
}
Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
// 准备请求
Request request = targetRequest(template);
Response response;
long start = System.nanoTime();
try {
// 执行请求
response = client.execute(request, options);
// ensure the request is set. TODO: remove in Feign 12
// 请求响应
response = response.toBuilder()
.request(request)
.requestTemplate(template)
.build();
} catch (IOException e) {
}
long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
if (decoder != null)
// Decoder 可自定义
return decoder.decode(response, metadata.returnType());
CompletableFuture
集成Ribbon
准备
Open Feign 的负载均衡能力需要借助于Ribbon实现,通过将 httpClient 委托为 LoadBalancer 实现Rpc调用的负载均衡。
Open Feign 为每个服务设置一个FeignContext 来隔离服务之间的配置和功能实例,Ribbon也会为每个服务创建一个 SpringClientFactory 作为服务的Ribbon上下文。
RibbonAutoConfiguration 配置类会创建Bean: SpringClientFactory,LoadBalancerClient的实现类 RibbonLoadBalancerClient
RibbonClientConfiguration 配置类会创建Bean: IRule的实现类 ZoneAvoidanceRule,IPing的实现类DummyPing,ILoadBalancer的实现类ZoneAwareLoadBalancer,
OkHttpFeignLoadBalancedConfiguration 配置会创建Bean:Client 的实现类LoadBalancerFeignClient
通过配置类对Ribbon中的主要功能类进行实例化,在ribbon context 中获取功能类时,会借助父 context,获取通过配置类注入的Bean。
LoadBalancerFeignClient作用
Feign接口转换代理对象过程中,服务的上下文中会根据Client的配置注入到当前下文中,LoadBalancerFeignClient 就是Client的实现类。
LoadBalancerFeignClient 在Feign.Builder.build()中参与 SynchronousMethodHandler.Factory 对象的构造,在feign.ReflectiveFeign#newInstance 中 调用 feign.ReflectiveFeign.ParseHandlersByName#apply,apply()时会将 client实例注入到
synchronousMethodHandler 中。Feign接口方法调用的时候,就会使用到 LoadBalancerFeignClient。
Feign接口调用最终会通过 org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient#execute 进入 Ribbon的处理流程。
工作流程
ribbon 的入口是 LoadBalancerFeignClient#execute
获取上下文中的 IClientConfig 实现类 DefaultClientConfigImpl,
组装FeignLoadBalancer.RibbonRequest对象,使用委派模式将OkHttpClient的调用委派到RibbonRequest中。
获取上下文中的 FeignLoadBalancer 对象
执行 com.netflix.client.AbstractLoadBalancerAwareClient#executeWithLoadBalancer(S, com.netflix.client.config.IClientConfig)。
- 将 IClientConfig 和 RibbonRequest 组装为 LoadBalancerCommand。
- 进入 com.netflix.loadbalancer.reactive.LoadBalancerCommand#submit, 执行com.netflix.loadbalancer.reactive.LoadBalancerCommand#selectServer查找服务实例
- 获取服务实例的地址后,执行真正的Http调用 org.springframework.cloud.openfeign.ribbon.FeignLoadBalancer#execute,完成RPC请求。
选择服务实例
- 进入 com.netflix.loadbalancer.LoadBalancerContext#getServerFromLoadBalancer
- 进入 com.netflix.loadbalancer.ILoadBalancer#chooseServer, ILoadBalancer的实现类 DynamicServerListLoadBalancer 在FeignLoadBalancer创建时注入。最终方法会进入 BaseLoadBalancer 执行 chooseServer()。
- 进入 com.netflix.loadbalancer.BaseLoadBalancer#chooseServer,基础的负载均衡实现
- 进入 com.netflix.loadbalancer.IRule#choose,根据负载均衡算法挑选服务器
OpenFeign 是一个提供开发效率的框架,也对SpringCloud提供的功能进行融合,也是对微服务框架分析的第一站,后续会基于此文进行扩展,比如 Hystrix ribbon sluth...