Eureka客戶端原始碼解析 註冊/心跳/本地重新整理/下線

跟心愛的人浪跡天涯發表於2020-11-18

Eureka服務端與客戶端互動是通過傳送http請求完成的. 使用JerseyClient進行服務間通訊, Jersey是一個RESTFUL請求服務JAVA框架, 與常規的JAVA程式設計使用的struts框架類似, 它主要用於處理業務邏輯層.

入口

在Client包中找到啟動類 重點EurekaClientAutoConfiguration

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

org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.netflix.eureka.config.EurekaDiscoveryClientConfigServiceBootstrapConfiguration

org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration#eurekaInstanceConfigBean

@Bean
@ConditionalOnMissingBean(value = EurekaInstanceConfig.class, search = SearchStrategy.CURRENT)
public EurekaInstanceConfigBean eurekaInstanceConfigBean(InetUtils inetUtils,
		ManagementMetadataProvider managementMetadataProvider) {
	//	從配置檔案中載入一系列配置
	...
}

初始化client

org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration.RefreshableEurekaClientConfiguration#eurekaClient
@Bean(destroyMethod = "shutdown")
@ConditionalOnMissingBean(value = EurekaClient.class, search = SearchStrategy.CURRENT)
@org.springframework.cloud.context.config.annotation.RefreshScope
@Lazy
public EurekaClient eurekaClient(ApplicationInfoManager manager,
		EurekaClientConfig config, EurekaInstanceConfig instance,
		@Autowired(required = false) HealthCheckHandler healthCheckHandler) {
	...
	// 初始化client
	CloudEurekaClient cloudEurekaClient = new CloudEurekaClient(appManager,
			config, this.optionalArgs, this.context);
	cloudEurekaClient.registerHealthCheck(healthCheckHandler);
	return cloudEurekaClient;
}

org.springframework.cloud.netflix.eureka.CloudEurekaClient#CloudEurekaClient
public CloudEurekaClient(ApplicationInfoManager applicationInfoManager,
		EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs<?> args,
		ApplicationEventPublisher publisher) {
	super(applicationInfoManager, config, args); // 呼叫父類構造方法
	this.applicationInfoManager = applicationInfoManager;
	this.publisher = publisher;
	this.eurekaTransportField = ReflectionUtils.findField(DiscoveryClient.class,
			"eurekaTransport");
	ReflectionUtils.makeAccessible(this.eurekaTransportField);
}

同步 註冊 開啟重新整理/心跳包

com.netflix.discovery.DiscoveryClient#DiscoveryClient
@Inject
DiscoveryClient(ApplicationInfoManager applicationInfoManager, EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args,
				Provider<BackupRegistry> backupRegistryProvider, EndpointRandomizer endpointRandomizer) {
	// 一系列賦值
	...
	try {
		// 建立可定時執行的執行緒池 
		scheduler = Executors.newScheduledThreadPool(2,
				new ThreadFactoryBuilder()
						.setNameFormat("DiscoveryClient-%d")
						.setDaemon(true)
						.build());

		// 建立用於傳送心跳包的執行緒池
		heartbeatExecutor = new ThreadPoolExecutor(
				1, clientConfig.getHeartbeatExecutorThreadPoolSize(), 0, TimeUnit.SECONDS,
				new SynchronousQueue<Runnable>(),
				new ThreadFactoryBuilder()
						.setNameFormat("DiscoveryClient-HeartbeatExecutor-%d")
						.setDaemon(true)
						.build()
		);  // use direct handoff

		// 建立用於重新整理本地應用列表的執行緒池
		cacheRefreshExecutor = new ThreadPoolExecutor(
				1, clientConfig.getCacheRefreshExecutorThreadPoolSize(), 0, TimeUnit.SECONDS,
				new SynchronousQueue<Runnable>(),
				new ThreadFactoryBuilder()
						.setNameFormat("DiscoveryClient-CacheRefreshExecutor-%d")
						.setDaemon(true)
						.build()
		);  // use direct handoff
		...
	} catch (Throwable e) {
		throw new RuntimeException("Failed to initialize DiscoveryClient!", e);
	}

	// 從註冊中心獲取服務列表
	if (clientConfig.shouldFetchRegistry() && !fetchRegistry(false)) {
		fetchRegistryFromBackup();
	}
	...
	try {
		if (!register() ) { // 註冊服務
			throw new IllegalStateException("Registration error at startup. Invalid server response.");
		}
	} catch (Throwable th) {
		logger.error("Registration error at startup: {}", th.getMessage());
		throw new IllegalStateException(th);
	}
	...
	// 開啟執行執行緒池 傳送心跳包 重新整理本地應用列表
	initScheduledTasks();
	...
}

從服務端同步資訊

