• 曹工说mini-dubbo(2)--分析eureka client源码,想办法把我们的服务提供者注册到eureka server(上)


    前言

    eureka是spring cloud Netflix技术体系中的重要组件,主要完成服务注册和发现的功能;那现在有个问题,我们自己写的rpc服务,如果为了保证足够的开放性和功能完善性,那肯定要支持各种注册中心。目前我们只支持redis注册中心,即服务提供者,在启动的时候,将自身的ip+端口信息写入到redis,那,我们是否注册到 eureka中呢?

    这个想法可行吗?可行。eureka client 和eureka server间,无非是网络通信,既然是网络通信,那就有网络协议,那我们的应用,只要遵照eureka server的协议来,就可以接入。

    另外,eureka server没有采用spring mvc来实现,而是采用了jersey框架,这个框架啥意思呢,可以理解为对Restful的实现。我从网上找了一段(https://www.jianshu.com/p/88f97b90963c):

    SpringMVC在开发REST应用时,是不支持JSR311/JSR339标准的。如果想要按照标准行事,最常用的实现了这两个标准的框架就是Jersey和CxF了。但是,因为Jersey是最早的实现,也是JSR311参考的主要对象,所以,可以说Jersey就是事实上的标准(类似Hibernate是JPA的事实上的标准),也是现在使用最为广泛的REST开发框架之一。

    因为eureka server采用了jersey,所以eureka client最终也是使用了配套的jersey client来和服务端通信。

    所以,eureka client,里面其实依赖了一堆jersey的包:

    注意,上面的jersey-client、jersey-core等包,其group id都是这样的:

        <dependency>
          <groupId>com.sun.jersey</groupId>
          <artifactId>jersey-client</artifactId>
          <version>1.19.1</version>
          <scope>runtime</scope>
        </dependency>
    
    

    但是,不知道为啥,eureka client中,最终并没有完全使用jersey-client,而是使用了

        <dependency>
          <groupId>com.sun.jersey.contribs</groupId>
          <artifactId>jersey-apache-client4</artifactId>
          <version>1.19.1</version>
          <scope>runtime</scope>
        </dependency>
    
    

    这个包,内部引入了:

            <dependency>
                <groupId>org.apache.httpcomponents</groupId>
                <artifactId>httpclient</artifactId>
                <version>4.1.1</version>
            </dependency>
    
    

    这个包,你可以简单理解为,jersey-client变成了一个接口,jersey-apache-client4是它的一个实现,实现里,用了httpClient去实现。

    httpClient,没有几个java同学不知道吧?这么做是可行的,因为你最终通信,还是http,不管你服务端框架,是jersey、还是spring mvc、甚至以前的struts,这都不重要。

    所以,大家在下面的源码中看到jersey的时候,脑海里可以有这么一张图。从上层到底层的接口,分别是:

    CloudEurekaClient
           ...
    DiscoveryClient  
    	...
    EurekaClient
    	...
    JerseyClient
    	...
    HttpClient	
    

    在此之前,我们还是先分析下eureka client 注册到eureka server的源码。

    源码环境

    minidubbo代码和相关博文在:
    曹工说mini-dubbo(1)--为了实践动态代理,我写了个简单的rpc框架
    https://gitee.com/ckl111/mini-dubbo

    代码很简单,不过还是给个代码链接吧:

    https://gitee.com/ckl111/all-simple-demo-in-work-1/tree/master/eureka-client

    主要就是在pom.xml中,引入:

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    

    然后启动类:

    @SpringBootApplication
    public class DemoApplication {
    
    	public static void main(String[] args) {
    		SpringApplication.run(DemoApplication.class, args);
    	}
    
    }
    

    源码分析

    spring.factory支持自动配置

    因为前面的pom,引入了如下jar包:

    		<dependency>
    			<groupId>org.springframework.cloud</groupId>
    			<artifactId>spring-cloud-netflix-eureka-client</artifactId>
    		</dependency>
    
    

    该jar包的META-INFspring.factories中,有如下几行:

    org.springframework.boot.autoconfigure.EnableAutoConfiguration=
    org.springframework.cloud.netflix.eureka.config.EurekaClientConfigServerAutoConfiguration,
    org.springframework.cloud.netflix.eureka.config.EurekaDiscoveryClientConfigServiceAutoConfiguration,
    org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration,
    org.springframework.cloud.netflix.ribbon.eureka.RibbonEurekaAutoConfiguration,
    org.springframework.cloud.netflix.eureka.EurekaDiscoveryClientConfiguration
    

    我们看到,key是org.springframework.boot.autoconfigure.EnableAutoConfiguration,value是逗号分割的列表,这里面都是需要被自动装配的配置类,其中,我们看第三行的:

    org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration

    这个类,是自动装配的配置类,我们可以简单一览:

    @Configuration
    @EnableConfigurationProperties
    @ConditionalOnClass(EurekaClientConfig.class)
    @Import(DiscoveryClientOptionalArgsConfiguration.class)
    @ConditionalOnBean(EurekaDiscoveryClientConfiguration.Marker.class)
    @ConditionalOnProperty(value = "eureka.client.enabled", matchIfMissing = true)
    @ConditionalOnDiscoveryEnabled
    @AutoConfigureBefore({ NoopDiscoveryClientAutoConfiguration.class,
    		CommonsClientAutoConfiguration.class, ServiceRegistryAutoConfiguration.class })
    public class EurekaClientAutoConfiguration {
    

    里面一堆@ConditionalOn***,主要是看该配置类是否生效。

    我们不管,这里条件是满足的,所以,看具体java文件里有什么要装配的内容,里面内容较多,我们关注我们需要关注的:

    		@Bean(destroyMethod = "shutdown")
    		@ConditionalOnMissingBean(value = EurekaClient.class, search = SearchStrategy.CURRENT)
    		@Lazy
    		public EurekaClient eurekaClient(ApplicationInfoManager manager,
    				EurekaClientConfig config, EurekaInstanceConfig instance,
    				@Autowired(required = false) HealthCheckHandler healthCheckHandler) {
    			ApplicationInfoManager appManager;
    			if (AopUtils.isAopProxy(manager)) {
    				appManager = ProxyUtils.getTargetObject(manager);
    			}
    			else {
    				appManager = manager;
    			}
                // 1
    			CloudEurekaClient cloudEurekaClient = new CloudEurekaClient(appManager,
    					config, this.optionalArgs, this.context);
    			cloudEurekaClient.registerHealthCheck(healthCheckHandler);
    			return cloudEurekaClient;
    		}
    

    这里会自动装配一个EurekaClient类型的bean,(从返回值可以看出来),而具体的类型呢,从上面的1处,可以看出,具体类型是CloudEurekaClient。

    所以,我们开始看1处,这个CloudEurekaClient是怎么new出来的。

    CloudEurekaClient的创建

    先看看其继承结构:

    我们这个CloudEurekaClient,位于spring-cloud-netflix-eureka-client-2.1.5.RELEASE包。

    而其父类DiscoveryClient和接口EurekaClient,位于eureka-client-1.9.13

    大致能分析出,CloudEurekaClient的底层实现是eureka,其本身,是一个胶水,集成 spring 和 Netflix。

    CloudEurekaClient的构造函数

    	public CloudEurekaClient(ApplicationInfoManager applicationInfoManager,
    			EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs<?> args,
    			ApplicationEventPublisher publisher) {
            // 1
    		super(applicationInfoManager, config, args);
            // 2
    		this.applicationInfoManager = applicationInfoManager;
    		this.publisher = publisher;
    		this.eurekaTransportField = ReflectionUtils.findField(DiscoveryClient.class,
    				"eurekaTransport");
    		ReflectionUtils.makeAccessible(this.eurekaTransportField);
    	}
    

    我们看1处,调用了父类的构造函数;2处下面的几行,主要是对本类中的几个field进行赋值,这几个字段,我们不关心,所以,直接看父类的构造函数吧。

    DiscoveryClient的构造函数

        public DiscoveryClient(ApplicationInfoManager applicationInfoManager, final EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args) {
            this(applicationInfoManager, config, args, ResolverUtils::randomize);
        }
    
    
        public DiscoveryClient(ApplicationInfoManager applicationInfoManager, final EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args, EndpointRandomizer randomizer) {
            // 1
            this(applicationInfoManager, config, args, null, randomizer);
        }
    

    上面两个,都是重载。1处调用的,我们接下来会重点分析。

    步骤1:一堆field赋值

    DiscoveryClient(ApplicationInfoManager applicationInfoManager, EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args,
                        Provider<BackupRegistry> backupRegistryProvider, EndpointRandomizer endpointRandomizer) {
        	// 0	
            if (args != null) {
                this.healthCheckHandlerProvider = args.healthCheckHandlerProvider;
                this.healthCheckCallbackProvider = args.healthCheckCallbackProvider;
                this.eventListeners.addAll(args.getEventListeners());
                this.preRegistrationHandler = args.preRegistrationHandler;
            } else {
                this.healthCheckCallbackProvider = null;
                this.healthCheckHandlerProvider = null;
                this.preRegistrationHandler = null;
            }
            
            this.applicationInfoManager = applicationInfoManager;
            InstanceInfo myInfo = applicationInfoManager.getInfo();
    		// 1
            clientConfig = config;
            staticClientConfig = clientConfig;
            transportConfig = config.getTransportConfig();
            instanceInfo = myInfo;
            if (myInfo != null) {
                appPathIdentifier = instanceInfo.getAppName() + "/" + instanceInfo.getId();
            } else {
                logger.warn("Setting instanceInfo to a passed in null value");
            }
    		...
    
    

    这一堆都是根据入数,来对类中的field进行赋值。比如

    0处,主要是一些健康检查的东西;1处,config类型为 com.netflix.discovery.EurekaClientConfig,这里主要是eureka client的一些配置,比如我们在yml中配置了eureka.client.*之类的,就会到这里。

    步骤2:判断是否要获取eureka server中的服务提供者信息

    	// 1
    	if (config.shouldFetchRegistry()) {
                this.registryStalenessMonitor = new ThresholdLevelsMetric(this, METRIC_REGISTRY_PREFIX + "lastUpdateSec_", new long[]{15L, 30L, 60L, 120L, 240L, 480L});
            } else {
                this.registryStalenessMonitor = ThresholdLevelsMetric.NO_OP_METRIC;
            }
    

    1处,可以看出来,是根据config中的shouldFetchRegistry进行判断,是否要去获取eureka server。

    然后进行了一些监控指标的初始化。

    步骤3:判断是否要注册到eureka server

        	// 1    
    		if (config.shouldRegisterWithEureka()) {
                this.heartbeatStalenessMonitor = new ThresholdLevelsMetric(this, METRIC_REGISTRATION_PREFIX + "lastHeartbeatSec_", new long[]{15L, 30L, 60L, 120L, 240L, 480L});
            } else {
                this.heartbeatStalenessMonitor = ThresholdLevelsMetric.NO_OP_METRIC;
            }
    

    同上。

    步骤4:如果既不注册,也不获取,则处理基本结束

    		// 1
    		if (!config.shouldRegisterWithEureka() && !config.shouldFetchRegistry()) {
                logger.info("Client configured to neither register nor query for data.");
                scheduler = null;
                heartbeatExecutor = null;
                cacheRefreshExecutor = null;
                eurekaTransport = null;
                instanceRegionChecker = new InstanceRegionChecker(new PropertyBasedAzToRegionMapper(config), clientConfig.getRegion());
    
                DiscoveryManager.getInstance().setDiscoveryClient(this);
                DiscoveryManager.getInstance().setEurekaClientConfig(config);
    			// 2
                return;  // no need to setup up an network tasks and we are done
            }
    
    • 1处,既不注册,也不从eureka server获取
    • 2处,直接结束

    步骤5:定义三个线程池

                //1 default size of 2 - 1 each for heartbeat and cacheRefresh
                scheduler = Executors.newScheduledThreadPool(2,
                        new ThreadFactoryBuilder()
                                .setNameFormat("DiscoveryClient-%d")
                                .setDaemon(true)
                                .build());
    			// 2
                heartbeatExecutor = new ThreadPoolExecutor(
                        1, clientConfig.getHeartbeatExecutorThreadPoolSize(), 0, TimeUnit.SECONDS,
                        new SynchronousQueue<Runnable>(),
                        new ThreadFactoryBuilder()
                                .setNameFormat("DiscoveryClient-HeartbeatExecutor-%d")
                                .setDaemon(true)
                                .build()
                );  // use direct handoff
    			// 3 
    			cacheRefreshExecutor = new ThreadPoolExecutor(
                        1, clientConfig.getCacheRefreshExecutorThreadPoolSize(), 0, TimeUnit.SECONDS,
                        new SynchronousQueue<Runnable>(),
                        new ThreadFactoryBuilder()
                                .setNameFormat("DiscoveryClient-CacheRefreshExecutor-%d")
                                .setDaemon(true)
                                .build()
                );  // use direct handoff
    
    • 1处,定义一个用于服务提供者信息的缓存刷新的定时线程池
    • 2处,定义一个心跳线程池
    • 3处,这个看起来也是用于缓存刷新的

    步骤6:创建eurekaTransport对象

    com.netflix.discovery.DiscoveryClient#scheduleServerEndpointTask
    // 1
    eurekaTransport = new EurekaTransport();
    // 2
    scheduleServerEndpointTask(eurekaTransport, args);
    
    • 1处,eurekaTransport是一个field,该类主要封装了几个后续通信要使用的底层client。

      
          private static final class EurekaTransport {
              private ClosableResolver bootstrapResolver;
              private TransportClientFactory transportClientFactory;
      		// 1.1
              private EurekaHttpClient registrationClient;
              private EurekaHttpClientFactory registrationClientFactory;
      		// 1.2
              private EurekaHttpClient queryClient;
              private EurekaHttpClientFactory queryClientFactory;
      

      1.1处,这个应该是注册用的,也是我们需要的;

      1.2处,应该是查询信息用的。

    • 调用了当前类的方法scheduleServerEndpointTask,且把eurekaTransport传入了

    步骤7:schedule周期任务

    创建抽象工厂

    因我们只是new了eurekaTransport,没有对其field进行任何赋值,所以,这个scheduleServerEndpointTask总,有个地方对其field进行赋值。

    com.netflix.discovery.DiscoveryClient#scheduleServerEndpointTask
    // 1
    TransportClientFactories transportClientFactories =new Jersey1TransportClientFactories();
    
    // 2
    eurekaTransport.transportClientFactory = transportClientFactories.newTransportClientFactory(clientConfig, additionalFilters, applicationInfoManager.getInfo(), sslContext, hostnameVerifier)
    
    
    • 1处,就是new了一个抽象工厂,抽象工厂,我个人理解是工厂的工厂,其产出的东西,不是直接的最终对象,而是另一种工厂。

      TransportClientFactories 是一个接口,主要包含了如下方法:

      	public TransportClientFactory newTransportClientFactory(
              	final EurekaClientConfig clientConfig,
              	final Collection<F> additionalFilters,
                  final InstanceInfo myInstanceInfo,
                  final Optional<SSLContext> sslContext,
                  final Optional<HostnameVerifier> hostnameVerifier);
      

      主要5个参数,排除掉最后的倒数2个,可选参数,剩3个。分别是:eurekaClient的配置bean,额外的filter集合,当前实例信息。

    具体工厂的职责

    • 2处,就是利用1处创建的抽象工厂,来生成我们需要的工厂。

      这里,我们可以先看看,最终我们需要的工厂,是什么样的。

      /**
       * A low level client factory interface. Not advised to be used by top level consumers.
       *
       * @author David Liu
       */
      public interface TransportClientFactory {
      
          EurekaHttpClient newClient(EurekaEndpoint serviceUrl);
      
          void shutdown();
      
      }
      

      newClient这个方法,听名字,就是一个创建客户端的,创建客户端,需要什么参数呢?总得知道要连接到哪个eureka server服务器吧,服务器地址是啥吧?没错,参数EurekaEndpoint serviceUrl可以给我们提供需要的这些:

      package com.netflix.discovery.shared.resolver;
      
      public interface EurekaEndpoint extends Comparable<Object> {
      	// 1
          String getServiceUrl();
      	// 2
          String getNetworkAddress();
      	// 3
          int getPort();
      
          boolean isSecure();
      
          String getRelativeUri();
      
      }
      
      
      • 1处,获取url
      • 2处,获取网络地址
      • 3处,获取端口

      基本对于我们一个客户端来说,需要的参数就这些。

      说完了newClient的参数,再来看看响应:

      
      /**
       * Low level Eureka HTTP client API.
       *
       * @author Tomasz Bak
       */
      public interface EurekaHttpClient {
      
          EurekaHttpResponse<Void> register(InstanceInfo info);
      
          EurekaHttpResponse<Void> cancel(String appName, String id);
      
          EurekaHttpResponse<InstanceInfo> sendHeartBeat(String appName, String id, InstanceInfo info, InstanceStatus overriddenStatus);
      
          EurekaHttpResponse<Void> statusUpdate(String appName, String id, InstanceStatus newStatus, InstanceInfo info);
      
          EurekaHttpResponse<Void> deleteStatusOverride(String appName, String id, InstanceInfo info);
      
          EurekaHttpResponse<Applications> getApplications(String... regions);
      
          EurekaHttpResponse<Applications> getDelta(String... regions);
      
          EurekaHttpResponse<Applications> getVip(String vipAddress, String... regions);
      
          EurekaHttpResponse<InstanceInfo> getInstance(String appName, String id);
      
          EurekaHttpResponse<InstanceInfo> getInstance(String id);
      
          void shutdown();
      }
      

      看到了吗,各种注册、取消、发送心跳、状态更新啥的,这几本涵盖了eureka client的所有操作了,没错,我们就是需要这么个东西。

    创建具体工厂

    看完了我们需要的工厂的功能,我们马上来看看这么厉害的工厂怎么创建出来?

    com.netflix.discovery.shared.transport.jersey.Jersey1TransportClientFactories#newTransportClientFactory(...)
            
    	@Override
        public TransportClientFactory newTransportClientFactory(
            	EurekaClientConfig clientConfig,
                Collection<ClientFilter> additionalFilters,
            	InstanceInfo myInstanceInfo,
            	Optional<SSLContext> sslContext,
            	Optional<HostnameVerifier> hostnameVerifier) {
        	// 2.1
            final TransportClientFactory jerseyFactory = JerseyEurekaHttpClientFactory.create(
                    clientConfig,
                    additionalFilters,
                    myInstanceInfo,
                    new EurekaClientIdentity(myInstanceInfo.getIPAddr()),
                    sslContext,
                    hostnameVerifier
            );
            // 2.2
            final TransportClientFactory metricsFactory = MetricsCollectingEurekaHttpClient.createFactory(jerseyFactory);
    		// 2.3
            return new TransportClientFactory() {
                @Override
                public EurekaHttpClient newClient(EurekaEndpoint serviceUrl) {
                    return metricsFactory.newClient(serviceUrl);
                }
    
                @Override
                public void shutdown() {
                    metricsFactory.shutdown();
                    jerseyFactory.shutdown();
                }
            };
        }
    
    • 2.1处,调用JerseyEurekaHttpClientFactory的create 静态方法,生成了一个工厂
    • 2.2处,对生成的工厂,进行了包装,看名称,应该是包装了统计相关信息。
    • 2.3处,对2.2处生成的工厂,用匿名内部类进行了包装,调用匿名内部类的newClient时,直接代理给了metricsFactory;而shutdown方法,则主要是关闭 metricsFactory 和 jerseyFactory 工厂。

    所以,我们现在要看看,2.1处,是怎么创建工厂的。

    com.netflix.discovery.shared.transport.jersey.JerseyEurekaHttpClientFactory#create
        
        public static JerseyEurekaHttpClientFactory create(
        	EurekaClientConfig clientConfig,
            Collection<ClientFilter> additionalFilters,
        	InstanceInfo myInstanceInfo,                                                       		 AbstractEurekaIdentity clientIdentity) {
            // 1
        	boolean useExperimental = "true".equals(clientConfig.getExperimental("JerseyEurekaHttpClientFactory.useNewBuilder"));
    		// 2
            JerseyEurekaHttpClientFactoryBuilder clientBuilder = (useExperimental ? experimentalBuilder() : newBuilder())
                    .withAdditionalFilters(additionalFilters)
                    .withMyInstanceInfo(myInstanceInfo)
                    .withUserAgent("Java-EurekaClient")
                    .withClientConfig(clientConfig)
                    .withClientIdentity(clientIdentity);
        	// 3
            clientBuilder.withClientName("DiscoveryClient-HTTPClient");
    		// 4
            return clientBuilder.build();
        }
    
    • 1处,砍断是否要使用实验性的builder
    • 2处,创建对应的builder,并把我们的参数,通过with*方法,设置进去
    • 3处,设置客户端名称
    • 4处,生成客户端工厂
    com.netflix.discovery.shared.transport.jersey.JerseyEurekaHttpClientFactory.JerseyEurekaHttpClientFactoryBuilder#build
    
        
            @Override
            public JerseyEurekaHttpClientFactory build() {
        		// 1
                Map<String, String> additionalHeaders = new HashMap<>();
        		// 2
                if (allowRedirect) {
                    additionalHeaders.put(HTTP_X_DISCOVERY_ALLOW_REDIRECT, "true");
                }
                if (EurekaAccept.compact == eurekaAccept) {
                    additionalHeaders.put(EurekaAccept.HTTP_X_EUREKA_ACCEPT, eurekaAccept.name());
                }
    			
                // 3
                return buildLegacy(additionalHeaders, systemSSL);
            }
    

    这里就是弄了个hashmap,设置了几个header进去,然后3处,调用buildLegacy。

    com.netflix.discovery.shared.transport.jersey.JerseyEurekaHttpClientFactory.JerseyEurekaHttpClientFactoryBuilder#buildLegacy
        
            private JerseyEurekaHttpClientFactory buildLegacy(Map<String, String> additionalHeaders, boolean systemSSL) {
        		// 1
                EurekaJerseyClientBuilder clientBuilder = new EurekaJerseyClientBuilder()
                        .withClientName(clientName)
                        .withUserAgent("Java-EurekaClient")
                        .withConnectionTimeout(connectionTimeout)
                        .withReadTimeout(readTimeout)
                        .withMaxConnectionsPerHost(maxConnectionsPerHost)
                        .withMaxTotalConnections(maxTotalConnections)
                        .withConnectionIdleTimeout((int) connectionIdleTimeout)
                        .withEncoderWrapper(encoderWrapper)
                        .withDecoderWrapper(decoderWrapper);
    			...
                
    			// 2
                EurekaJerseyClient jerseyClient = clientBuilder.build();
        		// 3
                ApacheHttpClient4 discoveryApacheClient = jerseyClient.getClient();
                addFilters(discoveryApacheClient);
    			// 4
                return new JerseyEurekaHttpClientFactory(jerseyClient, additionalHeaders);
            }
    
    • 1处,通过我们传入的一些参数,以及该类自身的一些field,比如connectionTimeout、readTimeout、maxTotalConnections、maxConnectionsPerHost这些,构造一个builder。

      这些参数,已经看出来,是网络通信所需要的东西了

    • 2处,通过1处的builder,调用build,拿到了EurekaJerseyClient类型的对象,可以说,这里其实是已经把客户端构造好了。也就是说,在构造这个工厂的过程中,其实已经在生成对应的产品了

    • 3处,对2处拿到的客户端,做一些处理

    • 4处,将2处拿到的客户端,封装到了工厂的一些field中,后续调用工厂生产产品的时候,直接从field中取就行了。

          public JerseyEurekaHttpClientFactory(EurekaJerseyClient jerseyClient, Map<String, String> additionalHeaders) {
              this(jerseyClient, null, -1, additionalHeaders);
          }
      	private JerseyEurekaHttpClientFactory(EurekaJerseyClient jerseyClient,
                                                ApacheHttpClient4 apacheClient,
                                                long connectionIdleTimeout,
                                                Map<String, String> additionalHeaders) {
              this.jerseyClient = jerseyClient;
              this.apacheClient = jerseyClient != null ? jerseyClient.getClient() : apacheClient;
              this.additionalHeaders = additionalHeaders;
          }
      

    所以,我们的重点,要放在2处的build身上。

    	com.netflix.discovery.shared.transport.jersey.EurekaJerseyClientImpl.EurekaJerseyClientBuilder#build
    	public EurekaJerseyClient build() {
                MyDefaultApacheHttpClient4Config config = new MyDefaultApacheHttpClient4Config();
                try {
                    // 1
                    return new EurekaJerseyClientImpl(connectionTimeout, readTimeout, connectionIdleTimeout, config);
                } catch (Throwable e) {
                    throw new RuntimeException("Cannot create Jersey client ", e);
                }
            }
    

    接下来看1处:

    public EurekaJerseyClientImpl(int connectionTimeout, int readTimeout, final int connectionIdleTimeout,ClientConfig clientConfig) {
            try {
                jerseyClientConfig = clientConfig;
                // 1
                apacheHttpClient = ApacheHttpClient4.create(jerseyClientConfig);
                // 2
                HttpParams params = apacheHttpClient.getClientHandler().getHttpClient().getParams();
    
                HttpConnectionParams.setConnectionTimeout(params, connectionTimeout);
                HttpConnectionParams.setSoTimeout(params, readTimeout);
    			
            } catch (Throwable e) {
                throw new RuntimeException("Cannot create Jersey client", e);
            }
        }
    
    • 1处,创建com.sun.jersey.client.apache4.ApacheHttpClient4类型的对象

      该类型,就位于:

          <dependency>
            <groupId>com.sun.jersey.contribs</groupId>
            <artifactId>jersey-apache-client4</artifactId>
            <version>1.19.1</version>
            <scope>runtime</scope>
          </dependency>
      
      
          public static ApacheHttpClient4 create(final ClientConfig cc) {
              return new ApacheHttpClient4(createDefaultClientHandler(cc), cc);
          }
      

      这里的createDefaultClientHandler(cc),里面会去创建HttpClient。

      private static ApacheHttpClient4Handler createDefaultClientHandler(final ClientConfig cc) {
      		...
      
      		// 1
              final DefaultHttpClient client = new DefaultHttpClient(
                      (ClientConnectionManager)connectionManager,
                      (HttpParams)httpParams
              );
      
              ...
      		
              return new ApacheHttpClient4Handler(client, cookieStore, preemptiveBasicAuth);
          }
      

      这里面细节省略了部分,主要就是1处,创建了HttpClient,这个就是平时我们用来发http请求的那个。

    • 2处,设置一些参数,这里的HttpParams,从哪儿取出来的?apacheHttpClient.getClientHandler().getHttpClient()。这里取到的,已经是HttpClient了。

      到此为止,我们可以看看httpParams中有哪些header:

    在具体工厂基础上,对注册用的工厂进行封装

            com.netflix.discovery.DiscoveryClient#scheduleServerEndpointTask
            // 1    
    		if (clientConfig.shouldRegisterWithEureka()) {
                EurekaHttpClientFactory newRegistrationClientFactory = null;
                EurekaHttpClient newRegistrationClient = null;
                // 2
                newRegistrationClientFactory = EurekaHttpClients.registrationClientFactory(
                    eurekaTransport.bootstrapResolver,
                    eurekaTransport.transportClientFactory,
                    transportConfig
                );
                // 3
                newRegistrationClient = newRegistrationClientFactory.newClient();
                // 4
                eurekaTransport.registrationClientFactory = newRegistrationClientFactory;
                eurekaTransport.registrationClient = newRegistrationClient;
            }
    

    我们前面的n步,已经把通信用的客户端,及对应的工厂,都已经创建出来了,为啥这里又要创建什么工厂。

    简单来说,前面的工厂,造出来的客户端,通信是没问题了;但是,你通信失败了,要重试吗,重试的话,换哪一台呢?你每次通信是成功,还是失败,还是超时,需要统计吗?一个生产级的框架,是要有这些功能的。

    所以,这里主要是进行一些上层的封装。

    ok,继续分析上面的代码。

    • 1处,判断是否要注册到eureka
    • 2处,生成一个工厂,该工厂负责生产:注册用的客户端
    • 3处,使用2处拿到的工厂,创建注册用的客户端
    • 4处,把3处拿到的客户端,存储到eurekaTransport的field中。

    继续深入2处。

        com.netflix.discovery.shared.transport.EurekaHttpClients#canonicalClientFactory
    	static EurekaHttpClientFactory canonicalClientFactory(
            final String name,
            final EurekaTransportConfig transportConfig,
            final ClusterResolver<EurekaEndpoint> clusterResolver,
            final TransportClientFactory transportClientFactory) {
    		// 1
            return new EurekaHttpClientFactory() {
                // 2
                @Override
                public EurekaHttpClient newClient() {
                    // 3
                    return new SessionedEurekaHttpClient(
                            name,
                            RetryableEurekaHttpClient.createFactory(...),
                            transportConfig.getSessionedClientReconnectIntervalSeconds() * 1000
                    );
                }
    
                @Override
                public void shutdown() {
                    wrapClosable(clusterResolver).shutdown();
                }
            };
        }
    
    • 1处,返回了一个工厂对象
    • 2处,工厂里重写了newClient
    • 3处,返回了一个包装过的EurekaClient。

    可以看下这里返回的SessionedEurekaHttpClient类。

    这里就是装饰器模式,对enreka进行了层层封装,和 java 的 io 流那样理解就对了。

    在具体工厂基础上,对查询用的工厂进行封装

    		// 1
    		if (clientConfig.shouldFetchRegistry()) {
                EurekaHttpClientFactory newQueryClientFactory = null;
                EurekaHttpClient newQueryClient = null;
                // 2
                newQueryClientFactory = EurekaHttpClients.queryClientFactory(
                    eurekaTransport.bootstrapResolver,
                    eurekaTransport.transportClientFactory,
                    clientConfig,
                    transportConfig,
                    applicationInfoManager.getInfo(),
                    applicationsSource,
                    endpointRandomizer
                );
                // 3
                newQueryClient = newQueryClientFactory.newClient();
                eurekaTransport.queryClientFactory = newQueryClientFactory;
                eurekaTransport.queryClient = newQueryClient;
            }
    

    这里的代码,和上面基本相似。只不过,这里是给查询用的,所谓查询,就是去eureka server获取信息,比如服务提供者列表啥的。

    • 1处,判断是否要去eureka server获取
    • 2处,创建查询用的工厂
    • 3处,利用2处拿到的工厂,创建查询客户端

    步骤8:去eureka server获取服务提供者信息

    我们终于把步骤7讲完了,实在有点长。

    com.netflix.discovery.DiscoveryClient#DiscoveryClient(...)
        
    // 1    
    if (clientConfig.shouldFetchRegistry() && !fetchRegistry(false)) {
        // 2
        fetchRegistryFromBackup();
    }
    

    这里1处,就是判断要不要去获取,如果要的话,就调用fetchRegistry(false)

    2处,如果1处没取到,则要从backup地方去取。这块可以自己定制backup策略。

    注册到eureka server

            if (clientConfig.shouldRegisterWithEureka() && clientConfig.shouldEnforceRegistrationAtInit()) {
                // 1
                if (!register() ) {
                    throw new IllegalStateException("Registration error at startup. Invalid server response.");
                }
            }
    

    这里会判断是否要注册,是否要在初始化的时候注册,如果要的话,进入1处,进行注册。

    初始化周期执行的任务

            // finally, init the schedule tasks (e.g. cluster resolvers, heartbeat, instanceInfo replicator, fetch
            initScheduledTasks();
    

    看这里注释,初始化的任务包括:集群解析、心跳、实例信息注册、周期从eureka server获取信息等。

    周期任务:获取服务提供者信息

    if (clientConfig.shouldFetchRegistry()) {
                // registry cache refresh timer
                int registryFetchIntervalSeconds = clientConfig.getRegistryFetchIntervalSeconds();
                int expBackOffBound = clientConfig.getCacheRefreshExecutorExponentialBackOffBound();
                scheduler.schedule(
                        new TimedSupervisorTask(
                                "cacheRefresh",
                                scheduler,
                                cacheRefreshExecutor,
                                registryFetchIntervalSeconds,
                                TimeUnit.SECONDS,
                                expBackOffBound,
                                new CacheRefreshThread()
                        ),
                        registryFetchIntervalSeconds, TimeUnit.SECONDS);
            }
    

    默认30s一次。

    周期任务:定时发心跳,向eureka server进行renew

                int renewalIntervalInSecs = instanceInfo.getLeaseInfo().getRenewalIntervalInSecs();
                int expBackOffBound = clientConfig.getHeartbeatExecutorExponentialBackOffBound();
                logger.info("Starting heartbeat executor: " + "renew interval is: {}", renewalIntervalInSecs);
    
                // Heartbeat timer
                scheduler.schedule(
                        new TimedSupervisorTask(
                                "heartbeat",
                                scheduler,
                                heartbeatExecutor,
                                renewalIntervalInSecs,
                                TimeUnit.SECONDS,
                                expBackOffBound,
                                new HeartbeatThread()
                        ),
                        renewalIntervalInSecs, TimeUnit.SECONDS);
    

    这个也是30s。

    心跳包,基本就是个put请求,里面携带了2个参数。

    @Override
        public EurekaHttpResponse<InstanceInfo> sendHeartBeat(String appName, String id, InstanceInfo info, InstanceStatus overriddenStatus) {
            String urlPath = "apps/" + appName + '/' + id;
            ClientResponse response = null;
            try {
                WebResource webResource = jerseyClient.resource(serviceUrl)
                        .path(urlPath)
                        .queryParam("status", info.getStatus().toString())
                        .queryParam("lastDirtyTimestamp", info.getLastDirtyTimestamp().toString());
    

    周期任务:InstanceInfoReplicator

    这个任务,默认也是30s执行一次。

                instanceInfoReplicator = new InstanceInfoReplicator(
                        this,
                        instanceInfo,
                        clientConfig.getInstanceInfoReplicationIntervalSeconds(),
                        2); // burstSize
    
    

    这个任务,其实现了runnable,注释如下:

    
    /**
     * A task for updating and replicating the local instanceinfo to the remote server. Properties of this task are:
     * - 1 configured with a single update thread to guarantee sequential update to the remote server
     * - 2 update tasks can be scheduled on-demand via onDemandUpdate()
     * - 3 task processing is rate limited by burstSize
     * - 4 a new update task is always scheduled automatically after an earlier update task.  However if an on-demand task is started, the scheduled automatic update task is discarded (and a new one will be scheduled after the new
     *   on-demand update).
     *
     *   @author dliu
     */
    class InstanceInfoReplicator implements Runnable 
    
    • 1处,配置了一个单线程,保证向远程eureka server,顺序更新
    • 2处,通过本类的onDemandUpdate,可以强行插入一个任务,而无需通过定时执行
    • 3处,限流相关
    • 4处,执行完一个周期任务后,马上会给自己安排下一个周期任务

    其run方法:

        public void run() {
            try {
                // 1
                discoveryClient.refreshInstanceInfo();
    
                Long dirtyTimestamp = instanceInfo.isDirtyWithTime();
                if (dirtyTimestamp != null) {
                    // 2
                    discoveryClient.register();
                    instanceInfo.unsetIsDirty(dirtyTimestamp);
                }
            }finally {
                // 3
                Future next = scheduler.schedule(this, replicationIntervalSeconds, TimeUnit.SECONDS);
                scheduledPeriodicRef.set(next);
            }
        }
    
    • 1处,刷新实例信息
    • 2处,如果有需要的话,向eureka server进行注册
    • 3处,调度下一次任务

    初始化结束

    基本,这个CloudEurekaClient构造就结束了,后续就依靠其开启的一堆定时任务去进行工作。

    总结

    eureka client的初始化就讲了这么多,注册还没讲,留带下一讲吧。

  • 相关阅读:
    Layabox记录坑
    爬虫 Beautifulsoup 常用笔记
    opencv +python 提取roi目标区域全部像素的值 得出上下限 均匀值
    opencv + python 读取像素点 BGRtoRGB 以及注意事项
    pyinstaller打包python+opencv 无法在别人电脑上正常运行 问题所在:opencv_ffmpeg341_64.dll
    python 绝对路径相对路径
    面向对象编程-OOP-一张图看懂类和实例的基本用法
    python3 _笨方法学Python_日记_DAY7
    儋州“炰米”:美味的特制粮食
    PHP学习步骤
  • 原文地址:https://www.cnblogs.com/grey-wolf/p/12995569.html
Copyright © 2020-2023  润新知