文章目录
前言
Eureka
客户端启动初始化的时候,会首先去EurekaServer
拉取一次全量注册表,还会创建一个定时任务,默认30s
执行一次,然后定时去EurekaServer
端拉取一次增量注册表信息。
一、增量拉取注册表源码解析
1.1、ApplicationsResource#getContainerDifferential
Eureka
客户端发送请求,服务端的处理接口是在ApplicationsResource#getContainerDifferential
方法去处理的,流程:
- 先是创建一个缓存
Key
,name
是ALL_APPS_DELTA
- 调用
responseCache#getGZIP
方法
//增量拉取注册表信息
public Response getContainerDifferential(
@PathParam("version") String version,
@HeaderParam(HEADER_ACCEPT) String acceptHeader,
@HeaderParam(HEADER_ACCEPT_ENCODING) String acceptEncoding,
@HeaderParam(EurekaAccept.HTTP_X_EUREKA_ACCEPT) String eurekaAccept,
@Context UriInfo uriInfo, @Nullable @QueryParam("regions") String regionsStr) {
boolean isRemoteRegionRequested = null != regionsStr && !regionsStr.isEmpty();
// If the delta flag is disabled in discovery or if the lease expiration
// has been disabled, redirect clients to get all instances
if ((serverConfig.shouldDisableDelta()) || (!registry.shouldAllowAccess(isRemoteRegionRequested))) {
return Response.status(Status.FORBIDDEN).build();
}
String[] regions = null;
if (!isRemoteRegionRequested) {
EurekaMonitors.GET_ALL_DELTA.increment();
} else {
regions = regionsStr.toLowerCase().split(",");
Arrays.sort(regions); // So we don't have different caches for same regions queried in different order.
EurekaMonitors.GET_ALL_DELTA_WITH_REMOTE_REGIONS.increment();
}
CurrentRequestVersion.set(Version.toEnum(version));
KeyType keyType = Key.KeyType.JSON;
String returnMediaType = MediaType.APPLICATION_JSON;
if (acceptHeader == null || !acceptHeader.contains(HEADER_JSON_VALUE)) {
keyType = Key.KeyType.XML;
returnMediaType = MediaType.APPLICATION_XML;
}
//创建缓存key
Key cacheKey = new Key(Key.EntityType.Application,
ResponseCacheImpl.ALL_APPS_DELTA,
keyType, CurrentRequestVersion.get(), EurekaAccept.fromString(eurekaAccept), regions
);
final Response response;
//go getGZIP
if (acceptEncoding != null && acceptEncoding.contains(HEADER_GZIP_VALUE)) {
response = Response.ok(responseCache.getGZIP(cacheKey))
.header(HEADER_CONTENT_ENCODING, HEADER_GZIP_VALUE)
.header(HEADER_CONTENT_TYPE, returnMediaType)
.build();
} else {
response = Response.ok(responseCache.get(cacheKey)).build();
}
CurrentRequestVersion.remove();
return response;
}
1.2、ResponseCacheImpl#getGZIP
这里直接调用
getValue
方法获取值
public byte[] getGZIP(Key key) {
// 调用 getValue 方法获取一个 value
Value payload = getValue(key, shouldUseReadOnlyResponseCache);
if (payload == null) {
return null;
}
return payload.getGzipped();
}
1.3、ResponseCacheImpl#getValue
- 如果开启一级缓存就先去一级缓存中读取,没有的话就去二级缓存中读取,读取后并同步到一级缓存,如果二级缓存中没有,则会调用
CacheLoader
的load
方法进行加载,这是guava
的cache
。
Value getValue(final Key key, boolean useReadOnlyCache) {
Value payload = null;
try {
//如果使用readOnly缓存,先根据key从readOnly缓存中获取,如果获取到了直接返回
if (useReadOnlyCache) {
final Value currentPayload = readOnlyCacheMap.get(key);
if (currentPayload != null) {
payload = currentPayload;
} else {
//如果一级缓存中获取不到,就从readWrite二级缓存中获取,然后将值缓存到readOnly一级缓存中
//这里用的是guava cache,如果二级缓存中也没有,会调用lead方法去加。
payload = readWriteCacheMap.get(key);
readOnlyCacheMap.put(key, payload);
}
} else {
payload = readWriteCacheMap.get(key);
}
} catch (Throwable t) {
logger.error("Cannot get value for key : {}", key, t);
}
return payload;
}
1.4、CacheLoader#load
这里在
load
方法中会调用generatePayload
生成对应的Value
//创建了一个readWriteMap,使用guava中的cache
this.readWriteCacheMap =
CacheBuilder.newBuilder().initialCapacity(serverConfig.getInitialCapacityOfResponseCache())
.expireAfterWrite(serverConfig.getResponseCacheAutoExpirationInSeconds(), TimeUnit.SECONDS)
.removalListener(new RemovalListener<Key, Value>() {
@Override
public void onRemoval(RemovalNotification<Key, Value> notification) {
Key removedKey = notification.getKey();
if (removedKey.hasRegions()) {
Key cloneWithNoRegions = removedKey.cloneWithoutRegions();
regionSpecificKeys.remove(cloneWithNoRegions, removedKey);
}
}
})
.build(new CacheLoader<Key, Value>() {
@Override
public Value load(Key key) throws Exception {
if (key.hasRegions()) {
Key cloneWithNoRegions = key.cloneWithoutRegions();
regionSpecificKeys.put(cloneWithNoRegions, key);
}
Value value = generatePayload(key);
return value;
}
});
1.4、ResponseCacheImpl#generatePayload
由于这里我们的keyName是ALL_APPS_DELTA,所以会调用registry.getApplicationDeltas()去获取最近变更的实例信息集合。
private Value generatePayload(Key key) {
Stopwatch tracer = null;
try {
String payload;
switch (key.getEntityType()) {
case Application:
boolean isRemoteRegionRequested = key.hasRegions();
if (ALL_APPS.equals(key.getName())) {
if (isRemoteRegionRequested) {
tracer = serializeAllAppsWithRemoteRegionTimer.start();
payload = getPayLoad(key, registry.getApplicationsFromMultipleRegions(key.getRegions()));
} else {
tracer = serializeAllAppsTimer.start();
payload = getPayLoad(key, registry.getApplications());
}
} else if (ALL_APPS_DELTA.equals(key.getName())) {
if (isRemoteRegionRequested) {
tracer = serializeDeltaAppsWithRemoteRegionTimer.start();
versionDeltaWithRegions.incrementAndGet();
versionDeltaWithRegionsLegacy.incrementAndGet();
payload = getPayLoad(key,
registry.getApplicationDeltasFromMultipleRegions(key.getRegions()));
} else {
tracer = serializeDeltaAppsTimer.start();
versionDelta.incrementAndGet();
versionDeltaLegacy.incrementAndGet();
//调用getApplicationDeltas获取最近变更的实例信息集合,然后调用getPayLoad方法编码实例信息
payload = getPayLoad(key, registry.getApplicationDeltas());
}
} else {
tracer = serializeOneApptimer.start();
payload = getPayLoad(key, registry.getApplication(key.getName()));
}
break;
case VIP:
case SVIP:
tracer = serializeViptimer.start();
payload = getPayLoad(key, getApplicationsForVip(key, registry));
break;
default:
logger.error("Unidentified entity type: {} found in the cache key.", key.getEntityType());
payload = "";
break;
}
return new Value(payload);
} finally {
if (tracer != null) {
tracer.stop();
}
}
}
1.5、ResponseCacheImpl#generatePayload
注意这里首先加了一个写锁,之前讲解服务注册、续约、下线的时候都是使用读锁,这里使用读锁就是为了保证这一刻注册表的数据不发生变更。至于为什么这里查询加读锁而注册,下线等修改操作加读锁还是为了减少锁竞争。
流程解析:
- 遍历
recentlyChangedQueue
队列(包含服务注册,下线等变更信息,这个队列只保存三分钟),将队列的实例信息添加到一个集合中- 获取全量注册表实例信息,生成一个
hashcode
,这个hashcode
会返回给客户端,这个hashcode
是校准使用的,客户端收到响应会将变更信息更新到自己的本地注册表,然后,对自己的本地注册表也生成一个hashcode
,然后与服务端响应回来的hashcode
做比较,如果不一样,说明出现了差异,那么会全量拉取一次注册表信息。
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>();
write.lock();
try {
//遍历 recentlyChangedQueue 队列(服务注册,续约,下线都会被塞到这个最近改变队列中,只保存最近三分钟的数据)
//将队列中的实例信息塞到一个集合中
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);
}
}
}
}
//获取全量注册表实例信息,生成一个hashcode,这个hashcode主要带给Eureka客户端,客户端收到响应后,先将最近变更实例信息更新到自己的本地注册表中,然后对自己本地注册表信息生成
//一个hashcode,如果和Eureka Server响应回来的那个hashcode做比较,一样的话说明一致,不一样的话,说明出现了差异,会全量拉取一次注册表信息放到本地注册表中
Applications allApps = getApplications(!disableTransparentFallback);
apps.setAppsHashCode(allApps.getReconcileHashCode());
return apps;
} finally {
write.unlock();
}
}