private boolean fetchRegistry(boolean forceFullRegistryFetch) {
	Stopwatch tracer = FETCH_REGISTRY_TIMER.start();

	try {
		Applications applications = getApplications();

		if (clientConfig.shouldDisableDelta()
				|| (!Strings.isNullOrEmpty(clientConfig.getRegistryRefreshSingleVipAddress()))
				|| forceFullRegistryFetch
				|| (applications == null)
				|| (applications.getRegisteredApplications().size() == 0)
				|| (applications.getVersion() == -1)) //Client application does not have latest library supporting delta
		{
			...
			getAndStoreFullRegistry();
		}
		...
	} catch (Throwable e) {
		logger.error(PREFIX + "{} - was unable to refresh its cache! status = {}", appPathIdentifier, e.getMessage(), e);
		return false;
	} finally {
		if (tracer != null) {
			tracer.stop();
		}
	}

	// Notify about cache refresh before updating the instance remote status
	onCacheRefreshed();

	// Update remote status based on refreshed data held in the cache
	updateInstanceRemoteStatus();

	// registry was fetched successfully, so return true
	return true;
}

private void getAndStoreFullRegistry() throws Throwable {
	long currentUpdateGeneration = fetchRegistryGeneration.get();
	Applications apps = null;
	EurekaHttpResponse<Applications> httpResponse = clientConfig.getRegistryRefreshSingleVipAddress() == null
			? eurekaTransport.queryClient.getApplications(remoteRegionsRef.get()) // 預設執行此方法
			: eurekaTransport.queryClient.getVip(clientConfig.getRegistryRefreshSingleVipAddress(), remoteRegionsRef.get());
	...
	if (apps == null) {
		logger.error("The application is null for some reason. Not storing this information");
	} else if (fetchRegistryGeneration.compareAndSet(currentUpdateGeneration, currentUpdateGeneration + 1)) {
		// 過濾可用的服務 並打亂儲存 AtomicReference<Applications> 底層使用ConcurrentHashMap儲存
		localRegionApps.set(this.filterAndShuffle(apps));
		logger.debug("Got full registry with apps hashcode {}", apps.getAppsHashCode());
	} else {
		logger.warn("Not updating applications as another thread is updating it already");
	}
}

com.netflix.discovery.shared.transport.jersey.AbstractJerseyEurekaHttpClient#getApplications
public EurekaHttpResponse<Applications> getApplications(String... regions) {
	return getApplicationsInternal("apps/", regions);
}

// 使用jerseyClient傳送GET請求
private EurekaHttpResponse<Applications> getApplicationsInternal(String urlPath, String[] regions) {
	ClientResponse response = null;
	String regionsParamValue = null;
	try {
		WebResource webResource = jerseyClient.resource(serviceUrl).path(urlPath);
		if (regions != null && regions.length > 0) {
			regionsParamValue = StringUtil.join(regions);
			webResource = webResource.queryParam("regions", regionsParamValue);
		}
		Builder requestBuilder = webResource.getRequestBuilder();
		addExtraHeaders(requestBuilder);
		response = requestBuilder.accept(MediaType.APPLICATION_JSON_TYPE).get(ClientResponse.class);

		Applications applications = null;
		if (response.getStatus() == Status.OK.getStatusCode() && response.hasEntity()) {
			applications = response.getEntity(Applications.class);
		}
		return anEurekaHttpResponse(response.getStatus(), Applications.class)
				.headers(headersOf(response))
				.entity(applications)
				.build();
	} finally {
		if (logger.isDebugEnabled()) {
			logger.debug("Jersey HTTP GET {}/{}?{}; statusCode={}",
					serviceUrl, urlPath,
					regionsParamValue == null ? "" : "regions=" + regionsParamValue,
					response == null ? "N/A" : response.getStatus()
			);
		}
		if (response != null) {
			response.close();
		}
	}
}

註冊服務

com.netflix.discovery.DiscoveryClient#register
boolean register() throws Throwable {
	...
	httpResponse = eurekaTransport.registrationClient.register(instanceInfo);
	...
}

// POST請求註冊服務
com.netflix.discovery.shared.transport.jersey.AbstractJerseyEurekaHttpClient#register
public EurekaHttpResponse<Void> register(InstanceInfo info) {
	String urlPath = "apps/" + info.getAppName();
	ClientResponse response = null;
	try {
		Builder resourceBuilder = jerseyClient.resource(serviceUrl).path(urlPath).getRequestBuilder();
		addExtraHeaders(resourceBuilder);
		response = resourceBuilder
				.header("Accept-Encoding", "gzip")
				.type(MediaType.APPLICATION_JSON_TYPE)
				.accept(MediaType.APPLICATION_JSON)
				.post(ClientResponse.class, info);
		return anEurekaHttpResponse(response.getStatus()).headers(headersOf(response)).build();
	} finally {
		if (logger.isDebugEnabled()) {
			logger.debug("Jersey HTTP POST {}/{} with instance {}; statusCode={}", serviceUrl, urlPath, info.getId(),
					response == null ? "N/A" : response.getStatus());
		}
		if (response != null) {
			response.close();
		}
	}
}

