Cao Gong said Mini Dubbo -- analyze the source code of eureka client and try to register our service provider with eureka server

preface

eureka is an important component of the spring cloud Netflix technology system, which mainly completes the function of service registration and discovery. Now there is a problem, the rpc service we wrote by ourselves, if we want to ensure enough openness and function perfection, we must support various registration centers. At present, we only support the redis registry, that is, the service provider, when starting, writes its ip + port information to redis. Then, do we register to eureka?

Is this idea feasible? Feasible. The communication between eureka client and eureka server is nothing more than network communication. Since it is network communication, there is a network protocol. Our application can be accessed as long as it follows the protocol of eureka server.

In addition, eureka server is not implemented by spring mvc, but by jersey framework. What does this framework mean? It can be understood as the implementation of Restful. I found a passage on the Internet( https://www.jianshu.com/p/88f97b90963c):

Spring MVC does not support JSR 311 / JSR 339 when developing REST applications. If you want to follow the standards, the most commonly used framework to implement these two standards is Jersey and CxF. However, because Jersey is the earliest implementation and the main object of JSR 311 reference, it can be said that Jersey is the de facto standard (similar to Hibernate is the de facto standard of JPA) and one of the most widely used REST development frameworks.

Because the eureka server uses jersey, the eureka client finally uses the matching jersey client to communicate with the server.

So, eureka client actually relies on a bunch of jersey packages:

Note that the above packages such as Jersey client and Jersey core have the following group id:

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

However, I don't know why, in eureka client, I didn't use Jersey client completely, but used

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

This package introduces:

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

This package, you can simply understand that Jersey client becomes an interface, and Jersey Apache client 4 is an implementation of it. In the implementation, httpClient is used to implement it.

httpClient, how many java students don't know? This is possible because it doesn't matter whether you end up communicating with http, whether your server framework is jersey, or spring mvc, or even the previous struts.

So when you see jersey in the source code below, you can have such a picture in your mind. The interfaces from the upper layer to the lower layer are as follows:

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

Before that, we will first analyze the source code of eureka client registered to eureka server.

Source environment

The minidubbo code and related blogs are:
Cao Gong said Mini Dubbo (1) -- in order to practice dynamic agent, I wrote a simple rpc framework
https://gitee.com/ckl111/mini-dubbo

The code is very simple, but let's give a code link:

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

Mainly in pom.xml In, introduce:

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

Then start the class:

@SpringBootApplication
public class DemoApplication {

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

}

Source code analysis

spring.factory Support for automatic configuration

Because of the previous pom, the following jar packages are introduced:

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

Meta-inf of the jar package\ spring.factories In, there are the following lines:

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

We see, key is org.springframework.boot.autoconfigure.EnableAutoConfiguration , value is a comma separated list of configuration classes that need to be automatically assembled. In the third line, let's look at:

org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration.

This class is an auto assembly configuration class. We can simply list:

@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 {

There are a lot of @ ConditionalOn * * in it, mainly to see whether the configuration class is effective.

We don't care. The conditions here are satisfied. Therefore, we need to pay attention to the specific content of the java file to be assembled. There are many contents in it. We need to pay attention to what we need to pay attention to:

		@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;
		}

A bean of EurekaClient type will be automatically assembled here (as can be seen from the return value), and the specific type, as can be seen from the above 1 place, is CloudEurekaClient.

So, let's start to see how the cloudeureka client comes out new.

CloudEurekaClient creation

Let's first look at its inheritance structure:

Our cloud Eureka client is located in the spring cloud Netflix Eureka client-2.1.5.release package.

Its parent class, DiscoveryClient, and interface Eureka client, are located in eureka-client-1.9.13 package

It can be roughly analyzed that the underlying implementation of cloudeureka client is eureka, which itself is a glue integrating spring and Netflix.

Constructor for 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);
	}

Let's look at 1 place, which calls the constructor of the parent class; the following lines in 2 places, which mainly assign values to several fields in this class. We don't care about these fields, so let's look directly at the constructor of the parent class.

Constructor for 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);
    }

