本章主要介紹Eureka Client端源碼分析。
客戶端主要是向Server服務端發(fā)送Http請求,主要有注冊,心跳續(xù)約,獲取注冊信息等功能
實例配置
在分析源碼之前,需要查看下客戶端配置文件
application.yml
server:
port: 8020
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8000/eureka/ #eureka服務端提供的注冊地址 參考服務端配置的這個路徑
instance:
instance-id: client-0 #此實例注冊到eureka服務端的唯一的實例ID
prefer-ip-address: true #是否顯示IP地址
leaseRenewalIntervalInSeconds: 10 #eureka客戶需要多長時間發(fā)送心跳給eureka服務器,表明它仍然活著,默認為30 秒 (與下面配置的單位都是秒)
leaseExpirationDurationInSeconds: 30 #Eureka服務器在接收到實例的最后一次發(fā)出的心跳后,需要等待多久才可以將此實例刪除,默認為90秒
spring:
application:
name: service-client #此實例注冊到eureka服務端的name
訪問服務端localhost:8000的注冊信息
EurekaClientAutoConfiguration
自動配置類,首先要從依賴包的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,\
org.springframework.cloud.netflix.eureka.reactive.EurekaReactiveDiscoveryClientConfiguration,\
org.springframework.cloud.netflix.eureka.loadbalancer.LoadBalancerEurekaAutoConfiguration
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.netflix.eureka.config.EurekaDiscoveryClientConfigServiceBootstrapConfiguration
其中最重要的是EurekaClientAutoConfiguration類
只要類中存在EurekaClientConfig類所在的依賴包eureka-client-xx.jar就可以加載這個類
初始化EurekaClientAutoConfiguration類中的方法Bean加載到spring容器中:
1、EurekaAutoServiceRegistration 用來注冊
2、RefreshableEurekaClientConfiguration 用來開啟定時任務
注冊功能實現
1、EurekaAutoServiceRegistration
實例化EurekaAutoServiceRegistration對象,并放到spring容器中
org.springframework.cloud.netflix.eureka.serviceregistry.EurekaAutoServiceRegistration
2、start
由于EurekaAutoServiceRegistration類實現了SmartLifecycle,SmartApplicationListener等接口,所以會在容器初始化完成之后調用EurekaAutoServiceRegistration#start方法。
調用EurekaServiceRegistry的注冊方法和發(fā)布InstanceRegisteredEvent事件
3、register
org.springframework.cloud.netflix.eureka.serviceregistry.EurekaServiceRegistry#register
調用ApplicationInfoManager應用信息管理設置實例初始化狀態(tài)信息initialStatus
4、setInstanceStatus
com.netflix.appinfo.ApplicationInfoManager#setInstanceStatus
設置實例狀態(tài)信息并調用監(jiān)聽器notify方法
5、notify
com.netflix.appinfo.ApplicationInfoManager.StatusChangeListener#notify
調用StatusChangeListener狀態(tài)改變監(jiān)聽器的notify方法。
這個對象實現在com.netflix.discovery.DiscoveryClient#initScheduledTasks方法內
6、onDemandUpdate
com.netflix.discovery.InstanceInfoReplicator#onDemandUpdate
7、run
由于InstanceInfoReplicator 類實現了Runnable接口,所以會調用這個run方法
8、register
com.netflix.discovery.DiscoveryClient#register
這里就到了注冊客戶端的地方
9、register
com.netflix.discovery.shared.transport.jersey.AbstractJerseyEurekaHttpClient#register
封裝http請求并調用到服務端方法
所以,封裝的url是:服務端的serviceUrl:defaultZone/apps/客戶端應用名
調到Eureka Server端
com.netflix.eureka.resources.ApplicationResource#addInstance
定時刷新任務
包括緩存更新與心跳續(xù)約,通過RefreshableEurekaClientConfiguration類開始初始化,并最終通過initScheduledTasks方法開啟定時調度器任務
RefreshableEurekaClientConfiguration
EurekaClientAutoConfiguration.RefreshableEurekaClientConfiguration
@Configuration(proxyBeanMethods = false)
@ConditionalOnRefreshScope
protected static class RefreshableEurekaClientConfiguration {
@Autowired
private ApplicationContext context;
@Autowired
private AbstractDiscoveryClientOptionalArgs<?> optionalArgs;
@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) {
// If we use the proxy of the ApplicationInfoManager we could run into a
// problem
// when shutdown is called on the CloudEurekaClient where the
// ApplicationInfoManager bean is
// requested but wont be allowed because we are shutting down. To avoid this
// we use the
// object directly.
ApplicationInfoManager appManager;
if (AopUtils.isAopProxy(manager)) {
appManager = ProxyUtils.getTargetObject(manager);
}
else {
appManager = manager;
}
CloudEurekaClient cloudEurekaClient = new CloudEurekaClient(appManager,
config, this.optionalArgs, this.context);
cloudEurekaClient.registerHealthCheck(healthCheckHandler);
return cloudEurekaClient;
}
}
這個類是程序實現定時刷新任務開始的地方,主要是通過new CloudEurekaClient()方法創(chuàng)建Cloud客戶端類(CloudEurekaClient)
CloudEurekaClient
下面主要查看CloudEurekaClient的調用鏈
1、CloudEurekaClient#CloudEurekaClient
org.springframework.cloud.netflix.eureka.CloudEurekaClient#CloudEurekaClient
調用CloudEurekaClient類的父類,和對applicationInfoManager、publisher、eurekaTransportField等屬性值賦值
2、DiscoveryClient#DiscoveryClient
com.netflix.discovery.DiscoveryClient#DiscoveryClient
3、DiscoveryClient#DiscoveryClient
com.netflix.discovery.DiscoveryClient#DiscoveryClient
構造器類調用,傳入獲取備用服務器backupRegistryInstance實例的get方法。不在這里調用
public DiscoveryClient(ApplicationInfoManager applicationInfoManager, final EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args, EndpointRandomizer randomizer) {
this(applicationInfoManager, config, args, new Provider<BackupRegistry>() {
private volatile BackupRegistry backupRegistryInstance;
@Override
public synchronized BackupRegistry get() {
if (backupRegistryInstance == null) {
String backupRegistryClassName = config.getBackupRegistryImpl();
if (null != backupRegistryClassName) {
try {
backupRegistryInstance = (BackupRegistry) Class.forName(backupRegistryClassName).newInstance();
logger.info("Enabled backup registry of type {}", backupRegistryInstance.getClass());
} catch (InstantiationException e) {
logger.error("Error instantiating BackupRegistry.", e);
} catch (IllegalAccessException e) {
logger.error("Error instantiating BackupRegistry.", e);
} catch (ClassNotFoundException e) {
logger.error("Error instantiating BackupRegistry.", e);
}
}
if (backupRegistryInstance == null) {
logger.warn("Using default backup registry implementation which does not do anything.");
backupRegistryInstance = new NotImplementedRegistryImpl();
}
}
return backupRegistryInstance;
}
}, randomizer);
}
4、DiscoveryClient#DiscoveryClient
定義調度器類、心跳執(zhí)行器和緩存刷新執(zhí)行器等的定義。
com.netflix.discovery.DiscoveryClient#DiscoveryClient
DiscoveryClient(ApplicationInfoManager applicationInfoManager, EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args,
Provider<BackupRegistry> backupRegistryProvider, EndpointRandomizer endpointRandomizer) {
// ........前半部分省略
try {
// default size of 2 - 1 each for heartbeat and cacheRefresh
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
eurekaTransport = new EurekaTransport();
scheduleServerEndpointTask(eurekaTransport, args);
AzToRegionMapper azToRegionMapper;
if (clientConfig.shouldUseDnsForFetchingServiceUrls()) {
azToRegionMapper = new DNSBasedAzToRegionMapper(clientConfig);
} else {
azToRegionMapper = new PropertyBasedAzToRegionMapper(clientConfig);
}
if (null != remoteRegionsToFetch.get()) {
azToRegionMapper.setRegionsToFetch(remoteRegionsToFetch.get().split(","));
}
instanceRegionChecker = new InstanceRegionChecker(azToRegionMapper, clientConfig.getRegion());
} catch (Throwable e) {
throw new RuntimeException("Failed to initialize DiscoveryClient!", e);
}
if (clientConfig.shouldFetchRegistry() && !fetchRegistry(false)) {
fetchRegistryFromBackup();
}
// call and execute the pre registration handler before all background tasks (inc registration) is started
if (this.preRegistrationHandler != null) {
this.preRegistrationHandler.beforeRegistration();
}
if (clientConfig.shouldRegisterWithEureka() && clientConfig.shouldEnforceRegistrationAtInit()) {
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);
}
}
// finally, init the schedule tasks (e.g. cluster resolvers, heartbeat, instanceInfo replicator, fetch
initScheduledTasks();
try {
Monitors.registerObject(this);
} catch (Throwable e) {
logger.warn("Cannot register timers", e);
}
// This is a bit of hack to allow for existing code using DiscoveryManager.getInstance()
// to work with DI'd DiscoveryClient
DiscoveryManager.getInstance().setDiscoveryClient(this);
DiscoveryManager.getInstance().setEurekaClientConfig(config);
initTimestampMs = System.currentTimeMillis();
logger.info("Discovery Client initialized at timestamp {} with initial instances count: {}",
initTimestampMs, this.getApplications().size());
}
這個方法主要流程:
①、這個方法前半部分是初始化屬性值。
②、根據客戶端client配置文件,config.shouldFetchRegistry()是否獲取注冊表信息和
config.shouldRegisterWithEureka()是否注冊到eureka上來對屬性賦值,或直接返回
③、初始化調度器scheduler、兩個線程池執(zhí)行器heartbeatExecutor(心跳續(xù)約)和cacheRefreshExecutor(緩存刷新,定時獲取注冊信息表)
④、在獲取服務注冊信息條件下,沒有獲取到信息或異常即fetchRegistry(false)返回false。可以從備用服務器獲取調用fetchRegistryFromBackup()方法,內部實現方法調用備用服務器類的get方法backupRegistryProvider.get()
⑤、初始化調度器任務方法initScheduledTasks()
初始化調度器任務initScheduledTasks
調度器任務包括:
1、定時刷新緩存注冊表信息,分為全量獲取和增量獲取
2、定時向服務端發(fā)送心跳續(xù)約
3、狀態(tài)改變監(jiān)聽器執(zhí)行
這里不僅包括這些定時任務,注冊也是在這里調用狀態(tài)改變監(jiān)聽器StatusChangeListener的notify方法
com.netflix.discovery.DiscoveryClient#initScheduledTasks
/**
* Initializes all scheduled tasks.
*/
private void initScheduledTasks() {
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);
}
if (clientConfig.shouldRegisterWithEureka()) {
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);
// InstanceInfo replicator
instanceInfoReplicator = new InstanceInfoReplicator(
this,
instanceInfo,
clientConfig.getInstanceInfoReplicationIntervalSeconds(),
2); // burstSize
statusChangeListener = new ApplicationInfoManager.StatusChangeListener() {
@Override
public String getId() {
return "statusChangeListener";
}
@Override
public void notify(StatusChangeEvent statusChangeEvent) {
if (InstanceStatus.DOWN == statusChangeEvent.getStatus() ||
InstanceStatus.DOWN == statusChangeEvent.getPreviousStatus()) {
// log at warn level if DOWN was involved
logger.warn("Saw local status change event {}", statusChangeEvent);
} else {
logger.info("Saw local status change event {}", statusChangeEvent);
}
instanceInfoReplicator.onDemandUpdate();
}
};
if (clientConfig.shouldOnDemandUpdateStatusChange()) {
applicationInfoManager.registerStatusChangeListener(statusChangeListener);
}
instanceInfoReplicator.start(clientConfig.getInitialInstanceInfoReplicationIntervalSeconds());
} else {
logger.info("Not registering with Eureka server per configuration");
}
}
1、心跳續(xù)約
1、initScheduledTasks
com.netflix.discovery.DiscoveryClient#initScheduledTasks
TimedSupervisorTask繼承了TimerTask,TimerTask實現了Runnable
TimedSupervisorTask類的構造方法
public TimedSupervisorTask(String name, ScheduledExecutorService scheduler, ThreadPoolExecutor executor,
int timeout, TimeUnit timeUnit, int expBackOffBound, Runnable task) {
this.scheduler = scheduler;
this.executor = executor;
this.timeoutMillis = timeUnit.toMillis(timeout);
this.task = task;
this.delay = new AtomicLong(timeoutMillis);
this.maxDelay = timeoutMillis * expBackOffBound;
Monitors.registerObject(name, this);
}
2、HeartbeatThread
執(zhí)行TimedSupervisorTask的task任務,在給定的間隔內執(zhí)行心跳續(xù)約任務
com.netflix.discovery.DiscoveryClient.HeartbeatThread
3、renew
續(xù)約任務,續(xù)約成功更新lastSuccessfulHeartbeatTimestamp參數。通過REST方式進行續(xù)訂
com.netflix.discovery.DiscoveryClient#renew
4、sendHeartBeat
拼接http請求,發(fā)送心跳
com.netflix.discovery.shared.transport.jersey.AbstractJerseyEurekaHttpClient#sendHeartBeat
客戶端appName=SERVICE-CLIENT,id是instance-id屬性值client-0
服務端調用到renewLease方法續(xù)約,appName和id與客戶端傳過來的相同
com.netflix.eureka.resources.InstanceResource#renewLease
全量拉取和增量拉取
在定時刷新緩存實現獲取注冊信息,分為全量拉取和增量拉取
創(chuàng)建TimedSupervisorTask調度任務類,傳入cacheRefreshExecutor執(zhí)行器、CacheRefreshThread任務類、從服務端獲取注冊信息的時間間隔RegistryFetchIntervalSeconds等參數信息
1、run
定時執(zhí)行CacheRefreshThread類的run方法
2、refreshRegistry
首先對remoteRegionsModified參數進行判斷,這樣可以確保對遠程區(qū)域進行動態(tài)更改時可以獲取數據。如果更改則remoteRegionsModified=true,只進行全量拉取
com.netflix.discovery.DiscoveryClient#refreshRegistry
3、fetchRegistry
com.netflix.discovery.DiscoveryClient#fetchRegistry
這個方法是決定了使用那種方式拉取
private boolean fetchRegistry(boolean forceFullRegistryFetch) {
Stopwatch tracer = FETCH_REGISTRY_TIMER.start();
try {
// If the delta is disabled or if it is the first time, get all
// applications
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
{
logger.info("Disable delta property : {}", clientConfig.shouldDisableDelta());
logger.info("Single vip registry refresh property : {}", clientConfig.getRegistryRefreshSingleVipAddress());
logger.info("Force full registry fetch : {}", forceFullRegistryFetch);
logger.info("Application is null : {}", (applications == null));
logger.info("Registered Applications size is zero : {}",
(applications.getRegisteredApplications().size() == 0));
logger.info("Application version is -1: {}", (applications.getVersion() == -1));
getAndStoreFullRegistry();
} else {
getAndUpdateDelta(applications);
}
applications.setAppsHashCode(applications.getReconcileHashCode());
logTotalInstances();
} 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;
}
全量拉取條件(任意一個)
①、disable-delta屬性值是true 關閉增量拉取
②、registry-refresh-single-vip-address 屬性vip地址的值不為空
③、forceFullRegistryFetch 為true 傳過來的變量值
④、localRegionApps的applications是null 當前區(qū)域應用
⑤、applications的數量是0
⑥、applications的版本是-1
增量拉取
實現增量拉取的條件是不符合全量拉取,調用getAndUpdateDelta方法
com.netflix.discovery.DiscoveryClient#getAndUpdateDelta
private void getAndUpdateDelta(Applications applications) throws Throwable {
long currentUpdateGeneration = fetchRegistryGeneration.get();
Applications delta = null;
EurekaHttpResponse<Applications> httpResponse = eurekaTransport.queryClient.getDelta(remoteRegionsRef.get());
if (httpResponse.getStatusCode() == Status.OK.getStatusCode()) {
delta = httpResponse.getEntity();
}
if (delta == null) {
logger.warn("The server does not allow the delta revision to be applied because it is not safe. "
+ "Hence got the full registry.");
getAndStoreFullRegistry();
} else if (fetchRegistryGeneration.compareAndSet(currentUpdateGeneration, currentUpdateGeneration + 1)) {
logger.debug("Got delta update with apps hashcode {}", delta.getAppsHashCode());
String reconcileHashCode = "";
if (fetchRegistryUpdateLock.tryLock()) {
try {
updateDelta(delta);
reconcileHashCode = getReconcileHashCode(applications);
} finally {
fetchRegistryUpdateLock.unlock();
}
} else {
logger.warn("Cannot acquire update lock, aborting getAndUpdateDelta");
}
// There is a diff in number of instances for some reason
if (!reconcileHashCode.equals(delta.getAppsHashCode()) || clientConfig.shouldLogDeltaDiff()) {
reconcileAndLogDifference(delta, reconcileHashCode); // this makes a remoteCall
}
} else {
logger.warn("Not updating application delta as another thread is updating it already");
logger.debug("Ignoring delta update with apps hashcode {}, as another thread is updating it already", delta.getAppsHashCode());
}
}
這個方法實現了增量拉取的請求實現,及對拉取增量結果的處理
1、getDelta
eurekaTransport.queryClient.getDelta(remoteRegionsRef.get())的具體實現是通過AbstractJerseyEurekaHttpClient類實現的
com.netflix.discovery.shared.transport.jersey.AbstractJerseyEurekaHttpClient#getDelta
拼接apps/delta開頭的請求
2、getApplicationsInternal
com.netflix.discovery.shared.transport.jersey.AbstractJerseyEurekaHttpClient#getApplicationsInternal
拼接的http請求是:http://localhost:8000/eureka/apps/delta然后調用到Eureka Server服務端
3、getContainerDifferential
服務端通過getContainerDifferential接收
com.netflix.eureka.resources.ApplicationsResource#getContainerDifferential
無論是調用到responseCache.getGZIP(cacheKey)方法,還是responseCache.get(cacheKey)方法。最終都是調用到Eureka Server的查詢的三種緩存,最終會經過讀寫緩存readWriteCacheMap處理。
4、readWriteCacheMap
調用到ResponseCacheImpl構造方法中readWriteCacheMap讀寫緩存的創(chuàng)建中,如果緩存中沒有會調用generatePayload方法
com.netflix.eureka.registry.ResponseCacheImpl#readWriteCacheMap
5、generatePayload
com.netflix.eureka.registry.ResponseCacheImpl#generatePayload
根據傳入的Key參數處理不同的情況
key = { eurekaAccept = "full",entityType = "Application",hashKey = "ApplicationALL_APPS_DELTAJSONV2full", requestVersion = "V2", requestType = "JSON", regions = null, entityName = "ALL_APPS_DELTA"}
entityType 可分為Application、VIP、SVIP、default等情況
在entityType 是Application情況下EntityName分為:"ALL_APPS"(全量拉取)和"ALL_APPS_DELTA"(增量拉取)
在全量或增量拉取下有isRemoteRegionRequested參數判斷是否具有遠程區(qū)域請求
本次處理是:增量拉取的沒有遠程區(qū)域請求,并調用getPayLoad方法加載是否含有增量數據
6、getApplicationDeltas
獲取增量實例
com.netflix.eureka.registry.AbstractInstanceRegistry#getApplicationDeltas
public Applications getApplicationDeltas() {
GET_ALL_CACHE_MISS_DELTA.increment();
Applications apps = new Applications();
apps.setVersion(responseCache.getVersionDelta().get());
Map<String, Application> applicationInstancesMap = new HashMap<String, Application>();
try {
write.lock();
Iterator<RecentlyChangedItem> iter = this.recentlyChangedQueue.iterator();
logger.debug("The number of elements in the delta queue is : {}",
this.recentlyChangedQueue.size());
while (iter.hasNext()) {
Lease<InstanceInfo> lease = iter.next().getLeaseInfo();
InstanceInfo instanceInfo = lease.getHolder();
logger.debug(
"The instance id {} is found with status {} and actiontype {}",
instanceInfo.getId(), instanceInfo.getStatus().name(), instanceInfo.getActionType().name());
Application app = applicationInstancesMap.get(instanceInfo
.getAppName());
if (app == null) {
app = new Application(instanceInfo.getAppName());
applicationInstancesMap.put(instanceInfo.getAppName(), app);
apps.addApplication(app);
}
app.addInstance(new InstanceInfo(decorateInstanceInfo(lease)));
}
boolean disableTransparentFallback = serverConfig.disableTransparentFallbackToOtherRegion();
if (!disableTransparentFallback) {
Applications allAppsInLocalRegion = getApplications(false);
for (RemoteRegionRegistry remoteRegistry : this.regionNameVSRemoteRegistry.values()) {
Applications applications = remoteRegistry.getApplicationDeltas();
for (Application application : applications.getRegisteredApplications()) {
Application appInLocalRegistry =
allAppsInLocalRegion.getRegisteredApplications(application.getName());
if (appInLocalRegistry == null) {
apps.addApplication(application);
}
}
}
}
Applications allApps = getApplications(!disableTransparentFallback);
apps.setAppsHashCode(allApps.getReconcileHashCode());
return apps;
} finally {
write.unlock();
}
}
這個方法主要是遍歷recentlyChangedQueue存在的數據放入到Applications對象中。所以recentlyChangedQueue隊列中存在什么數據就很重要,因此我們需要了解最新更新隊列recentlyChangedQueue是如何放入的及放入那些數據,及其的移除的原理。
在這個方法最后 apps.setAppsHashCode設置了當前服務端所有注冊信息的HashCode,所以這個增量對象存儲了最新的狀態(tài)HashCode值。
7、客戶端獲取增量數據的處理
還是在getAndUpdateDelta方法內,對服務端傳輸過來數據,獲取當前服務端的增量數據部分
com.netflix.discovery.DiscoveryClient#getAndUpdateDelta
這個方法的主要過程是:
如果增量數據部分為空,則執(zhí)行全量拉取。
對當前服務的注冊信息表執(zhí)行updateDelta(delta)方法,對當前注冊實例的增加刪除或修改操作
當前更新后的服務注冊表的HashCode值與增量對象存儲的最新的狀態(tài)HashCode值比較,如果不相等 則執(zhí)行全量拉取
recentlyChangedQueue
最新更新隊列ConcurrentLinkedQueue<RecentlyChangedItem> recentlyChangedQueue
com.netflix.eureka.registry.AbstractInstanceRegistry#AbstractInstanceRegistry類在構建創(chuàng)建注冊表時創(chuàng)建了recentlyChangedQueue隊列,并創(chuàng)建了一個增量調度任務方法getDeltaRetentionTask方法
com.netflix.eureka.registry.AbstractInstanceRegistry#getDeltaRetentionTask
對recentlyChangedQueue隊列中對最近改變的隊列在一定時間范圍retentionTimeInMSInDeltaQueue=180000ms(3分鐘)外的進行定時清除(30s清除一次)
recentlyChangedQueue隊列添加條件:
1、注冊時register
2、下線時Cancel
3、statusUpdate
4、deleteStatusOverride
getDeltaRetentionTask進行定時清除
全量拉取
全量拉取與增量拉取過程類似
全量拉取調用getAndStoreFullRegistry方法
1、getAndStoreFullRegistry
com.netflix.discovery.DiscoveryClient#getAndStoreFullRegistry
2、getApplications
com.netflix.discovery.shared.transport.EurekaHttpClient#getApplications
3、getApplications
com.netflix.discovery.shared.transport.jersey.AbstractJerseyEurekaHttpClient#getApplications
拼接的Http請求是:apps/
4、getApplicationsInternal
com.netflix.discovery.shared.transport.jersey.AbstractJerseyEurekaHttpClient#getApplicationsInternal
5、getContainers
調用到Eureka Server服務端的getContainers方法,這里調用與增量拉取類似。查詢經過三層緩存結構,具體過程可參考增量查詢與三層緩存結構。
com.netflix.eureka.resources.ApplicationsResource#getContainers
如果acceptEncoding包含值gzip將會調用ResponseCacheImpl#getGZIP方法獲取
否則調用ResponseCacheImpl#get(Key)方法
最終會調用到讀寫緩存的generatePayload方法處理
6、generatePayload
com.netflix.eureka.registry.ResponseCacheImpl#generatePayload
Key結構中的EntityName是"ALL_APPS"全量查詢。
7、getApplications
獲取注冊表所有的注冊信息
com.netflix.eureka.registry.AbstractInstanceRegistry#getApplications()
8、結果處理
放入到當前對象localRegionApps緩存中
如果含有獲取遠程區(qū)域注冊信息FetchingRemoteRegionRegistries,只需要分到不同的索引位置
總結:
Eureka Client客戶端與Eureka Server服務端是關聯(lián)的,不能分開處理。關鍵點有以下幾點:
客戶端主要了解的是如何向服務端發(fā)起請求及服務端接收接口的對應
客戶端的定時任務有哪些:心跳及緩存獲取
客戶端注冊的實現,緩存獲取原理
緩存獲取中的增量拉取與全量拉取的區(qū)別
增量拉取在服務端的實現原理,由一個狀態(tài)更改隊列實現的