前言

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的源码。

源码环境

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

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-INF\spring.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的初始化就讲了这么多,注册还没讲,留带下一讲吧。

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

  1. Spring Cloud Netflix Eureka client源码分析

    1.client端 EurekaClient提供三个功能: EurekaClient API contracts are:* - provide the ability to get Instance ...

  2. Spring Cloud 学习 之 Spring Cloud Eureka(源码分析)

    Spring Cloud 学习 之 Spring Cloud Eureka(源码分析) Spring Boot版本:2.1.4.RELEASE Spring Cloud版本:Greenwich.SR1 ...

  3. 【SpringCloud技术专题】「Eureka源码分析」从源码层面让你认识Eureka工作流程和运作机制(上)

    前言介绍 了解到了SpringCloud,大家都应该知道注册中心,而对于我们从过去到现在,SpringCloud中用的最多的注册中心就是Eureka了,所以深入Eureka的原理和源码,接下来我们要进 ...

  4. 深入理解Eureka之源码解析

    转载请标明出处: http://blog.csdn.net/forezp/article/details/73017664 本文出自方志朋的博客 Eureka的一些概念 Register:服务注册 当 ...

  5. Android5.1.1 - APK签名校验分析和修改源码绕过签名校验

    Android5.1.1 - APK签名校验分析和修改源码绕过签名校验 作者:寻禹@阿里聚安全 APK签名校验分析 找到PackageParser类,该类在文件“frameworks/base/cor ...

  6. 【原】Spark中Client源码分析(二)

    继续前一篇的内容.前一篇内容为: Spark中Client源码分析(一)http://www.cnblogs.com/yourarebest/p/5313006.html DriverClient中的 ...

  7. JPEG概述和头分析(C源码)

    原创文章,转载请注明:JPEG概述和头分析(C源码)  By Lucio.Yang 部分内容来自:w285868925,JPEG压缩标准 1.JPEG概述 JPEG是一个压缩标准,又可分为标准 JPE ...

  8. Cesium专栏-填挖方分析(附源码下载)

    Cesium 是一款面向三维地球和地图的,世界级的JavaScript开源产品.它提供了基于JavaScript语言的开发包,方便用户快速搭建一款零插件的虚拟地球Web应用,并在性能,精度,渲染质量以 ...

  9. Proxy Server源码及分析(TCP Proxy源码 Socket实现端口映射)

    版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明.本文链接:https://blog.csdn.net/u014530704/article/de ...

随机推荐

  1. ubuntu 和 centos 如何区分系统

    Ubuntu Ubuntu有着漂亮的用户界面,完善的包管理系统,强大的软件源支持,丰富的技术社区,Ubuntu还对大多数硬件有着良好的兼容性,包括最新的图形显卡等等.这一切让Ubuntu越来越向大众化 ...

  2. Java中的动态定义数组

    1.一维矩阵的动态定义(代码注释) 1.1方法一 package dongtai; import java.util.Scanner; import java.util.ArrayList; publ ...

  3. oracle常用字符函数

    字符函数: concat:(字符连接函数) --字符连接 select concat('con','cat') from dual; select 'co'||'nc'||'at' from dual ...

  4. 字节码编程,Byte-buddy篇一《基于Byte Buddy语法创建的第一个HelloWorld》

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 相对于小傅哥之前编写的字节码编程: ASM.Javassist 系列,Byte Bu ...

  5. Java语言简介、基础组成、封装、继承、多态、抽象类、内部类、接口

    目录 Java简介 Java语言基础组成 面向对象 对象 封装 构造函数 this关键字 static(静态关键字) 主函数 静态什么时候用呢? 面向对象(数组工具对象建立) 设计模式 继承 成员变量 ...

  6. Linux 物理卷(PV)、逻辑卷(LV)、卷组(VG)管理

    (一)相关概念 逻辑卷是使用逻辑卷组管理(Logic Volume Manager)创建出来的设备,如果要了解逻辑卷,那么首先需要了解逻辑卷管理中的一些概念. 物理卷(Physical Volume, ...

  7. python之导入模块的方法

    一.导入标准库模块 1.第一种方式: 可以通过以下方法导入 import time 当使用时间模块的sleep方法时可以使用 time.sleep(2) 2.第二种方式: 当只想使用sleep函数时, ...

  8. Linux shell 正则表达式用法

    1.“ \  ” 用法 用于关闭其后续字符的特殊含义,恢复字符的本身含义,如:\\ 表示字符 \ 2. “ . " 用法 匹配任意单个字符 3. " * " 用法 匹配任 ...

  9. python之模块、类、对象

    模块就像字典 字典是python中唯一映射关系,它用一个事物对应另外一个事物,也就是所谓的key->value. 模块包含一些变量和函数,可以导入,并且可以用点(·)来操作访问变量和函数. 记住 ...

  10. 微信小程序canvas canvasGetImageData方法真机获得数据显示到image为空白

    方法 wx.canvasGetImageData的参数 width,height 只能是整数