Both of them are overloaded. 1 call, we will focus on the analysis next.

Step 1: a bunch of field assignments

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");
        }
		...

This heap assigns values to field s in a class based on the input. such as

0, mainly for health check; 1, config type is com.netflix.discovery.EurekaClientConfig. Here are some configurations of eureka client. For example, we configured them in yml eureka.client . * and so on, it'll be here.

Step 2: determine whether to obtain the service provider information in 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. It can be seen that whether to get the eureka server is based on the shouldFetchRegistry in config.

Then some monitoring indicators are initialized.

Step 3: determine whether to register to 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;
        }

ditto.

Step 4: if neither registration nor acquisition occurs, the processing is basically finished

		// 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 place, neither registered nor obtained from eureka server
  • 2 places, direct end

Step 5: define three thread pools

            //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, define a timed thread pool for cache refresh of service provider information
  • 2 places, define a heartbeat thread pool
  • 3 places. This seems to be used for cache refresh

Step 6: create the eurekamtransport object

com.netflix.discovery.DiscoveryClient#scheduleServerEndpointTask
// 1
eurekaTransport = new EurekaTransport();
// 2
scheduleServerEndpointTask(eurekaTransport, args);
  • 1. Eureka transport is a field, which encapsulates several underlying client s for subsequent communication.

    
        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, which should be used for registration and is also what we need;

    1.2, it should be used to query information.

  • The current class's method scheduleServerEndpointTask is called, and eurekatraport is passed in the

Step 7: schedule cycle task

Create an abstract factory

Because we only have new Eureka transport and have not assigned any value to its field, there is always a place to assign value to its field in this scheduleserverendpoint task.

com.netflix.discovery.DiscoveryClient#scheduleServerEndpointTask
// 1
TransportClientFactories transportClientFactories =new Jersey1TransportClientFactories();

// 2
eurekaTransport.transportClientFactory = transportClientFactories.newTransportClientFactory(clientConfig, additionalFilters, applicationInfoManager.getInfo(), sslContext, hostnameVerifier)

  • One is the new abstract factory. I personally understand that it is the factory of the factory. What it produces is not a direct final object, but another factory.

    Transportclientfactors is an interface, mainly including the following methods:

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

    There are five main parameters, excluding the last two. There are three optional parameters. They are: configuration bean of eurekaClient, additional filter collection, current instance information.

