这里就不再赘述Client端是如何一步步跟进到发请求的代码的,因为之前通过单元测试代码已经搞清楚了Server端接受请求的类是ApplicationsResource.java, Client端主要核心的代码也在 DiscoveryClient.java中。
代码还是之前看了好多遍的祖传代码,只是省略了很多内容,只展示我们需要分析的地方。
clientConfig.shouldFetchRegistry() 这个配置默认是true,然后fetchRegistry方法中getAndStoreFullRegistry(),因为第一次都是获取全量注册表信息,继续往后。
getAndStoreFullRegistry 方法中可以看到就是发送Http请求给Server端,然后等待Server端返回全量注册表信息。
这里获取全量请求执行的是eurekaTransport.queryClient.getApplications(remoteRegionsRef.get())
然后再一路往下,跟踪到 AbstractJersey2EurekaHttpClient.java中,getApplicationsInternal方法,发下发送的是GET请求,于是到Server端ApplicationsResource.java中的GET方法getContainers中查看逻辑
server端返回注册表信息集合的多级缓存机制上面已经看了Client端 发送抓取全量注册表的逻辑,到了Server端查看ApplicationsResource.java中的GET方法getContainers,接着看看这部分的源码
private final ResponseCache responseCache; @GET 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) { // 省略部分代码 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)) { 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; }这里接收到Client端的请求后,会去responseCache 中去拿去全量的数据信息。
从属性名字就可以看出来,这个是从缓存中获取数据。
ResponseCacheImpl.java
String get(final Key key, boolean useReadOnlyCache) { Value payload = getValue(key, useReadOnlyCache); if (payload == null || payload.getPayload().equals(EMPTY_PAYLOAD)) { return null; } else { return payload.getPayload(); } } 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; }这里主要关注getValue方法,这里主要有两个map,一个是readOnlyCacheMap 另一个是readWriteCacheMap, 这里我们光看名字就可以知道一个是只读缓存,一个是读写缓存,这里用了两层的缓存结构,如果只读缓存不为空 则直接返回,如果为空查询可读缓存。
关于缓存的讲解 我们继续往下看。
server端注册表多级缓存过期机制:主动+定时+被动继续看缓存相关,用到了多级缓存这里可能就会存在一些疑问:
两级缓存数据如何保存同步?
缓存数据如何过期?
带着疑问我们来继续看源代码
private final ConcurrentMap<Key, Value> readOnlyCacheMap = new ConcurrentHashMap<Key, Value>(); private final LoadingCache<Key, Value> readWriteCacheMap; ResponseCacheImpl(EurekaServerConfig serverConfig, ServerCodecs serverCodecs, AbstractInstanceRegistry registry) { // 省略部分代码 long responseCacheUpdateIntervalMs = serverConfig.getResponseCacheUpdateIntervalMs(); 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; } }); // 省略部分代码 }