0. 前言
- springboot版本:2.1.9.RELEASE
- springcloud版本:Greenwich.SR4
1. 处理客户端拉取全量注册表
服务端接收客户端拉取全量注册表的请求在 ApplicationsResource 类中的 getContainers() 方法
// ApplicationsResource.class
public Response getContainers(@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) {
// 当 regionsStr 不为空字符串的时候 ,isRemoteRegionRequested = true
// 表示需要拉取的注册表包含远程 region 的
boolean isRemoteRegionRequested = null != regionsStr && !regionsStr.isEmpty();
String[] regions = null;
if (!isRemoteRegionRequested) {
EurekaMonitors.GET_ALL.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_WITH_REMOTE_REGIONS.increment();
}
// Check if the server allows the access to the registry. The server can
// restrict access if it is not
// ready to serve traffic depending on various reasons.
// 1.1 判断服务端是否允许访问注册表
if (!registry.shouldAllowAccess(isRemoteRegionRequested)) {
// 不允许返回403,拒绝请求
return Response.status(Status.FORBIDDEN).build();
}
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.EntityType.Application:Application 表示拉取注册表
// ResponseCacheImpl.ALL_APPS:ALL_APPS 表示全量拉取
// keyType:key 的类型,默认为 json
// CurrentRequestVersion.get():请求的版本号
// EurekaAccept.fromString(eurekaAccept):返回数据格式,默认为 full
// regions:远程 region 列表,不为空表示要从这些远程 region 拉取注册表
Key cacheKey = new Key(Key.EntityType.Application,
ResponseCacheImpl.ALL_APPS,
keyType, CurrentRequestVersion.get(), EurekaAccept.fromString(eurekaAccept), regions
);
Response response;
if (acceptEncoding != null && acceptEncoding.contains(HEADER_GZIP_VALUE)) {
// acceptEncoding = gzip 表示响应体里的数据需要压缩后返回
// 2 从 responseCache 响应缓存中获取注册表
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();
}
return response;
}
1.1 registry.shouldAllowAccess()
// PeerAwareInstanceRegistryImpl.class
public boolean shouldAllowAccess(boolean remoteRegionRequired) {
if (this.peerInstancesTransferEmptyOnStartup) {
// peerInstancesTransferEmptyOnStartup = true 时,表示当前服务端是集群节点中第一个启动的
// 这个时候注册表为空,不允许访问
// 要在一定时间后才能访问,默认当前服务端启动5分钟后
if (!(System.currentTimeMillis() > this.startupTime + serverConfig.getWaitTimeInMsWhenSyncEmpty())) {
return false;
}
}
if (remoteRegionRequired) {
for (RemoteRegionRegistry remoteRegionRegistry : this.regionNameVSRemoteRegistry.values()) {
// 如果需要从远程 region 拉取注册表,本地所有注册了的远程 region 只要有一个还没准备好,那么不允许访问
if (!remoteRegionRegistry.isReadyForServingData()) {
return false;
}
}
}
return true;
}
2. responseCache.getGZIP()
// ResponseCacheImpl.class
public byte[] getGZIP(Key key) {
// 从缓存中获取注册表信息
// shouldUseReadOnlyResponseCache:表示是否允许使用只读缓存,默认为 true
Value payload = getValue(key, shouldUseReadOnlyResponseCache);
if (payload == null) {
return null;
}
// 返回压缩过的数据
return payload.getGzipped();
}
Value getValue(final Key key, boolean useReadOnlyCache) {
Value payload = null;
try {
if (useReadOnlyCache) {
// 如果允许从只读缓存中获取注册表信息
// 先从只读缓存中获取,如果只读缓存中为空,再从读写缓存中获取并保存到只读缓存中
final Value currentPayload = readOnlyCacheMap.get(key);
if (currentPayload != null) {
payload = currentPayload;
} else {
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;
}
3. ResponseCache 缓存机制
分析前简单熟悉下前置知识:
-
LoadingCache :
-
Guava 提供的本地缓存类,在多线程的场景下保证只有一个线程会去加载缓存数据
-
能够指定监听缓存移除时的监听器和实现缓存过期方法,当缓存过期时触发过期方法
-
能够实现加载方法,当缓存中获取不到指定缓存项时触发加载方法
-
有三种基于指定时间清理或刷新缓存项的方式
- expireAfterAccess: 当缓存项在指定的时间段内没有被读或写就会被移除
- expireAfterWrite:当缓存项在指定的时间段内没有被刷新就会被移除
- refreshAfterWrite:缓存项写入后超过指定时间不会被移除,在获取缓存项时如果发现缓存项最近一次写入超过了指定的时间,则触发加载方法刷新缓存项
-
能够指定缓存初始化大小和最大容量,当超过最大容量时利用 LRU 算法移除缓存项
- initialCapacity:指定初始化大小
- maximumSize:指定最大容量
-
public class ResponseCacheImpl implements ResponseCache {
// ......
// 3.1 只读缓存
private final ConcurrentMap<Key, Value> readOnlyCacheMap = new ConcurrentHashMap<Key, Value>();
// 3.2 读写缓存
private final LoadingCache<Key, Value> readWriteCacheMap;
// ......
ResponseCacheImpl(EurekaServerConfig serverConfig, ServerCodecs serverCodecs, AbstractInstanceRegistry registry) {
// ......
// 构建读写缓存,指定了缓存的初始大小(默认1000)、过期时间(默认180s),实现了缓存过期方法、加载方法
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()) {
// 这里的 removedKey 指的是上面的缓存 key
// 如果过期缓存的缓存 key 里的远程 regions 不为空
// 则移除 regionSpecificKeys 相应的值(下面加载方法会放入)
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 里的远程 regions 不为空
// 则以“移除远程 regions 的缓存 key ”为 key ,“未移除远程 regions 的缓存 key ” 为 value ,放入 regionSpecificKeys 中
Key cloneWithNoRegions = key.cloneWithoutRegions();
regionSpecificKeys.put(cloneWithNoRegions, key);
}
// 4 加载指定缓存 key 的 value(注册表)
Value value = generatePayload(key);
return value;
}
});
if (shouldUseReadOnlyResponseCache) {
// 如果允许使用只读缓存,则开启一个定时任务,处理只读缓存和读写缓存之间的数据同步
// 固定时间重复执行的定时任务,默认30s后开始,每30s执行一次
// 3.3 getCacheUpdateTask():缓存同步定时任务
timer.schedule(getCacheUpdateTask(),
new Date(((System.currentTimeMillis() / responseCacheUpdateIntervalMs) * responseCacheUpdateIntervalMs)
+ responseCacheUpdateIntervalMs),
responseCacheUpdateIntervalMs);
}
try {
Monitors.registerObject(this);
} catch (Throwable e) {
logger.warn("Cannot register the JMX monitor for the InstanceRegistry", e);
}
}
// ......
}
3.1 readOnlyCacheMap 和 readWriteCacheMap
-
readOnlyCacheMap:只读缓存在服务端中添加或更新缓存项通过 put 方法,时机是:
- 在获取响应缓存中的注册表信息时,如果允许使用 readOnlyCacheMap ,在 readOnlyCacheMap 中获取不到相应缓存项,则从 readWriteCacheMap 获取后添加进 readOnlyCacheMap
- 执行缓存同步定时任务时,如果存在缓存项在 readOnlyCacheMap 和 readWriteCacheMap 中不一致,则将 readWriteCacheMap 中的更新到 readOnlyCacheMap
-
readWriteCacheMap:读写缓存在服务端中添加或更新缓存项通过 get 方法,时机是:
- 执行缓存同步定时任务时,如果从 readWriteCacheMap 获取不到相应缓存项,则触发加载方法,调用 generatePayload() 方法进行获取后添加
- 在获取响应缓存中的注册表信息时,如果从 readWriteCacheMap 获取不到相应缓存项,则触发加载方法,调用 generatePayload() 方法进行获取后添加
-
readWriteCacheMap:读写缓存在服务端中清理缓存项通过 invalidate() 方法,时机是:处理客户端注册、下架、更改状态、删除状态
3.3 缓存同步定时任务
// ResponseCacheImpl.class
private TimerTask getCacheUpdateTask() {
return new TimerTask() {
@Override
public void run() {
logger.debug("Updating the client cache from response cache");
// 遍历只读缓存的缓存 key
for (Key key : readOnlyCacheMap.keySet()) {
if (logger.isDebugEnabled()) {
logger.debug("Updating the client cache from response cache for key : {} {} {} {}",
key.getEntityType(), key.getName(), key.getVersion(), key.getType());
}
try {
// 设置版本号
CurrentRequestVersion.set(key.getVersion());
// 根据缓存 key 从读写缓存中取出对应的 value (注册表)
// 如果和只读缓存中的 value 不一致,则将读写缓存的赋值给只读缓存
Value cacheValue = readWriteCacheMap.get(key);
Value currentCacheValue = readOnlyCacheMap.get(key);
if (cacheValue != currentCacheValue) {
readOnlyCacheMap.put(key, cacheValue);
}
} catch (Throwable th) {
logger.error("Error while updating the client cache from response cache for key {}", key.toStringCompact(), th);
}
}
}
};
}
4. 获取注册表
// ResponseCacheImpl.class
private Value generatePayload(Key key) {
Stopwatch tracer = null;
try {
String payload;
switch (key.getEntityType()) {
// 主要关注该类型
case Application:
boolean isRemoteRegionRequested = key.hasRegions();
// ALL_APPS:全量拉取
if (ALL_APPS.equals(key.getName())) {
if (isRemoteRegionRequested) {
tracer = serializeAllAppsWithRemoteRegionTimer.start();
// 需要获取的注册表,包含本地服务端的和客户端请求指定的远程 regions 的
// 4.1 调用 getApplicationsFromMultipleRegions() 方法获取
// getPayLoad():返回结果前,转换格式,默认 json
payload = getPayLoad(key, registry.getApplicationsFromMultipleRegions(key.getRegions()));
} else {
tracer = serializeAllAppsTimer.start();
// 4.2 获取注册表,调用 getApplications() 方法获取
payload = getPayLoad(key, registry.getApplications());
}
// 5 ALL_APPS_DELTA:增量拉取
} else if (ALL_APPS_DELTA.equals(key.getName())) {
if (isRemoteRegionRequested) {
tracer = serializeDeltaAppsWithRemoteRegionTimer.start();
versionDeltaWithRegions.incrementAndGet();
versionDeltaWithRegionsLegacy.incrementAndGet();
// 5.1 调用 getApplicationDeltasFromMultipleRegions() 方法获取
payload = getPayLoad(key,
registry.getApplicationDeltasFromMultipleRegions(key.getRegions()));
} else {
tracer = serializeDeltaAppsTimer.start();
versionDelta.incrementAndGet();
versionDeltaLegacy.incrementAndGet();
// 5.2 获取注册表,调用 getApplicationDeltas() 方法获取
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();
}
}
}
4.1 registry.getApplicationsFromMultipleRegions()
// AbstractInstanceRegistry.class
public Applications getApplicationsFromMultipleRegions(String[] remoteRegions) {
boolean includeRemoteRegion = null != remoteRegions && remoteRegions.length != 0;
logger.debug("Fetching applications registry with remote regions: {}, Regions argument {}",
includeRemoteRegion, remoteRegions);
if (includeRemoteRegion) {
GET_ALL_WITH_REMOTE_REGIONS_CACHE_MISS.increment();
} else {
GET_ALL_CACHE_MISS.increment();
}
// 新建一个注册表对象
// 该对象既是返回给客户端请求的注册表,也是 readWriteCacheMap 只读缓存中的 value
Applications apps = new Applications();
apps.setVersion(1L);
// 遍历服务端本地注册表,将服务实例信息添加到 apps
for (Entry<String, Map<String, Lease<InstanceInfo>>> entry : registry.entrySet()) {
Application app = null;
if (entry.getValue() != null) {
for (Entry<String, Lease<InstanceInfo>> stringLeaseEntry : entry.getValue().entrySet()) {
Lease<InstanceInfo> lease = stringLeaseEntry.getValue();
if (app == null) {
// 从相应实例的租约信息中取出服务名,新建一个服务信息对象 app
// 第一进入当前 for 循环时候触发
app = new Application(lease.getHolder().getAppName());
}
// 相应实例的租约信息转换成实例信息,添加到 app
app.addInstance(decorateInstanceInfo(lease));
}
}
if (app != null) {
// 服务实例信息添加到 apps
apps.addApplication(app);
}
}
if (includeRemoteRegion) {
// 遍历需要获取的远程 regions
for (String remoteRegion : remoteRegions) {
// 获取已经注册到本地的远程 region 信息
RemoteRegionRegistry remoteRegistry = regionNameVSRemoteRegistry.get(remoteRegion);
if (null != remoteRegistry) {
// 获取注册表信息
Applications remoteApps = remoteRegistry.getApplications();
for (Application application : remoteApps.getRegisteredApplications()) {
// 判断是否允许该服务从注册表获取
// 白名单机制
if (shouldFetchFromRemoteRegistry(application.getName(), remoteRegion)) {
logger.info("Application {} fetched from the remote region {}",
application.getName(), remoteRegion);
// 根据实例名从 apps 中获取服务信息
Application appInstanceTillNow = apps.getRegisteredApplications(application.getName());
if (appInstanceTillNow == null) {
// 如果上面处理本地注册表后, apps 没有相应服务信息,而注册到本地的远程 regions 注册表中有
// 则新建一个服务信息添加到 apps
appInstanceTillNow = new Application(application.getName());
apps.addApplication(appInstanceTillNow);
}
for (InstanceInfo instanceInfo : application.getInstances()) {
// 实例信息添加到服务信息
appInstanceTillNow.addInstance(instanceInfo);
}
} else {
logger.debug("Application {} not fetched from the remote region {} as there exists a "
+ "whitelist and this app is not in the whitelist.",
application.getName(), remoteRegion);
}
}
} else {
logger.warn("No remote registry available for the remote region {}", remoteRegion);
}
}
}
apps.setAppsHashCode(apps.getReconcileHashCode());
return apps;
}
4.2 registry.getApplications()
// AbstractInstanceRegistry.class
public Applications getApplications() {
boolean disableTransparentFallback = serverConfig.disableTransparentFallbackToOtherRegion();
if (disableTransparentFallback) {
return getApplicationsFromLocalRegionOnly();
} else {
return getApplicationsFromAllRemoteRegions(); // Behavior of falling back to remote region can be disabled.
}
}
public Applications getApplicationsFromLocalRegionOnly() {
// getApplicationsFromMultipleRegions() 方法上面已分析
// 这里入参 EMPTY_STR_ARRAY 表示只获取本地注册表
return getApplicationsFromMultipleRegions(EMPTY_STR_ARRAY);
}
public Applications getApplicationsFromAllRemoteRegions() {
// getApplicationsFromMultipleRegions() 方法上面已分析
// 这里入参 allKnownRemoteRegions 表示获取服务端本地的注册表和所有注册到本地的远程 region 的注册表
return getApplicationsFromMultipleRegions(allKnownRemoteRegions);
}
5. 处理客户端拉取增量注册表
服务端接收客户端拉取增量注册表的请求在 ApplicationsResource 类中的 getContainers() 方法
// ApplicationsResource.class
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) {
// 代码逻辑只有下面两点和全量的不同,其他基本一样
// ......
if ((serverConfig.shouldDisableDelta()) || (!registry.shouldAllowAccess(isRemoteRegionRequested))) {
// 如果本地服务端配置文件配置了禁止拉取增量注册表或不允许访问注册表
// 则返回403,拒绝请求
return Response.status(Status.FORBIDDEN).build();
}
// ......
// 定义缓存 key
// ResponseCacheImpl.ALL_APPS_DELTA:表示增量拉取
Key cacheKey = new Key(Key.EntityType.Application,
ResponseCacheImpl.ALL_APPS_DELTA,
keyType, CurrentRequestVersion.get(), EurekaAccept.fromString(eurekaAccept), regions
);
// ......
}
5.1 getApplicationDeltasFromMultipleRegions()
// AbstractInstanceRegistry.class
public Applications getApplicationDeltasFromMultipleRegions(String[] remoteRegions) {
if (null == remoteRegions) {
// null 表示需要拉取所有注册到本地的远程 region 注册表
remoteRegions = allKnownRemoteRegions; // null means all remote regions.
}
boolean includeRemoteRegion = remoteRegions.length != 0;
if (includeRemoteRegion) {
GET_ALL_WITH_REMOTE_REGIONS_CACHE_MISS_DELTA.increment();
} else {
GET_ALL_CACHE_MISS_DELTA.increment();
}
// 新建一个注册表对象
Applications apps = new Applications();
apps.setVersion(responseCache.getVersionDeltaWithRegions().get());
// 新建一个服务实例 map
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) {
// applicationInstancesMap 中获取不到服务信息,则新建一个添加进去
// 当服务信息第一次新建时触发
app = new Application(instanceInfo.getAppName());
applicationInstancesMap.put(instanceInfo.getAppName(), app);
// 服务信息添加到 apps
apps.addApplication(app);
}
// 相应实例的租约信息转换成实例信息,添加到 app
app.addInstance(new InstanceInfo(decorateInstanceInfo(lease)));
}
if (includeRemoteRegion) {
// 遍历需要获取的远程 regions
for (String remoteRegion : remoteRegions) {
// 获取已经注册到本地的远程 region 信息
RemoteRegionRegistry remoteRegistry = regionNameVSRemoteRegistry.get(remoteRegion);
if (null != remoteRegistry) {
// 获取注册表增量信息
Applications remoteAppsDelta = remoteRegistry.getApplicationDeltas();
if (null != remoteAppsDelta) {
for (Application application : remoteAppsDelta.getRegisteredApplications()) {
// 判断是否允许该服务从注册表获取
// 白名单机制
if (shouldFetchFromRemoteRegistry(application.getName(), remoteRegion)) {
// 根据实例名从 apps 中获取服务信息
Application appInstanceTillNow =
apps.getRegisteredApplications(application.getName());
if (appInstanceTillNow == null) {
// 如果上面处理本地注册表后, apps 没有相应服务信息,而注册到本地的远程 regions 注册表中有
// 则新建一个服务信息添加到 apps
appInstanceTillNow = new Application(application.getName());
apps.addApplication(appInstanceTillNow);
}
for (InstanceInfo instanceInfo : application.getInstances()) {
// 实例信息添加到服务信息
appInstanceTillNow.addInstance(new InstanceInfo(instanceInfo));
}
}
}
}
}
}
}
// 对本地全量注册表(包含注册到本地的远程 region 的)计算 hashCode 值,返回给客户端
// 客户端在收到服务端返回的注册表更新后,也对自己的注册表计算 hashCode 值
// 如果两个 hashCode 值不一致表示两端注册表不一致,客户端再发起全量拉取注册表请求
Applications allApps = getApplicationsFromMultipleRegions(remoteRegions);
apps.setAppsHashCode(allApps.getReconcileHashCode());
return apps;
} finally {
// 关闭写锁
write.unlock();
}
}
5.2 registry.getApplicationDeltas()
// AbstractInstanceRegistry.class
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) {
// applicationInstancesMap 中获取不到服务信息,则新建一个添加进去
// 当服务信息第一次新建的时触发
app = new Application(instanceInfo.getAppName());
applicationInstancesMap.put(instanceInfo.getAppName(), app);
// 服务信息添加到 apps
apps.addApplication(app);
}
// 相应实例的租约信息转换成实例信息,添加到 app
app.addInstance(new InstanceInfo(decorateInstanceInfo(lease)));
}
boolean disableTransparentFallback = serverConfig.disableTransparentFallbackToOtherRegion();
if (!disableTransparentFallback) {
// 全量获取本地注册表信息,不包含远程 regions 的
Applications allAppsInLocalRegion = getApplications(false);
// 遍历所有注册到本地的远程 regions 信息
for (RemoteRegionRegistry remoteRegistry : this.regionNameVSRemoteRegistry.values()) {
// 获取注册表增量信息
Applications applications = remoteRegistry.getApplicationDeltas();
// 遍历注册表增量信息
for (Application application : applications.getRegisteredApplications()) {
Application appInLocalRegistry =
allAppsInLocalRegion.getRegisteredApplications(application.getName());
if (appInLocalRegistry == null) {
// 如果 注册到本地的远程 region 增量注册表的某个服务实例 在 本地注册表信息 中存在
// 则该服务实例信息添加到 apps
apps.addApplication(application);
}
}
}
}
// 对本地全量注册表(包含注册到本地的远程 region 的)计算 hashCode 值,返回给客户端
// 客户端在收到服务端返回的注册表更新后,也对自己的注册表计算 hashCode 值
// 如果两个 hashCode 值不一致表示两端注册表不一致,客户端再发起全量拉取注册表请求
Applications allApps = getApplications(!disableTransparentFallback);
apps.setAppsHashCode(allApps.getReconcileHashCode());
return apps;
} finally {
// 关闭写锁
write.unlock();
}
}
6. 总结
6.1 主要流程
- 首先,当服务端收到客户端拉取注册表请求时,判断是否允许访问注册表,如果不允许的话返回403拒绝请求,如果允许的话继续处理
- 然后,定义缓存 key ,根据缓存 key 从响应缓存(只读缓存、读写缓存)中获取注册表,压缩后返回给客户端
6.2 获取注册表
-
全量:
- 从本地注册表中获取,如果有需要的话,还要从注册到本地的远程 region 注册表中获取,前后进行合并
-
增量:
- 从本地最近变更队列中获取,如果有需要的话,还要从注册到本地的远程 region 增量注册表中获取,前后进行合并