“不积跬步,无以至千里。”
Eureka client服务注册之后,需要从Server服务器那里拉取最新的注册表信息到本地存储起来,供后续的业务使用,这个服务注册表,我们之前也看过了,是一个ConcurrentHashMap数据结构,Key是服务名称,Value是一个Map,为什么是Map,这个好理解,一个服务,可以部署在多台机器上面,那么就会有多个实例信息,即InstanceInfo,所以Map的Key就是服务实例Id,Value就是服务实例信息,即InstanceInfo,只不过用Lease对象包了一下而已,放在Lease的一个holder对象,这个holder的类型,就是Lease的泛型,即InstanceInfo。
这一篇文章先来看看client端抓取注册表的逻辑,其实看完了eureka源码发现,服务发现这个功能真正复杂的地方是在eureka server端,那里有一套很优秀的设计思路,就是多级缓存机制。
首先要明白一点,eureka client第一次启动的时候,一定是从server端一次性抓取全量的注册表信息,在本地进行缓存,后续会每隔30s从server端抓取增量的注册表信息,跟本地缓存进行合并,当然这个间隔时间都是可以配置的,在application.yml文件中。
作为client端,在启动的时候就会自动抓取全量注册表,
if (clientConfig.shouldFetchRegistry()) {
try {
boolean primaryFetchRegistryResult = fetchRegistry(false);
...
}
}
这个shouldFetchRegistry()默认为ture,说白了,除了单机的eureka server以后,配置的都是ture,然后看看fetchRegistry(false)这个方法,这是去执行拉取注册表,
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.info(PREFIX + "{} - was unable to refresh its cache! This periodic background refresh will be retried in {} seconds. status = {} stacktrace = {}",
appPathIdentifier, clientConfig.getRegistryFetchIntervalSeconds(), e.getMessage(), ExceptionUtils.getStackTrace(e));
return false;
} finally {
if (tracer != null) {
tracer.stop();
}
}
看到这个方法先获取本地的Application缓存,Applications就是代指所有的服务,而一个Application中包含了自己这个服务所有的InstanceInfo信息。
Applications applications = getApplications();
private final AtomicReference<Applications> localRegionApps = new AtomicReference<Applications>();
@Override
public Applications getApplications() {
return localRegionApps.get();
}
然后进行一大串的if判断,是否要进行全量拉取注册表,这里这么搞不是很好,把大量的判断放在if里面,会导致代码不够简洁,可读性、可维护性都很差,一般我们设计业务模块的时候也不会这么写的,如果是我来写这块,会把if里的整个逻辑封装成一个方法,来判断是否全量拉取,shouldGetAllRegistry(),类似的一个方法,会比较好一些。
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);
}
第一次肯定会全量抓取了,clientConfig.getRegistryRefreshSingleVipAddress()这行代码默认是null,因为我们没有配置registryRefreshSingleVipAddress这个值,所以取默认值。
那么就会调用queryClient这个组件的getApplications方法,发送rest请求到server端,然后拿到响应结果。
private EurekaHttpClient queryClient;
private void getAndStoreFullRegistry() throws Throwable {
long currentUpdateGeneration = fetchRegistryGeneration.get();
logger.info("Getting all instance registry info from the eureka server");
Applications apps = null;
EurekaHttpResponse<Applications> httpResponse = clientConfig.getRegistryRefreshSingleVipAddress() == null
? eurekaTransport.queryClient.getApplications(remoteRegionsRef.get())
: eurekaTransport.queryClient.getVip(clientConfig.getRegistryRefreshSingleVipAddress(), remoteRegionsRef.get());
if (httpResponse.getStatusCode() == Status.OK.getStatusCode()) {
apps = httpResponse.getEntity();
}
logger.info("The response status is {}", httpResponse.getStatusCode());
... ...
}
同样的,使用的是jersey2的客户端工具类,

@Override
public EurekaHttpResponse<Applications> getApplications(String... regions) {
return getApplicationsInternal("apps/", regions);
}
调用jersey client,发送http请求(http://localhost:8080/v2/apps),GET请求,调用eureka server的getContainers restful接口,获取全量注册表,缓存在自己的本地。
private EurekaHttpResponse<Applications> getApplicationsInternal(String urlPath, String[] regions) {
Response response = null;
try {
WebTarget webTarget = jerseyClient.target(serviceUrl).path(urlPath);
if (regions != null && regions.length > 0) {
webTarget = webTarget.queryParam("regions", StringUtil.join(regions));
}
Builder requestBuilder = webTarget.request();
addExtraProperties(requestBuilder);
addExtraHeaders(requestBuilder);
response = requestBuilder.accept(MediaType.APPLICATION_JSON_TYPE).get();
Applications applications = null;
if (response.getStatus() == Status.OK.getStatusCode() && response.hasEntity()) {
applications = response.readEntity(Applications.class);
}
return anEurekaHttpResponse(response.getStatus(), applications).headers(headersOf(response)).build();
} finally {
if (logger.isDebugEnabled()) {
logger.debug("Jersey2 HTTP GET {}/{}; statusCode={}", serviceUrl, urlPath, response == null ? "N/A" : response.getStatus());
}
if (response != null) {
response.close();
}
}
}
493

被折叠的 条评论
为什么被折叠?



