Eureka源码深度刨析-(4)EurekaClient服务发现

“不积跬步,无以至千里。”

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的客户端工具类,
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();
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值