openfeign

openfeign
https://www.cnblogs.com/crazymakercircle/p/11965726.html
https://www.cnblogs.com/crazymakercircle/p/11968479.html
https://www.cnblogs.com/crazymakercircle/p/10243749.html
https://www.cnblogs.com/crazymakercircle/p/11664812.html
https://www.cnblogs.com/crazymakercircle/p/11832534.html
https://blog.csdn.net/bz120413/article/details/122215774
https://zhuanlan.zhihu.com/p/621332668
https://www.zhihu.com/people/Shepherd


在微服务启动时,Feign会进行包扫描,对加@FeignClient注解的接口,按照注解的规则,创建远程接口的本地JDK Proxy代理实例。然后,将这些本地Proxy代理实例,注入到Spring IOC容器中。当远程接口的方法被调用,由Proxy代理实例去完成真正的远程访问,并且返回结果。

###   feign的组件

本地JDK Proxy代理实例
调用处理器 InvocationHandler/默认的调用处理器FeignInvocationHandler
Feign的MethodHandler方法处理器/默认的SynchronousMethodHandler:主要职责是完成实际远程URL请求,然后返回解码后的远程URL的响应结果。
客户端组件:负责端到端的执行URL请求。其核心的逻辑:发送request请求到服务器,并接收response响应后进行解码。


由于不同的feign.Client 实现类,内部完成HTTP请求的组件和技术不同,故,feign.Client 有多个不同的实现。这里举出几个例子:

(1)Client.Default类:默认的feign.Client 客户端实现类,内部使用HttpURLConnnection 完成URL请求处理;

(2)ApacheHttpClient 类:内部使用 Apache httpclient 开源组件完成URL请求处理的feign.Client 客户端实现类;

(3)OkHttpClient类:内部使用 OkHttp3 开源组件完成URL请求处理的feign.Client 客户端实现类。

(4)LoadBalancerFeignClient类:内部使用 Ribben 负载均衡技术完成URL请求处理的feign.Client 客户端实现类。

### ----------------------------------------------------------------------------------------------------Feign远程调用执行流程,大致分为4步,具体如下:
    
    服务调用例子:
                        @FeignClient(name = "ww-nacos")
                        @Component
                        public interface TestControllerFeign {
                            @RequestMapping("/t29/test")
                            String test();
                        }


从@EnableFeignClients注解类开始:
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {


}   
这个类的作用就是引入FeignClientsRegistrar类:

    class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
      // 注册动态代理类feignProxyService入口
      @Override
      public void registerBeanDefinitions(AnnotationMetadata metadata,
          BeanDefinitionRegistry registry) {
        registerDefaultConfiguration(metadata, registry);
        registerFeignClients(metadata, registry);
      }
    }

第1步:通过Spring IOC 容器实例,装配代理实例,然后进行远程调用。
-> SpringApplication.run()
...
-> SpringApplication.refreshContext()
-> SpringApplication.refresh()

-> AbstractApplicationContext.refresh()
-> AbstractApplicationContext.invokeBeanFactoryPostProcessors()
...
-> ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsFromRegistrars()

    private void loadBeanDefinitionsFromRegistrars(Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> registrars) {
        registrars.forEach((registrar, metadata) ->
                registrar.registerBeanDefinitions(metadata, this.registry, this.importBeanNameGenerator));
    }

-> ImportBeanDefinitionRegistrar.registerBeanDefinitions()
-> FeignClientsRegistrar.registerBeanDefinitions()

    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        registerDefaultConfiguration(metadata, registry);
        registerFeignClients(metadata, registry);
    }

