本文主要讲述 Feign 是如何注册到 Spring 容器、融合 Ribbon进行负载均衡、进行 RPC 调用。
简单提一下项目中一般都是如何使用 Feign 的,首先声明一个@FeignClient ,定义 RPC 调用方法 ,然后像调用本地方法一样,调用远程服务的方法
@FeignClient(value = "service-order",path = "/order")
public interface OrderFeignService {
@RequestMapping("/findOrderByUserId/{userId}")
R findOrderByUserId(@PathVariable("userId") Integer userId);
}
@Autowired
OrderFeignService orderFeignService;
R findOrderByUserId(@PathVariable("id") Integer id) {
R result = orderFeignService.findOrderByUserId(id);
return result;
}
这样一来,我们省去了自己去配置 RestTemplate 或其他 HTTPClient 的麻烦,但是简单方便的同时你是否会有一些疑惑:
我们使用@Autowired注入的 OrderFeignService,那么它一定是一个 Spring Bean,它是什么时候,如何被注入到 Spring 容器的?
- Feign 是如何执行 findOrderByUserId()的?
- @RequestMapping 是 SpringMVC 的注解呀,怎么在 Feign 中也会生效呢?
- Feign 是如何整合 Ribbon 的?
- Ribbon 是如何获取注册中心的服务的?
- Ribbon 是如何进行负载均衡的?
理解了上面的这些问题,我们也就明白了 Feign 是如何进行调用的,那么带着这些问题我们来一步步分析
@FeignClient 注册
@EnableFeignClients 注解
根据 SpringBoot 自动装配的思想,先猜想下一定会有@Enablexxx ,然后再有@Import(xxx.class),来进行 Feign 的自动注入
要使用 Feign 首先要
- 引入 Feign 的 Maven 依赖,接着一定要在启动类上添加注解
@EnableFeignClients @EnableFeignClients @Import(FeignClientsRegistrar.class) - 注册
@FeignClient
FeignClientsRegistrar.class
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
this.registerDefaultConfiguration(metadata, registry);
this.registerFeignClients(metadata, registry);
}
1. 扫描并注册 FeignClient 为 BeanDefinition
public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
扫描所有 @FeignClient 标注的类
Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter( FeignClient.class);
for (String basePackage : basePackages) {
Map<String, Object> attributes = annotationMetadata.getAnnotationAttributes(FeignClient.class.getCanonicalName());
registerFeignClient(registry, annotationMetadata, attributes);
} } }
2. 将 FeignClient 包裹成 FeignClientFactoryBean
private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata,
Map<String, Object> attributes) {
String className = annotationMetadata.getClassName();
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);
definition.addPropertyValue("fallbackFactory",attributes.get("fallbackFactory")); definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
FeignClientFactoryBean BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, new String[] { alias });
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
3. FeignClientFactoryBean #getObject(),生成FeignClient 的代理对象
public Object getObject() throws Exception {
return getTarget();
}
<T> T getTarget() {
FeignContext context = applicationContext.getBean(FeignContext.class);
Client client = getOptional(context, Client.class);
Targeter targeter = get(context, Targeter.class);
return (T) targeter.target(this, builder, context, new HardCodedTarget<>(type, name, url));
}
public <T> T target(Target<T> target) {
return build().newInstance(target);
}
4.生成FeignClient 的动态代理 FeignInvocationHandler
public <T> T newInstance(Target<T> target) {
MethodHandler Map<String, MethodHandler> nameToHandler = targetoHandlersByName.apply(target);
Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
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;
}
- FeignInvocationHandler 执行 invoke ,一步步调用,最终会调用 Client 的 execute()方法,执行远程调用
总结:
被@FeignClient 标记的 Interface 会在开启了@EnableFeignClients 之后,被 Spring 扫描到容器中,并且生成一个 FeignInvocationHandler 的动态代理
FeignClient 调用
上面我们已经分析完了FeignClient 是如何创建动态代理的,在我们按着FeignInvocationHandler 的 invoke() 方法往下分析之前,先看下上图中步骤 2,这一步就是 @RequestMapping如何在 Feign 中生效的
回头看下生成动态代理过程中有段代码
Map<String, MethodHandler> nameToHandler = targetoHandlersByName.apply(target);
这里完成了 Contract 对 SpringMVC 的解析
public Map<String, MethodHandler> apply(Target key) {
List<MethodMetadata> metadata = contract.parseAndValidatateMetadata(key.type());
Map<String, MethodHandler> result = new LinkedHashMap<String, MethodHandler>();
for (MethodMetadata md : metadata) {
BuildTemplateByResolvingArgs buildTemplate;
if (!md.formParams().isEmpty() && md.template().bodyTemplate() == null) {
buildTemplate = new BuildFormEncodedTemplateFromArgs(md, encoder);
} else if (md.bodyIndex() != null) {
buildTemplate = new BuildEncodedTemplateFromArgs(md, encoder);
} else {
buildTemplate = new BuildTemplateByResolvingArgs(md);
}
result.put(md.configKey(), factory.create(key, md, buildTemplate, options, decoder, errorDecoder));
}
return result;
}
所以我们可以看到,Feign 之所以能用 SpringMVC 注解是因为专门对这层注解做了解析。
FeignInvocationHandler#invoke()
FeignInvocationHandler#invoke() --> executeAndDecode(template, options)
Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
RequestInterceptor Request request = targetRequest(template);
Response response;
try {
response = client.execute(request, options);
}
}
LoadBalancerFeignClient#execute()
public Response execute(Request request, Request.Options options) throws IOException {
try {
FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest( this.delegate, request, uriWithoutHost);
IClientConfig requestConfig = getClientConfig(options, clientName);
return lbClient(clientName).executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();
}
lbClient(clientName) 整合 Ribbon 实例化出 FeignLoadBalancer
lbClient(String clientName) --> this.lbClientFactory.create(clientName)
public FeignLoadBalancer create(String clientName) {
FeignLoadBalancer client = this.cache.get(clientName);
if (client != null) {
return client;
}
IClientConfig config = this.factory.getClientConfig(clientName);
ILoadBalancer lb = this.factory.getLoadBalancer(clientName);
ServerIntrospector serverIntrospector = this.factory.getInstance(clientName, ServerIntrospector.class);
client = this.loadBalancedRetryFactory != null ? new RetryableFeignLoadBalancer(lb, config, serverIntrospector, this.loadBalancedRetryFactory) : new FeignLoadBalancer(lb, config, serverIntrospector); this.cache.put(clientName, client);
return client;
}
FeignLoadBalancer如何负载均衡选择 Server
public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
LoadBalancerCommand<T> command = buildLoadBalancerCommand(request, requestConfig);
try {
return command.submit(
new ServerOperation<T>() {
@Override
public Observable<T> call(Server server) {
URI finalUri = reconstructURIWithServer(server, request.getUri());
S requestForServer = (S) request.replaceUri(finalUri);
try {
return Observable.just(AbstractLoadBalancerAwareClient.
this.execute(requestForServer, requestConfig));
}
catch (Exception e) {
return Observable.error(e);
}
}
}).toBlocking().single();
}
}
在 submit() 中调用selectServer(),进行 url 替换,选择一个服务实例
LoadBalancerCommand.java
public Observable<T> submit(final ServerOperation<T> operation) {
final ExecutionInfoContext context = new ExecutionInfoContext();
if (listenerInvoker != null) {
try {
listenerInvoker.onExecutionStart();
} catch (AbortExecutionException e) {
return Observable.error(e);
}
}
final int maxRetrysSame = retryHandler.getMaxRetriesOnSameServer();
final int maxRetrysNext = retryHandler.getMaxRetriesOnNextServer();
Observable<T> o = (server == null ? selectServer() : Observable.just(server))
......省略部分代码
return operation.call(server)
... ......省略部分代码
private Observable<Server> selectServer() {
return Observable.create(new OnSubscribe<Server>() {
@Override public void call(Subscriber<? super Server> next) {
try {
Server server = loadBalancerContext.getServerFromLoadBalancer(loadBalancerURI, loadBalancerKey);
next.onNext(server); next.onCompleted();
} catch (Exception e) {
next.onError(e);
}
}
}
); }
- 构造了一个LoadBalancerCommand
- 构造了一个ServerOperation,包含了发起http调用的逻辑,作为参数传入- -
LoadBalancerCommand.submit方法,后面会进行回调 - 在submit方法中,会调用selectServer方法,选择服务实例
selectServer方法调用loadBalancerContext.getServerFromLoadBalancer, 最终调用负载均衡器chooseServer方法选择一个服务实例, - 拿到服务实例后,将Server对象传入ServerOperation的call方法进行回调
ServerOperation用server的信息替换host里的服务名,拿到真正的请求地址 - 再调用子类也就是FeignLoadBalancer.execute方法执行http请求
- 默认的connectTimeout和readTimeout都是1000毫秒
- 响应结果封装为RibbonResponse
执行 http 请求
获取到了远程服务的真实地址,就可以采用 Http 方式调用了,问题是可提供Http 调用的方式有那么多(OkHttp 、ApacheHttpClient、RestTemplate 等等),Feign 怎么知道用哪个呢?
答案在 FeignAutoConfiguration 里
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(ApacheHttpClient.class)
@ConditionalOnMissingClass("com.netflix.loadbalancer.ILoadBalancer")
@ConditionalOnMissingBean(CloseableHttpClient.class)
@ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true)
protected static class HttpClientFeignConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(OkHttpClient.class)
@ConditionalOnMissingClass("com.netflix.loadbalancer.ILoadBalancer")
@ConditionalOnMissingBean(okhttp3.OkHttpClient.class)
@ConditionalOnProperty("feign.okhttp.enabled")
protected static class OkHttpFeignConfiguration {
通过 @ConditionalOnClass 、@ConditionalOnProperty 等条件注解 来匹配用哪个 http 客户端,所以我们在使用过程中也是无感知的,只要我们引入 OkHttp 或者 ApacheHttpClient 相关的 Maven 依赖,就可以完成调用了
至此,FeignClient 被注册,被动态代理,被执行,整个流程已经清晰了,上面的问题也应该都有了答案,还有一个:Ribbon 是如何知道我们用到的是哪个注册中心,是 Eureka 是 Nacos ?他又是如何获取注册中心上的服务的?
Ribbon 获取注册中心服务路由
既然我们在使用过程中没有自己指定,那么我猜想是不是 Ribbon 自己注册的时候自己会选择呢? 我们就从Ribbon 的注册入手 RibbonClientConfiguration ,果然配置ILoadBalancer
在 RibbonClientConfiguration 中会配置 ILoadBalancer ,会返回一个 ZoneAwareLoadBalancer 实例
@Bean
@ConditionalOnMissingBean
public ILoadBalancer ribbonLoadBalancer(IClientConfig config, ServerList<Server> serverList,
ServerListFilter<Server> serverListFilter, IRule rule, IPing ping,ServerListUpdater serverListUpdater) {
return (ILoadBalancer)(this.propertiesFactory.isSet(ILoadBalancer.class, this.name) ?
(ILoadBalancer)this.propertiesFactory.get(ILoadBalancer.class, config, this.name) :
new ZoneAwareLoadBalancer(config, rule, ping, serverList, serverListFilter, serverListUpdater));
}
更新注册中心列表
restOfInit() --> restOfInit() --> updateListOfServers --> servers = serverListImpl.getUpdatedListOfServers();
public void updateListOfServers() {
List<T> servers = new ArrayList<T>();
if (serverListImpl != null) {
servers = serverListImpl.getUpdatedListOfServers();
LOGGER.debug("List of Servers for {} obtained from Discovery client: {}",
getIdentifier(), servers);
if (filter != null) {
servers = filter.getFilteredListOfServers(servers);
LOGGER.debug("Filtered List of Servers for {} obtained from Discovery client: {}",
getIdentifier(), servers);
}
}
updateAllServerList(servers);
}
ServerList serverListImpl
获取服务列表是通过 serverListImpl 来获取的,serverListImpl 是一个interface,不同的注册中心服务商会实现 ServerLis, 比如 Nacos 的实现NacosServerList 。
这样我们就不用指定,Ribbon 就自己知道是从哪个注册中心获取了。
|