開啟執行緒池

com.netflix.discovery.DiscoveryClient#initScheduledTasks
new TimedSupervisorTask(
		"cacheRefresh",
		scheduler,
		cacheRefreshExecutor,
		registryFetchIntervalSeconds,
		TimeUnit.SECONDS,
		expBackOffBound,
		new CacheRefreshThread() // 本地服務重新整理執行緒
)

new TimedSupervisorTask(
	"heartbeat",
	scheduler,
	heartbeatExecutor,
	renewalIntervalInSecs,
	TimeUnit.SECONDS,
	expBackOffBound,
	new HeartbeatThread() // 心跳執行緒
)

重新整理本地服務列表

com.netflix.discovery.DiscoveryClient.CacheRefreshThread
class CacheRefreshThread implements Runnable {
	public void run() {
		refreshRegistry();
	}
}

void refreshRegistry() {
	try {
		...
		boolean success = fetchRegistry(remoteRegionsModified);
		...
	} catch (Throwable e) {
		logger.error("Cannot fetch registry from server", e);
	}
}

心跳執行緒

com.netflix.discovery.DiscoveryClient.HeartbeatThread

private class HeartbeatThread implements Runnable {
	public void run() {
		if (renew()) {
			lastSuccessfulHeartbeatTimestamp = System.currentTimeMillis();
		}
	}
}

boolean renew() {
	...
	httpResponse = eurekaTransport.registrationClient.sendHeartBeat(instanceInfo.getAppName(), instanceInfo.getId(), instanceInfo, null);
	...
}

// jerseyClient傳送PUT心跳請求
com.netflix.discovery.shared.transport.jersey.AbstractJerseyEurekaHttpClient#sendHeartBeat
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());
		if (overriddenStatus != null) {
			webResource = webResource.queryParam("overriddenstatus", overriddenStatus.name());
		}
		Builder requestBuilder = webResource.getRequestBuilder();
		addExtraHeaders(requestBuilder);
		response = requestBuilder.put(ClientResponse.class);
		EurekaHttpResponseBuilder<InstanceInfo> eurekaResponseBuilder = anEurekaHttpResponse(response.getStatus(), InstanceInfo.class).headers(headersOf(response));
		if (response.hasEntity()) {
			eurekaResponseBuilder.entity(response.getEntity(InstanceInfo.class));
		}
		return eurekaResponseBuilder.build();
	} finally {
		if (logger.isDebugEnabled()) {
			logger.debug("Jersey HTTP PUT {}/{}; statusCode={}", serviceUrl, urlPath, response == null ? "N/A" : response.getStatus());
		}
		if (response != null) {
			response.close();
		}
	}
}

取消服務

PreDestroy 在容器關閉前呼叫

com.netflix.discovery.DiscoveryClient#shutdown
@PreDestroy 
public synchronized void shutdown() {
	...
	cancelScheduledTasks(); // shutdownNow各執行緒池
	if (applicationInfoManager != null
			&& clientConfig.shouldRegisterWithEureka()
			&& clientConfig.shouldUnregisterOnShutdown()) {
		applicationInfoManager.setInstanceStatus(InstanceStatus.DOWN);
		// 傳送取消服務請求
		unregister(); 
	}
	...
}

void unregister() {
	// It can be null if shouldRegisterWithEureka == false
	if(eurekaTransport != null && eurekaTransport.registrationClient != null) {
		try {
			logger.info("Unregistering ...");
			EurekaHttpResponse<Void> httpResponse = eurekaTransport.registrationClient.cancel(instanceInfo.getAppName(), instanceInfo.getId());
			logger.info(PREFIX + "{} - deregister  status: {}", appPathIdentifier, httpResponse.getStatusCode());
		} catch (Exception e) {
			logger.error(PREFIX + "{} - de-registration failed{}", appPathIdentifier, e.getMessage(), e);
		}
	}
}

// jerseyClient傳送DELETE請求
com.netflix.discovery.shared.transport.jersey.AbstractJerseyEurekaHttpClient#cancel
public EurekaHttpResponse<Void> cancel(String appName, String id) {
	String urlPath = "apps/" + appName + '/' + id;
	ClientResponse response = null;
	try {
		Builder resourceBuilder = jerseyClient.resource(serviceUrl).path(urlPath).getRequestBuilder();
		addExtraHeaders(resourceBuilder);
		response = resourceBuilder.delete(ClientResponse.class);
		return anEurekaHttpResponse(response.getStatus()).headers(headersOf(response)).build();
	} finally {
		if (logger.isDebugEnabled()) {
			logger.debug("Jersey HTTP DELETE {}/{}; statusCode={}", serviceUrl, urlPath, response == null ? "N/A" : response.getStatus());
		}
		if (response != null) {
			response.close();
		}
	}
}

點此跳轉 Eureka服務端原始碼解析 入口/註冊/重新整理/下線

相關文章