-> FeignClientsRegistrar.registerFeignClients(metadata, registry)                                      扫描定义的Feign组件包路径下指定扫描类注解类型为@FeignClient的类
-> FeignClientsRegistrar.registerFeignClient()                                  

      private void registerFeignClient(BeanDefinitionRegistry registry,
          AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
        String className = annotationMetadata.getClassName();
        //定义Feign组件的创建工厂FeignClientFactoryBean
        BeanDefinitionBuilder definition = BeanDefinitionBuilder
            .genericBeanDefinition(FeignClientFactoryBean.class);
        validate(attributes);
        definition.addPropertyValue("url", getUrl(attributes));
        definition.addPropertyValue("path", getPath(attributes));
        String name = getName(attributes);
        definition.addPropertyValue("name", name);
        definition.addPropertyValue("type", className);
        definition.addPropertyValue("decode404", attributes.get("decode404"));
        definition.addPropertyValue("fallback", attributes.get("fallback"));
        definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
        definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
    ​
        String alias = name + "FeignClient";
        AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
    ​
        boolean primary = (Boolean)attributes.get("primary"); // has a default, won't be null
    ​
        beanDefinition.setPrimary(primary);
    ​
        String qualifier = getQualifier(attributes);
        if (StringUtils.hasText(qualifier)) {
          alias = qualifier;
        }
    ​
        BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, qualifiers);
        BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);                                            把@FeignClient标注的接口的bean定义注册到ioc容器中
        registerRefreshableBeanDefinition(registry, contextId, Request.Options.class, OptionsFactoryBean.class);       
        registerRefreshableBeanDefinition(registry, contextId, RefreshableUrl.class, RefreshableUrlFactoryBean.class);
      }
到这里,通过循环把@FeignClient标注的接口的bean定义注册到ioc容器中。这里可以看到使用了FeignClientFactoryBean创建了feignclient组件bean

-> AbstractApplicationContext.finishBeanFactoryInitialization()
   创建CompositeDiscoveryClient类型的bean
-> new CompositeDiscoveryClient()
    this.discoveryClients = {ArrayList@8139}  size = 2
                 0 = {NacosDiscoveryClient@10398} 
                 1 = {SimpleDiscoveryClient@10399}   
    
假如调用类如下:

@RefreshScope
@RestController
public class TestController {

    @Resource
    TestControllerFeign testFeignController;

    @RequestMapping("/test")
    public String test() {
        return testFeignController.test();
    }

}


...
-> AbstractApplicationContext.refresh()
-> AbstractApplicationContext.finishRefresh()
-> AbstractApplicationContext.publishEvent(new ContextRefreshedEvent(this))                          发布容器刷新事件    
    
-> RefreshScope.onApplicationEvent()
    start(event)
-> RefreshScope.start()
    eagerlyInitialize()
-> RefreshScope.eagerlyInitialize()
    private void eagerlyInitialize() {
        for (String name : this.context.getBeanDefinitionNames()) {
            BeanDefinition definition = this.registry.getBeanDefinition(name);
            if (this.getName().equals(definition.getScope()) && !definition.isLazyInit()) {
                Object bean = this.context.getBean(name);                                           其中的一个name=scopedTarget.testController即@FeignClient标注的feign接口
                if (bean != null) {
                    bean.getClass();
                }
            }
        }
    }

-> AbstractApplicationContext.getBean(name)                                                         name=scopedTarget.testController

-> AbstractBeanFactory.getBean()
    beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, null)
    
假如调用类如下:

@RestController
public class TestController {


    @Resource
    TestControllerFeign testFeignController;

    @RequestMapping("/test")
    public String test() {
        return testFeignController.test();
    }

}
会退栈执行到
-> AbstractApplicationContext.finishBeanFactoryInitialization()
    beanFactory.preInstantiateSingletons()
-> AbstractBeanFactory.getBean(String name)                                                        name=testController创建bean并解析依赖
   
-> AbstractAutowireCapableBeanFactory.populateBean()                                               name=testController的bean属性赋值
    PropertyValues pvsToUse = bp.postProcessProperties()
-> CommonAnnotationBeanPostProcessor.postProcessProperties()

    public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
        InjectionMetadata metadata = findResourceMetadata(beanName, bean.getClass(), pvs);
        try {
            metadata.inject(bean, beanName, pvs);
        }
        catch (Throwable ex) {
            throw new BeanCreationException(beanName, "Injection of resource dependencies failed", ex);
        }
        return pvs;
    }

-> InjectionMetadata$InjectedElement.inject() 
-> InjectionMetadata.inject() 
    field.set(target, getResourceToInject(target, requestingBeanName))
-> CommonAnnotationBeanPostProcessor.getResourceToInject()
   
    protected Object getResourceToInject(Object target, @Nullable String requestingBeanName) {
            return (this.lazyLookup ? buildLazyResourceProxy(this, requestingBeanName) :                      惰性注入或立即注入
                    getResource(this, requestingBeanName));
    }
   
-> CommonAnnotationBeanPostProcessor.getResource()
    autowireResource(this.resourceFactory, element, requestingBeanName)
