【源码】spring cloud openfeign负载均衡分析之FeignBlockingLoadBalancerClient 本文主要分析被 FeignClient 注解的接口类请求过程中负载均衡逻辑流程分析使用的依赖版本信息如下spring-boot.version3.2.1/spring-boot.version spring-cloud.version2023.0.0/spring-cloud.version com.alibaba.cloud.version2023.0.0.0-RC1/com.alibaba.cloud.version !-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-dependencies -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-dependencies/artifactId version${spring-boot.version}/version typepom/type scopeimport/scope /dependency !-- spring-cloud -- dependency groupIdorg.springframework.cloud/groupId artifactIdspring-cloud-dependencies/artifactId version${spring-cloud.version}/version typepom/type scopeimport/scope /dependency !--注册中心-- dependency groupIdcom.alibaba.cloud/groupId artifactIdspring-cloud-starter-alibaba-nacos-discovery/artifactId version${com.alibaba.cloud.version}/version /dependency背景平常我们代码里用FeignClien注解一个接口类实现一个远程接口如下FeignClient(name ServiceNameConstants.XXX, fallbackFactory XXXFactory.class) public interface RemoteXXXService { GetMapping(/XXX/getById) ResultXXX getById(RequestParam(Id) String Id); }被FeignClien注解的类在运行的时候容器会生成一个动态类从调用堆栈能看出最终调用到 FeignBlockingLoadBalancerClient 的 execute 方法下面我们详细分析这个方法调用的由来初始化与调用流程分析直接答案openfeign负载均衡使用需要注解和自动装配配合才能生效即EnableFeignClients与 spring-cloud-openfeign-core下的org.springframework.boot.autoconfigure.AutoConfiguration.imports 配合EnableFeignClients主要是决定是否要使用负载均衡引用了负载均衡依赖但是可以不使用该特性AutoConfiguration 主要是初始化一些负载均衡所需的基础设施负载均衡代理类生成流程使用open-feign我们一般会在启动类上添加注解 EnableFeignClients这个注解内容如下Retention(RetentionPolicy.RUNTIME) Target(ElementType.TYPE) Documented Import(FeignClientsRegistrar.class) public interface EnableFeignClients { ... }可以看出该注解类是一个 Import 的复合注解也就是说在启动过程中该注解具备Import的功能会引入FeignClientsRegistrar类我们看看FeignClientsRegistrar这个类是ImportBeanDefinitionRegistrar的子类应用启动后会执行registerBeanDefinitions方法注册FeignClient BeanDefinition我们直接来到核心方法org.springframework.cloud.openfeign.FeignClientsRegistrar#registerFeignClientprivate void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, MapString, Object attributes) { String className annotationMetadata.getClassName(); if (String.valueOf(false).equals( environment.getProperty(spring.cloud.openfeign.lazy-attributes-resolution, String.valueOf(false)))) { eagerlyRegisterFeignClientBeanDefinition(className, attributes, registry); } else { lazilyRegisterFeignClientBeanDefinition(className, attributes, registry); } }这个方法主要根据配置注册懒加载bean或者是立即实例化的类配置key是spring.cloud.openfeign.lazy-attributes-resolution 如果是true的话应该会加长应用启动时间只有配置了true才会懒初始化BeanDefinition的目标类是org.springframework.cloud.openfeign.FeignClientFactoryBean那么后续创建FeignClient 的代理类就由该类承担1、核心入口方法是org.springframework.cloud.openfeign.FeignClientFactoryBean#getTarget2、最终调用的方法是feign.ReflectiveFeign#newInstance(feign.Target, C)public T T newInstance(TargetT target, C requestContext) { ReflectiveFeign.TargetSpecificationVerifier.verify(target); MapMethod, InvocationHandlerFactory.MethodHandler methodToHandler this.targetToHandlersByName.apply(target, requestContext); InvocationHandler handler this.factory.create(target, methodToHandler); T proxy (T)Proxy.newProxyInstance(target.type().getClassLoader(), new Class[]{target.type()}, handler); for(InvocationHandlerFactory.MethodHandler methodHandler : methodToHandler.values()) { if (methodHandler instanceof DefaultMethodHandler) { ((DefaultMethodHandler)methodHandler).bindTo(proxy); } } return proxy; }到这里 Proxy 代理类就创建好了open-feign自动装配我们打开spring-cloud-openfeign-core的spring配置内容如下org.springframework.cloud.openfeign.hateoas.FeignHalAutoConfiguration org.springframework.cloud.openfeign.FeignAutoConfiguration org.springframework.cloud.openfeign.encoding.FeignAcceptGzipEncodingAutoConfiguration org.springframework.cloud.openfeign.encoding.FeignContentGzipEncodingAutoConfiguration org.springframework.cloud.openfeign.loadbalancer.FeignLoadBalancerAutoConfigurationFeignAutoConfiguration主要功能提供生成动态代理所需的默认组件如 Encoder、Decoder、Contract确保 EnableFeignClients 扫描到的接口能正确实例化。FeignAutoConfiguration主要功能FeignAutoConfiguration 自动检测项目中是否包含负载均衡依赖如 spring-cloud-starter-loadbalancer若存在则配置 LoadBalancerFeignClientConditionalOnClass(Feign.class) ConditionalOnBean({ LoadBalancerClient.class, LoadBalancerClientFactory.class }) AutoConfigureBefore(FeignAutoConfiguration.class) AutoConfigureAfter({ BlockingLoadBalancerClientAutoConfiguration.class, LoadBalancerAutoConfiguration.class }) EnableConfigurationProperties(FeignHttpClientProperties.class) Configuration(proxyBeanMethods false) // Order is important here, last should be the default, first should be optional // see // https://github.com/spring-cloud/spring-cloud-netflix/issues/2086#issuecomment-316281653 Import({ OkHttpFeignLoadBalancerConfiguration.class, HttpClient5FeignLoadBalancerConfiguration.class, Http2ClientFeignLoadBalancerConfiguration.class, DefaultFeignLoadBalancerConfiguration.class }) public class FeignLoadBalancerAutoConfiguration { Bean ConditionalOnBean(LoadBalancerClientFactory.class) ConditionalOnMissingBean(XForwardedHeadersTransformer.class) public XForwardedHeadersTransformer xForwarderHeadersFeignTransformer(LoadBalancerClientFactory factory) { return new XForwardedHeadersTransformer(factory); } }这个类通过Import引入了DefaultFeignLoadBalancerConfiguration其注入FeignBlockingLoadBalancerClientBean ConditionalOnMissingBean Conditional(OnRetryNotEnabledCondition.class) public Client feignClient(LoadBalancerClient loadBalancerClient, LoadBalancerClientFactory loadBalancerClientFactory, ListLoadBalancerFeignRequestTransformer transformers) { return new FeignBlockingLoadBalancerClient(new Client.Default(null, null), loadBalancerClient, loadBalancerClientFactory, transformers); }FeignBlockingLoadBalancerClient这个类是归属spring-cloud-starter-openfeign依赖groupIdorg.springframework.cloud/groupId artifactIdspring-cloud-starter-openfeign/artifactId我们看看execute方法public Response execute(Request request, Request.Options options) throws IOException { final URI originalUri URI.create(request.url()); String serviceId originalUri.getHost(); Assert.state(serviceId ! null, Request URI does not contain a valid hostname: originalUri); String hint getHint(serviceId); DefaultRequestRequestDataContext lbRequest new DefaultRequest(new RequestDataContext(buildRequestData(request), hint)); SetLoadBalancerLifecycle supportedLifecycleProcessors LoadBalancerLifecycleValidator .getSupportedLifecycleProcessors( loadBalancerClientFactory.getInstances(serviceId, LoadBalancerLifecycle.class), RequestDataContext.class, ResponseData.class, ServiceInstance.class); supportedLifecycleProcessors.forEach(lifecycle - lifecycle.onStart(lbRequest)); // A ServiceInstance instance loadBalancerClient.choose(serviceId, lbRequest); org.springframework.cloud.client.loadbalancer.ResponseServiceInstance lbResponse new DefaultResponse( instance); if (instance null) { String message Load balancer does not contain an instance for the service serviceId; if (LOG.isWarnEnabled()) { LOG.warn(message); } supportedLifecycleProcessors.forEach(lifecycle - lifecycle .onComplete(new CompletionContextResponseData, ServiceInstance, RequestDataContext( CompletionContext.Status.DISCARD, lbRequest, lbResponse))); return Response.builder().request(request).status(HttpStatus.SERVICE_UNAVAILABLE.value()) .body(message, StandardCharsets.UTF_8).build(); } // B String reconstructedUrl loadBalancerClient.reconstructURI(instance, originalUri).toString(); Request newRequest buildRequest(request, reconstructedUrl, instance); return executeWithLoadBalancerLifecycleProcessing(delegate, options, newRequest, lbRequest, lbResponse, supportedLifecycleProcessors); }A通过 loadBalancer 获得服务实例B获得真是的请求地址即nacos上注册的地址BlockingLoadBalancerClient上面FeignBlockingLoadBalancerClient A 和 B的地方都调用了loadBalancerClient的方法loadBalancerClient是一个org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient类对象。pom里依赖如下需要单独引入groupIdorg.springframework.cloud/groupId artifactIdspring-cloud-starter-loadbalancer/artifactId可以看出是负载均衡器提供的类BlockingLoadBalancerClient的choose方法public T ServiceInstance choose(String serviceId, RequestT request) { // C ReactiveLoadBalancerServiceInstance loadBalancer this.loadBalancerClientFactory.getInstance(serviceId); if (loadBalancer null) { return null; } else { // D ResponseServiceInstance loadBalancerResponse (Response)Mono.from(loadBalancer.choose(request)).block(); return loadBalancerResponse null ? null : (ServiceInstance)loadBalancerResponse.getServer(); } }C这个地方很奇特底层代码是根据serviceId找到缓存的GenericApplicationContext对象然后通过getBean的方式获得对象没有想通这样做的理由D如果从缓存中有找到ReactiveLoadBalancer则将结果封装成ServiceInstance对象。这个过程代码比较复杂reconstructURI 方法public URI reconstructURI(ServiceInstance serviceInstance, URI original) { return LoadBalancerUriTools.reconstructURI(serviceInstance, original); }最终调用到如下方法private static URI doReconstructURI(ServiceInstance serviceInstance, URI original) { String host serviceInstance.getHost(); String scheme (String)Optional.ofNullable(serviceInstance.getScheme()).orElse(computeScheme(original, serviceInstance)); int port computePort(serviceInstance.getPort(), scheme); if (Objects.equals(host, original.getHost()) port original.getPort() Objects.equals(scheme, original.getScheme())) { return original; } else { boolean encoded containsEncodedParts(original); return UriComponentsBuilder.fromUri(original).scheme(scheme).host(host).port(port).build(encoded).toUri(); } }最终从ServiceInstance 解析出目标服务信息返回URI对象最终http接口调用在 OpenFeign 结合 Spring Cloud LoadBalancer 的默认环境下由哪个核心类完成最终的 HTTP 调用答案是feign.Client.Default。在feign.Client.Default#execute方法打断点我们可以看到最终底层是通过 java.net.HttpURLConnection实现的publicHttpURLConnectiongetConnection(URLurl)throwsIOException{return(HttpURLConnection)url.openConnection();}虽然它足够简单但 HttpURLConnection 性能一般每次握手、无连接复用。在生产环境中建议通过配置切换为 Apache HttpClient 5feign.httpclient.ApacheHttpClient或 OkHttp它们支持连接池性能更好。使用HttpURLConnection时候超时时间通过配置文件进行配置格式如下spring:cloud:# openfeign配置openfeign:# 通过openfeign调用其他微服务超时设置client:config:default:# 日志级别loggerLevel:FULL# 请求超时时间msconnectTimeout:1000# 响应超时时间msreadTimeout:3000# 根据服务名字匹配的server-xx-xxx:# 日志级别loggerLevel:FULL# 请求超时时间msconnectTimeout:1000# 响应超时时间msreadTimeout:3000代码位置feign.Client.Default#convertAndSendover~~