Netflix Eureka - Eureka Client 服务注册

252 阅读3分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

Eureka Client 服务注册

InstanceInfoReplicator

InstanceInfoReplicator(服务实例信息复制组件):负责服务的注册

instanceInfoReplicator.start(
        // 40秒
        clientConfig.getInitialInstanceInfoReplicationIntervalSeconds());

初始化

InstanceInfoReplicator.start()方法将自己作为一个线程放到一个调度线程池中,将isDirty设置为了ture。默认延迟40s执行。

/**
 * 1. 设置dirty标志
 * 2. 加入调度任务
 * @param initialDelayMs
 */
public void start(int initialDelayMs) {
    if (started.compareAndSet(false, true)) {
        instanceInfo.setIsDirty();  // for initial register
        Future next = scheduler.schedule(this, initialDelayMs, TimeUnit.SECONDS);
        scheduledPeriodicRef.set(next);
    }
}

执行注册逻辑

InstanceInfoReplicator.run() 方法重载Runnable接口方法。由调度线程执行服务注册逻辑。

/**
 * 服务实例信息复制器线程逻辑
 */
@Override
public void run() {
    try {
        // 刷新instanceInfo(数据中心、租约、服务实例状态)
        discoveryClient.refreshInstanceInfo();

        // 向注册中心注册服务
        Long dirtyTimestamp = instanceInfo.isDirtyWithTime();
        if (dirtyTimestamp != null) {
            discoveryClient.register();
            instanceInfo.unsetIsDirty(dirtyTimestamp);
        }
    } catch (Throwable t) {
        logger.warn("There was a problem with the instance info replicator", t);
    } finally {
        Future next = scheduler.schedule(this, replicationIntervalSeconds, TimeUnit.SECONDS);
        scheduledPeriodicRef.set(next);
    }
}

刷新服务实例信息

EurekaClient.refreshInstanceInfo()调用ApplicationInfoManager的方法刷新服务实例信息。

/**
 * Refresh the current local instanceInfo. Note that after a valid refresh where changes are
 * observed, the
 * isDirty flag on the instanceInfo is set to true
 */
void refreshInstanceInfo() {
    // hostname、ipAddress如果发生变化的话就刷新一下
    applicationInfoManager.refreshDataCenterInfoIfRequired();
    // 租约信息如果发生变化的话就刷新一下
    applicationInfoManager.refreshLeaseInfoIfRequired();

    InstanceStatus status;
    try {
        // 通过健康检查处理器(HealthCheckHandler)获取InstanceStatus并设置到ApplicationInfoManager中
        status = getHealthCheckHandler().getStatus(instanceInfo.getStatus());
    } catch (Exception e) {
        logger.warn("Exception from healthcheckHandler.getStatus, setting status to DOWN", e);
        status = InstanceStatus.DOWN;
    }

    if (null != status) {
        applicationInfoManager.setInstanceStatus(status);
    }
}

数据中心信息刷新

hostname、ipAddress如果发生变化的话就刷新一下;

租约信息刷新

租约信息如果发生变化的话就刷新一下;

实例状态信息设置

通过健康检查处理器(HealthCheckHandler)获取InstanceStatus并设置到ApplicationInfoManager中;

向注册中心注册服务

EurekaClient.reigster()

通过EurekaClient的reigster()方法注册服务。

底层调用的是 eurekaTransport.registrationClient.register(instanceInfo)方法,将InstanceInfo服务实例的信息通过HTTP请求,发送到eureka server进行注册。

/**
 * Register with the eureka service by making the appropriate REST call.
 */
boolean register() throws Throwable {
    logger.info(PREFIX + appPathIdentifier + ": registering service...");
    EurekaHttpResponse<Void> httpResponse;
    try {
        httpResponse = eurekaTransport.registrationClient.register(instanceInfo);
    } catch (Exception e) {
        logger.warn("{} - registration failed {}", PREFIX + appPathIdentifier, e.getMessage()
                , e);
        throw e;
    }
    if (logger.isInfoEnabled()) {
        logger.info("{} - registration status: {}", PREFIX + appPathIdentifier,
                httpResponse.getStatusCode());
    }
    return httpResponse.getStatusCode() == 204;
}

eurekaTransport.registrationClient.register() 最终调用的是JerseyApplicationClient.register();

真正执行注册请求的是AbstractJerseyEurekaHttpClient的register方法,请求http://localhost:8080/v2/apps/ServiceA 将服务实例的信息发送过去

@Override
public EurekaHttpResponse<Void> register(InstanceInfo info) {
    String urlPath = "apps/" + info.getAppName();
    ClientResponse response = null;
    try {
        Builder resourceBuilder = jerseyClient.resource(serviceUrl).path(urlPath).getRequestBuilder();
        addExtraHeaders(resourceBuilder);
        response = resourceBuilder
                .header("Accept-Encoding", "gzip")
                .type(MediaType.APPLICATION_JSON_TYPE)
                .accept(MediaType.APPLICATION_JSON)
                .post(ClientResponse.class, info);
        return anEurekaHttpResponse(response.getStatus()).headers(headersOf(response)).build();
    } finally {
        if (logger.isDebugEnabled()) {
            logger.debug("Jersey HTTP POST {}/{} with instance {}; statusCode={}", serviceUrl, urlPath, info.getId(),
                    response == null ? "N/A" : response.getStatus());
        }
        if (response != null) {
            response.close();
        }
    }
}