-> CommonAnnotationBeanPostProcessor.autowireResource()   
    resource = autowireCapableBeanFactory.resolveDependency(descriptor,                                       解析依赖
                requestingBeanName, autowiredBeanNames, null);
-> DefaultListableBeanFactory.resolveDependency()
-> DependencyDescriptor.resolveCandidate()                                 
-> AbstractBeanFactory.getBean(name)                                                                          创建依赖的bean name = com.yfw.learn.TestControllerFeign    


以上两种方式都会进入AbstractBeanFactory.getObjectForBeanInstance()方法                                        ?为啥加上@RefreshScope走的路径不一样

-> AbstractBeanFactory.getObjectForBeanInstance()
    object = getObjectFromFactoryBean(factoryBean, beanName, !synthetic)
-> FactoryBeanRegistrySupport.getObjectFromFactoryBean()
    object = doGetObjectFromFactoryBean(factory, beanName)
-> FactoryBeanRegistrySupport.doGetObjectFromFactoryBean()
    object = factory.getObject()
-> FeignClientFactoryBean.getObject()
    getTarget()    
-> FeignClientFactoryBean.getTarget()                                                              如果@FeignClient配置的name或value属性,会以name为主
                                                                                                   ,执行loadBalance方法;否则以url属性为主
        
    class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean, ApplicationContextAware {
        @Override
        public Object getObject() throws Exception {
        return getTarget();
        }
        
        <T> T getTarget() {
          FeignContext context = applicationContext.getBean(FeignContext.class);
          //从Spring Context中获取到Feign的Builder
          Feign.Builder builder = feign(context);
          //@FeignClient注解没有配置URL属性
          if (!StringUtils.hasText(this.url)) {
            String url;
            if (!this.name.startsWith("http")) {
              url = "http://" + this.name;
            }
            else {
              url = this.name;
            }
            url += cleanPath();
            return loadBalance(builder, context, new HardCodedTarget<>(this.type,
                this.name, url));
          }
          //处理@FeignClient URL属性(主机名)存在的情况
          if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
            this.url = "http://" + this.url;
          }
          String url = this.url + cleanPath();
          //获取到调用客户端:Spring封装了基于Ribbon的客户端(LoadBalancerFeignClient)
          //1、Feign自己封装的Request(基于java.net原生),2、OkHttpClient(新一代/HTTP2),3、ApacheHttpClient(常规)
          Client client = getOptional(context, Client.class);
          if (client != null) {
            if (client instanceof LoadBalancerFeignClient) {
              // not lod balancing because we have a url,
              // but ribbon is on the classpath, so unwrap
              client = ((LoadBalancerFeignClient)client).getDelegate();
            }
            //设置调用客户端
            builder.client(client);
          }
          //DefaultTargeter或者HystrixTargeter,其中HystrixTargeter带熔断和降级功能
          //主要用户在Builder中配置调用失败回调方法
          Targeter targeter = get(context, Targeter.class);
          //Bean创建实际目标封装,最终生成InvocationHandler
          return targeter.target(this, builder, context, new HardCodedTarget<>(
              this.type, this.name, url));

        }
    }
    
    执行targeter.target方法时,最终会来到ReflectiveFeign的newInstance方法
    
-> Targeter.target()
-> DefaultTargeter.target()
    feign.target(target)    
-> ReflectiveFeign.newInstance()
    public <T> T newInstance(Target<T> target) {
       //核心方法,解析定义的@FeignClient组件中的方法和请求路径
       Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);                  ParseHandlersByName内部类:请求方法和路径解析器
       Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
       List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
    ​
       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)));
         }
       }
       //调用工厂Bean,创建执行Handler
       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原生的InvocationHandlerFactory.Default工厂来创建FeignInvocationHandler: 封装处理请求的Hander。
    最后说明下,默认的与 FeignInvocationHandler 相关的远程调用执行流程,在运行机制以及调用性能上,满足不了生产环境的要求,为啥呢? 
    大致原因有以下两点:
        (1) 没有远程调用过程中的熔断监测和恢复机制;
        (2) 也没有用到高性能的HTTP连接池技术。
    
退栈.....
    
    
到这里,feignClient和接口方法的解析、封装已完成,并放入到spring容器之中。    
即Feign在启动时,会为加上@FeignClient注解的接口,创建一个本地JDK Proxy代理实例,并注册到Spring IOC容器。


调用起点-> testFeignController.test()

