共计 7690 个字符,预计需要花费 20 分钟才能阅读完成。
原因
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 依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
-
性能开启
@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<String> 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<BeanDefinition> candidateComponents = new LinkedHashSet<>();
Map<String, Object> 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<String> 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<String, Object> 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() {
// ... 省略代码
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> T newInstance(Target<T> target) {Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
// 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<Object> resultFuture = new CompletableFuture<>();
asyncResponseHandler.handleResponse(resultFuture, metadata.configKey(), response,
metadata.returnType(),
elapsedTime);
try {return resultFuture.join();
} catch (CompletionException e) {}}
Rpc 调用过程逻辑还是比较简单的。
OpenFeign 是一个提供开发效率的框架,也对 SpringCloud 提供的性能进行交融,也是对微服务框架剖析的第一站,后续会基于此文进行扩大,比方 Hystrix ribbon sluth…