重置dirty标志

EurekaTransport 网络组件

  • TransportClientFactory transportClientFactory:网络组件客户端工厂实例

  • EurekaHttpClientFactory registrationClientFactory:通过transportClientFactory获取的用于获取注册客户端的工厂实例,

  • EurekaHttpClient registrationClient:通过registrationClientFactory工厂获得的用于注册服务的Http客户端

初始化网络组件

scheduleServerEndpointTask(eurekaTransport, args = null)

TransportClientFactories transportClientFactories = new Jersey1TransportClientFactories()
TransportClientFactory jerseyFactory:获取 jerseyFactory,生产JerseyApplicationClient。
TransportClientFactory metricsFactory:获取 metricsFactory,装饰 jerseyFactory。
RedirectingEurekaHttpClient:获取 RedirectingEurekaHttpClient ,装饰 metricsFactory
RetryableEurekaHttpClient:获取 RetryableEurekaHttpClient ,装饰 RedirectingEurekaHttpClient
SessionedEurekaHttpClient:获取 SessionedEurekaHttpClient ,装饰 RetryableEurekaHttpClient

eurekaTransport.transportClientFactory = transportClientFactories.newTransportClientFactory()

public TransportClientFactory newTransportClientFactory(final EurekaClientConfig clientConfig,
                                                               final Collection<ClientFilter> additionalFilters,
                                                               final InstanceInfo myInstanceInfo) {
    // 获取 jerseyFactory,生产JerseyApplicationClient。
    // JerseyApplicationClient:负责发送http请求到eureka server的网络客户端
    final TransportClientFactory jerseyFactory = JerseyEurekaHttpClientFactory.create(
            clientConfig,
            additionalFilters,
            myInstanceInfo,
            new EurekaClientIdentity(myInstanceInfo.getIPAddr())
    );

    // 获取 metricsFactory,装饰 jerseyFactory。
    // MetricsCollectingEurekaHttpClient:为 JerseyApplicationClient 增加面板信息统计的能力
    final TransportClientFactory metricsFactory = MetricsCollectingEurekaHttpClient.createFactory(jerseyFactory);

    return new TransportClientFactory() {
        @Override
        public EurekaHttpClient newClient(EurekaEndpoint serviceUrl) {
            return metricsFactory.newClient(serviceUrl);
        }

        @Override
        public void shutdown() {
            metricsFactory.shutdown();
            jerseyFactory.shutdown();
        }
    };
}
public static EurekaHttpClientFactory registrationClientFactory(ClusterResolver bootstrapResolver,
                                                                // metricsFactory
                                                                TransportClientFactory transportClientFactory,
                                                                EurekaTransportConfig transportConfig) {
    return canonicalClientFactory(
            EurekaClientNames.REGISTRATION,
            transportConfig,
            bootstrapResolver,
            // metricsFactory
            transportClientFactory);
}

static EurekaHttpClientFactory canonicalClientFactory(final String name,
                                                      final EurekaTransportConfig transportConfig,
                                                      final ClusterResolver<EurekaEndpoint> clusterResolver,
                                                      // metricsFactory
                                                      final TransportClientFactory transportClientFactory) {

    return new EurekaHttpClientFactory() {
        @Override
        public EurekaHttpClient newClient() {
            // 获取 SessionedEurekaHttpClient ,装饰 RetryableEurekaHttpClient
            return new SessionedEurekaHttpClient(
                    name,
                    // 获取 RetryableEurekaHttpClient ,装饰 RedirectingEurekaHttpClient
                    RetryableEurekaHttpClient.createFactory(
                            name,
                            transportConfig,
                            clusterResolver,
                            // 获取 RedirectingEurekaHttpClient ,装饰 metricsFactory
                            RedirectingEurekaHttpClient.createFactory(
                                    // metricsFactory
                                    transportClientFactory),
                            ServerStatusEvaluators.legacyEvaluator()),
                    transportConfig.getSessionedClientReconnectIntervalSeconds() * 1000
            );
        }

        @Override
        public void shutdown() {
            wrapClosable(clusterResolver).shutdown();
        }
    };
}
eurekaTransport.registrationClientFactory =
        EurekaHttpClients.registrationClientFactory(
                eurekaTransport.bootstrapResolver,
                // metricsFactory
                eurekaTransport.transportClientFactory,
                transportConfig);
eurekaTransport.registrationClient = eurekaTransport.registrationClientFactory.newClient();

EurekaHttpClient

EurekaHttpClient 使用装饰者模式装饰各个子类:

SessionedEurekaHttpClient:负责RetryEurekaHttpClient的超时重建

RetryEurekaHttpClient:负责请求失败重试

RedirectingEurekaHttpClient:负责死循环重定向或者超次数重定向断开处理

MetricsCollectingEurekaHttpClient:负责进行面板信息统计处理

JerseyApplicationClient:负责发送http请求到eureka server

eureka client核心机制:

  • eureka client的服务注册,是在InstanceInfoReplicator中的
  • 实际发送服务注册请求的是 AbstractJerseyEurekaHttpClient,调用了一个restful接口