-> ReflectiveFeign$FeignInvocationHandler.invoke()                                             Feign默认的调用处理器FeignInvocationHandle,
                                                                                               内部保持了一个远程调用方法实例和方法处理器
                                                                                               的一个Key-Value键值对Map映射-dispatch。
                                                                                               key= public abstract java.lang.String com.yfw.learn.TestControllerFeign.test()
                                                                                               value= 
                                                                                               FeignInvocationHandler执行invoke方法时会根据Java反射的方法实例从自己的 dispatch映射中,
                                                                                               找到hello()方法所对应的MethodHandler 方法处理器,然后调用其 invoke(…)方法。


    dispatch.get(method).invoke(args)


-> SynchronousMethodHandler.invoke()                                                            Feign默认的方法处理器为 SynchronousMethodHandler,
                                                                                                其invoke(…)方法主要是通过内部成员feign客户端成员 client,
                                                                                                完成远程 URL 请求执行和获取远程结果。
 
    executeAndDecode(template)                                                                    执行请求并解析   


-> SynchronousMethodHandler.executeAndDecode()
    Request request = targetRequest(template)                                                   重点:如果@FeignClient仅配置的服务名,此处会把RequestTemplate的实例转为
                                                                                                      feign.Request实例,注意此实例的属性仅有服务名,还没有服务地址
                                                                                                      
    client.execute(request, options)
->     FeignBlockingLoadBalancerClient.execute()                                                   重点:
    
    
    loadBalancerClientFactory.getInstances(serviceId, LoadBalancerLifecycle.class)
                                                                                                以服务名创建上下文,并刷新上下文,并创建getBean(beanName),其中
                                                                                                beanName = discoveryClientServiceInstanceListSupplier,
                                                                                                ...
                                                                                                调用到BlockingSupportConfiguration.discoveryClientServiceInstanceListSupplier()
                                                                                                创建ServiceInstanceListSupplier类型的bean。
                                                                                                调用到DiscoveryClientServiceInstanceListSupplier的构造函数对this.serviceInstances
                                                                                                赋值。
                                                                                                ...
                                                                                                NacosServiceDiscovery.getInstances(String serviceId)从nacos找到服务
                                                                                                
    
    ServiceInstance instance = loadBalancerClient.choose(serviceId, lbRequest)                  通过服务名选择服务实例


-> NamedContextFactory.getInstances()
    GenericApplicationContext context = getContext(name);    
-> NamedContextFactory.getContext(name)                                                            name=应用名称
    createContext(name)
-> NamedContextFactory.createContext(name)
    registerBeans(name, context);                                                               注册bean
    context.refresh();
-> AbstractApplicationContext.refresh()
-> AbstractApplicationContext.finishBeanFactoryInitialization()
-> AbstractBeanFactory.getBean(name)                                                            name=discoveryClientServiceInstanceListSupplier
-> LoadBalancerClientConfiguration
   $BlockingSupportConfiguration.discoveryClientServiceInstanceListSupplier()
   
   ServiceInstanceListSupplier.builder().
   withBlockingDiscoveryClient().withCaching().build(context)
-> ServiceInstanceListSupplierBuilder.withBlockingDiscoveryClient()

    public ServiceInstanceListSupplierBuilder withBlockingDiscoveryClient() {
         
        this.baseCreator = context -> {
            DiscoveryClient discoveryClient = context.getBean(DiscoveryClient.class);          从容器找到DiscoveryClient        

            return new DiscoveryClientServiceInstanceListSupplier(discoveryClient, 
                                                context.getEnvironment());
        };
        return this;
    }
   
-> new DiscoveryClientServiceInstanceListSupplier(DiscoveryClient delegate, 
                                                  Environment environment)
    this.serviceId = environment.getProperty(PROPERTY_NAME);
        resolveTimeout(environment);
    this.serviceInstances=   delegate.getInstances(serviceId)                                   ?

   其中delege是CompositeDiscoveryClient的实例,里面有 List<DiscoveryClient> discoveryClients

  退栈并执行loadBalancerClient.choose
-> BlockingLoadBalancerClient.choose()   

   ReactiveLoadBalancer<ServiceInstance> loadBalancer = loadBalancerClientFactory.getInstance(serviceId)  根据应用名称从容器中找到对应的bean ? 
        loadBalancer = {RoundRobinLoadBalancer@11363} 
                         position = {AtomicInteger@11365} "980"
                         serviceId = "ww-nacos"
                         serviceInstanceListSupplierProvider = {ClientFactoryObjectProvider@11366} 
   
