Eureka客戶端原始碼解析 註冊/心跳/本地重新整理/下線
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();
}
}
}
相關文章
- Spring Cloud系列(四):Eureka原始碼解析之客戶端SpringCloud原始碼客戶端
- 註冊中心 Eureka 原始碼解析 —— 應用例項註冊發現(三)之下線原始碼
- Nacos - 客戶端註冊客戶端
- 註冊客戶端事件客戶端事件
- 註冊中心 Eureka 原始碼解析 —— 應用例項註冊發現(一)之註冊原始碼
- 記一次eureka客戶端註冊失敗的問題客戶端
- 註冊中心 Eureka 原始碼解析 —— 應用例項註冊發現(五)之過期原始碼
- Oauth2(2)客戶端註冊OAuth客戶端
- Nacos - 客戶端心跳續約及客戶端總結客戶端
- 註冊中心 Eureka 原始碼解析 —— 應用例項註冊發現(六)之全量獲取原始碼
- 註冊中心 Eureka 原始碼解析 —— 應用例項註冊發現 (四)之自我保護機制原始碼
- Flutter在Android端註冊外掛流程原始碼解析FlutterAndroid原始碼
- Fabric1.4原始碼解析:客戶端建立通道過程原始碼客戶端
- Eureka註冊中心
- eureka客戶端依賴引入爆紅客戶端
- HDFS原始碼解析:教你用HDFS客戶端寫資料原始碼客戶端
- 【SpringCloud】(三):客戶端發現方式 EurekaSpringGCCloud客戶端
- Tars-Java客戶端原始碼分析Java客戶端原始碼
- Telegram原始碼之安卓客戶端配置原始碼安卓客戶端
- Dubbo原始碼解析之客戶端初始化及服務呼叫原始碼客戶端
- mybatis原始碼解析(四)--- MapperStatement的註冊MyBatis原始碼APP
- 故障排查:是什麼 導致了客戶端批量心跳超時掉線客戶端
- Eureka高可用叢集服務端和客戶端配置服務端客戶端
- 批量域更改客戶端本地administrator密碼客戶端密碼
- SpringCloud元件 & 原始碼剖析:Eureka服務註冊方式流程全面分析SpringGCCloud元件原始碼
- 《球球大作戰》原始碼解析:伺服器與客戶端架構原始碼伺服器客戶端架構
- Spring Cloud系列(三):Eureka原始碼解析之服務端SpringCloud原始碼服務端
- MapReduce——客戶端提交任務原始碼分析客戶端原始碼
- Java中OpenAI API客戶端原始碼教程JavaOpenAIAPI客戶端原始碼
- impala客戶端連線客戶端
- Redis客戶端連線Redis客戶端
- 強大的Git客戶端:Tower for Mac註冊啟用版Git客戶端Mac
- 故障排查:是什麼導致了客戶端批量心跳超時掉線(轉)客戶端
- 解析RocketMQ的client客戶端MQclient客戶端
- git客戶端建立本地公鑰Git客戶端
- 超詳細的Eureka原始碼解析原始碼
- 一個線上問題的思考:Eureka註冊中心叢集如何實現客戶端請求負載及故障轉移?客戶端負載
- Mailplane for Mac(Gmail郵件客戶端) v 4.3.1註冊啟用版AIMac客戶端