Specific plant responsibilities

  • Two is to use one created abstract factory to generate the factory we need.

    Here, we can first see what kind of factory we need in the end.

    /**
     * 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();
    
    }
    

    The newClient method, listen to the name, is to create a client. What parameters do you need to create a client? You have to know which eureka server to connect to, and what is the server address? Yes, the EurekaEndpoint serviceUrl parameter can provide us with the following:

    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 place, get url
    • 2, get network address
    • 3 places, get port

    Basically for a client, we need these parameters.

    After finishing the parameters of newClient, let's look at the response:

    
    /**
     * 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();
    }
    

    Have you seen all kinds of registration, cancellation, sending heartbeat, status update and so on? These books cover all the operations of eureka client. Yes, we need such a thing.

Create specific plant

After reading the functions of the factory we need, let's see how to create such a powerful factory right away?

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();
            }
        };
    }
  • At 2.1, the create static method of Jersey eurekahttpclientfactory is called to generate a factory
  • 2.2. For the generated factory, it is packed. Look at the name. It should be packed with relevant statistical information.
  • In Section 2.3, the factory generated in Section 2.2 is wrapped with anonymous inner class. When calling the new client of anonymous inner class, the proxy directly gives metricsFactory; while the shutdown method mainly closes the metricsFactory and Jersey factory factories.

So now we're going to see how 2.1 creates the factory.

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 place, cutting whether to use experimental builder
  • 2 places, create the corresponding builder, and set our parameters through the with * method
  • 3 places, set client name
  • 4, generate client factory
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);
        }

Here is a hashmap, set up several header s, and then call buildlegap in three places.

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. Construct a builder through some parameters we passed in and some field s of the class itself, such as connectionTimeout, readTimeout, maxTotalConnections, maxConnectionsPerHost.

    These parameters, it can be seen, are what network communication needs

  • Two places, through the builder of one place, call build, and get the EurekaJerseyClient type object. It can be said that the client has been constructed here. In other words, in the process of building this factory, corresponding products are already being generated

  • 3, do some processing for the clients obtained from 2

  • 4. Encapsulate the client from 2 to some fields in the factory. When calling the factory to produce products in the future, just take it directly from the 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;
        }
    

So, our focus should be on build in 2 places.

	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);
            }
        }

Next look at one place:

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 place, create com.sun.jersey.client.apache4.ApacheHttpClient4 type object

    This type is located in:

        <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);
        }
    

    Here, create default client handler (CC), which will create HttpClient.

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

    In this part, the details are omitted, which is mainly one place. HttpClient is created. This is the one we usually use to send http requests.

  • 2 places, set some parameters, where can I get HttpParams here? apacheHttpClient.getClientHandler().getHttpClient(). This is the HttpClient.

    So far, let's look at the header s in httpParams:

On the basis of specific factories, the registered factories are sealed

        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;
        }

In step n, we have created the communication client and the corresponding factory. Why do we need to create any factory here.

In short, the communication of the client made by the factory in front is no problem; however, your communication fails. Do you want to try again? If you try again, which one should you change? Do you need statistics for success, failure or timeout of each communication? A production level framework should have these functions.

So, here is mainly to carry out some upper layer encapsulation.

ok, continue to analyze the above code.

  • 1 place, judge whether to register to eureka
  • 2, generate a factory, which is responsible for production: registered client
  • 3, use 2 factories to create the client for registration
  • 4. Store the 3 clients in the field of Eureka transport.

Continue to drill down to 2 places.

    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, returned a factory object
  • 2, the factory rewrites the newClient
  • 3 places, a wrapped EurekaClient is returned.

You can see the SessionedEurekaHttpClient class returned here.

This is the decorator pattern. It encapsulates the enreka layer by layer. It's right to understand it as the java io flow.

On the basis of specific factory, encapsulate the factory for query

		// 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;
        }

The code here is basically similar to the above. However, this is for query. The so-called query is to go to eureka server to get information, such as service provider list.

  • 1 place, judge whether to get it from eureka server
  • 2, create the factory for query
  • 3 factories, 2 factories obtained, create query client

Step 8: go to eureka server to obtain service provider information

We've finally finished step 7. It's a bit long.

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

Here 1 place is to judge whether to get it. If so, call fetchRegistry(false).

2 places. If 1 place is not picked up, it should be picked up from the backup place. You can customize the backup strategy.

Register to eureka server

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

Here we will judge whether to register or not, and whether to register during initialization. If so, enter 1 place to register.

Tasks performed by initialization cycle

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

Look at the notes here. The initialization tasks include: cluster resolution, heartbeat, instance information registration, periodic access to information from eureka server, etc.

Cycle task: get service provider information

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);
        }

The default is 30s once.

Cycle task: send heartbeat regularly and renew to eureka server

            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);

This is also 30s.

Heartbeat packet is basically a put request, which carries two parameters.

@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());

Cycle task: InstanceInfoReplicator

This task is also executed once in 30s by default.

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

This task implements runnable. The comments are as follows:


/**
 * 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 place, a single thread is configured to ensure that the remote eureka server is updated in sequence
  • 2 places, through this class of onDemandUpdate, you can forcibly insert a task without executing it regularly
  • 3 places, current limiting related
  • 4 places, after performing one cycle task, they will immediately arrange the next cycle task for themselves

Its run method:

    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 place, refresh instance information
  • If necessary, register with eureka server
  • 3 places, schedule the next task

End of initialization

Basically, the CloudEurekaClient construction is over, and the subsequent work depends on a batch of scheduled tasks it starts.

summary

So much has been said about the initialization of eureka client. Registration hasn't been mentioned yet. Let's take the next one.

Keywords: Spring network Java Apache

Added by Zoud on Mon, 01 Jun 2020 12:55:50 +0300