-> CompositeDiscoveryClient.getInstances()

    public List<ServiceInstance> getInstances(String serviceId) {
        if (this.discoveryClients != null) {
            for (DiscoveryClient discoveryClient : this.discoveryClients) {                                遍历服务客户端,找服务
                List<ServiceInstance> instances = discoveryClient.getInstances(serviceId);
                if (instances != null && !instances.isEmpty()) {
                    return instances;
                }
            }
        }
        return Collections.emptyList();
    }
    即:
    ServiceInstance instance = loadBalancerClient.choose(serviceId, lbRequest)                  通过服务名选择服务实例
      instance = {NacosServiceInstance@11024} 
      serviceId = "ww-nacos"
      instanceId = null
      host = "169.254.157.153"
      port = 8080
      secure = false
      metadata = {HashMap@11057}  size = 6
      "nacos.instanceId" -> null
      "nacos.weight" -> "1.0"
      "nacos.cluster" -> "DEFAULT"
      "nacos.ephemeral" -> "true"
      "nacos.healthy" -> "true"
      "preserved.register.source" -> "SPRING_CLOUD"
    String reconstructedUrl = loadBalancerClient.reconstructURI(instance, originalUri)          解析出服务的url赋值给feign.Request实例的url属性
    
-> FeignBlockingLoadBalancerClient.buildRequest()                                               替换服务名为url
-> LoadBalancerUtils.executeWithLoadBalancerLifecycleProcessing()
    Response response = feignClient.execute(feignRequest, options)
    
    
开始通过 feign.Client 客户端成员,完成远程 URL 请求执行和获取远程结果
-> feign.Client.execute()
    public Response execute(Request request, Options options) throws IOException {
      HttpURLConnection connection = convertAndSend(request, options);
      return convertResponse(connection, request);
    }


-> feign.Client.convertAndSend()                                                             
      final URL url = new URL(request.url());
      final HttpURLConnection connection = this.getConnection(url);
    如果MethodHandler方法处理器实例中的client客户端是默认的 feign.Client.Default 实现类,则使用JDK自带的HttpURLConnnection类,完成远程 URL 请求执行和获取远程结果。
    如果MethodHandler方法处理器实例中的client客户端是 ApacheHttpClient 客户端实现类,则使用 Apache httpclient 开源组件,完成远程 URL 请求执行和获取远程结果。
    通过以上四步,应该可以清晰的了解到了 SpringCloud中的 feign 远程调用执行流程和运行机制

###------------------------------------------Feign中默认情况下的长连接
Feign中,默认情况下,使用的是JDK1.8中的 HttpURLConnection 基础连接类。该类的内部,使用了JDK1.8自带的 HttpClient 请求客户端类去负责完成底层的socket流操作。另外,JDK1.8还提供了一个简单的长连接缓冲类 KeepAliveCache,实现HttpClient 请求客户端类的缓存和复用。


三个类的所处位置为:HttpURLConnection 类处于 java.net 包中,而 HttpClient 类和KeepAliveCache类,则处于sun.net.www.http 包中。
HttpURLConnection、HttpClient、KeepAliveCache三个类的简单关系为:
每个HTTP请求都是一个HttpURLConnection实例,每个请求都会有一个 HttpClient 客户端实例,一个HttpClient 实例都持有一个TCP socket 长连接。如果 HttpClient 实例可以复用,则暂存在KeepAliveCache 缓存实例中。HttpURLConnection 会优先从缓存中取得合适的HttpClient 客户端,如果缓存中没有,HttpURLConnection 才会选择去创建新的HttpClient 实例。
通过三者之间的关系,可以看出: HttpClient 实例的复用,就是底层 TCP socket 长连接的复用。
JDK1.8 的 HttpClient 实例的复用的流程
下面,通过单步跟踪的方式,说一下HttpClient 实例的复用大致流程。
(1)首先,从默认的 feign.Client.Default 客户端的 execute 方法开始。
execute 方法首先会调用 convertAndSend(connection, request) 方法,打开一个URL连接实例,也即是一个 HttpURLConnection 类型的实例。然后,在convertResponse 方法中,开始获取响应码。这个时候,请求的整个处理过程,才真正开始。
(2) 通过单步跟踪发现,connection.getResponseCode() 语句执行过程中, 会通过调用HttpClient.New(..) 获取一个可用的 HttpClient 客户端实例。
HttpClient.New(..)是一个静态方法,大致的逻辑:它会调用 KeepAliveCache 类型的静态成员 kac 的get方法,去首先获取缓存中的HttpClient 客户端实例。KeepAliveCache实例的 get方法,会主要以请求的 url 值作为 key,去缓存中查找是否有绑定的 HttpClient 实例,如果有的话直接拿过来用。


(3) 如果KeepAliveCache 缓存实例中没有,则调用HttpClient的构造器,新建一个HttpClient 对象,这个构造方法的最后一行,会调用了openServer() 方法,这个时候才会去真正的建立TCP连接。
(4) 至此,HttpURLConnection 实例的getResponseCode() 方法,终于拿到了内部的 HttpClient 连接,这个时候可以向 SERVER 端写请求数据了,这个时候会调用 writeRequests 方法。HttpURLConnection 实例的writeRequests方法,首先会判断 httpClient.isKeepAlive 的值,该值默认是true,所以在请求上加上了 Connection:keep-alive 请求头 。
(5)writeRequests 方法写数据完成之后,会调用HttpClient.parseHTTP(..)方法,去解析服务端响应的数据,包括服务的响应头。
在parseHTTP(..)方法中,如果响应头中包含了Connection:keep-alive,并设置了Keep-Alive 头,比如含有以下内容:“Keep-Alive:timeout=xx,max=xxx” ,其中 timeout 表示服务端的‘空闲’超时时间,max表示长连接最多处理多少个请求。则这两个值,将覆盖掉 httpClient对象的keepAliveTimeout 和 keepAliveConnections 属性的值。
(6) parseHTTP(..)方法读取完数据之后,最终会调用到httpClient.finished方法,将当前httpClient对象,加入到缓存中,这个地方是实现 TCP 连接复用的关键。
(7)HttpClient的 putInKeepAliveCache方法,主要以请求的 url 值作为 key (因为这里的第二个参数总是写死为 null ),以当前 HttpClient 实例为 value,放入到KeepAliveCache 类型的静态成员 kac 缓存中,以便后面进行复用 。
通过以上的七步,JDK1.8 实现了HttpURLConnection 的长连接。

为什么Feign调用会默认在请求头中加上Connection:keep-alive?
在 sun.net.www.http.HttpClient 类的静态初始化代码部分,包括了 keepAliveProp 静态属性的初始化。keepAliveProp 静态属性的值,是决定HttpClient 客户端实例是否复用的关键之一,如果keepAliveProp 静态属性值为false,则无论如何都不会进行连接复用。sun.net.www.http.HttpClient 类的静态初始化代码节选如下:

可以看到,首先取得一个系统配置项 http.keepAlive 的值,如果该配置项的值没有做专门的设置或者修改,则sun.net.www.http.HttpClient 静态属性 keepAliveProp 的值,默认被赋值为true。
通过阅读源码可以知道,静态属性 keepAliveProp 的值,是决定HttpClient 的实例对象是否放入长连接缓冲池 KeepAliveCache 的一个重要关键属性值。也就是说,这个属性为true,则 HttpClient 实例对象具备复用的可能,否则,HttpClient 实例对象不能被复用。
至此,关于JDK1.8 实现如何实现长连接,也就介绍完了。不过,细心的读者可能会发现,HttpURLConnection 内部的长连接复用,和URL有关:只有在URL字符串相同的情况下,才能进行复用。这就有一个问题,如果URL中带有变量值,比如 /order/1/detail、/order/2/detail ,则不同的参数,不能进行HttpClient 实例对象的复用。


JDK默认的HttpClient 实例对象的复用的问题
和ApacheHttpClient 连接复用相比,JDK默认的HttpClient 实例对象的复用,有以下问题:
(1)JDK默认的 HttpClient 实例对象复用的粒度太小,只有URL相同的情况下,才能进行连接复用。而 ApacheHttpClient 连接复用的粒度则大很多,同路由的连接,就可以复用。
(2)在URL字符串变化比较大的场景下,JDK默认的 HttpClient 实例对象的内部连接,会保持一段时间才被释放,会占用系统的连接资源,更加不利于高并发。
所以,从以上两点出发,由于不能相同保证URL的请求数据巨大,所以不建议使用JDK默认的HttpClient 实例对象。建议在Feign中,使用ApacheHttpClient或okhttp 连接池进行连接的复用。


 

  • 42
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值