Eureka 系列(02)客户端源码分析

Eureka 系列(02)客户端源码分析

[TOC]

在上一篇 Eureka 系列(01)最简使用姿态 中对 Eureka 的简单用法做了一个讲解,本节分析一下 EurekaClient 的实现 DiscoveryClient。本文的源码是基于 Eureka-1.9.8。

1)服务注册(发送注册请求到注册中心)

2)服务发现(本质就是获取调用服务名所对应的服务提供者实例信息,包括IP、port等)

3)服务续约(本质就是发送当前应用的心跳请求到注册中心)

4)服务下线(本质就是发送取消注册的HTTP请求到注册中心)

1. DiscoveryClient 基本功能简介

图1:DiscoveryClient 服务注册与发现时序图

sequenceDiagram
participant DiscoveryClient
participant InstanceInfoReplicator
participant scheduler
participant cacheRefreshExecutor
participant CacheRefreshThread
participant heartbeatExecutor
participant HeartbeatThread
DiscoveryClient ->> scheduler : initScheduledTasks
loop 1)服务注册
DiscoveryClient ->> InstanceInfoReplicator : run() or onDemandUpdate()
InstanceInfoReplicator -->> DiscoveryClient : register
end
loop 2)服务发现
scheduler ->> cacheRefreshExecutor : schedule(CacheRefreshThread)
cacheRefreshExecutor ->> CacheRefreshThread : run
CacheRefreshThread -->> DiscoveryClient : refreshRegistry
end
loop 3)服务续约
scheduler ->> heartbeatExecutor : schedule(HeartbeatThread)
heartbeatExecutor ->> HeartbeatThread : run
HeartbeatThread -->> DiscoveryClient : renew
end
loop 4)服务下线
DiscoveryClient -->> DiscoveryClient : unregister
end

总结: DiscoveryClient 构造时会初始化一个 scheduler 定时任务调度器,两个线程池 heartbeatExecutor 和 cacheRefreshExecutor,分别执行 CacheRefreshThread 和 HeartbeatThread 定时任务,前者定时(默认 30s)从 Eureka Server 更新服务列表,后者定时(默认 30s)上报心跳。

1.1 DiscoveryClient 初始化

DiscoveryClient 初始化最核心就是:一是服务发现定时任务,二是心跳发送定时任务。

DiscoveryClient(ApplicationInfoManager applicationInfoManager, EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args,
                    Provider<BackupRegistry> backupRegistryProvider) {
    // default size of 2 - 1 each for heartbeat and cacheRefresh
    // 1. scheduler 是 CacheRefreshThread 和 HeartbeatThread 任务调度器
    scheduler = Executors.newScheduledThreadPool(2,
                      new ThreadFactoryBuilder()
                          .setNameFormat("DiscoveryClient-%d")
                          .setDaemon(true)
                          .build());

    // 2. 执行 HeartbeatThread 线程池,定时发送心跳
    heartbeatExecutor = new ThreadPoolExecutor(
        1, clientConfig.getHeartbeatExecutorThreadPoolSize(), 0, TimeUnit.SECONDS,
        new SynchronousQueue<Runnable>(),
        new ThreadFactoryBuilder()
        .setNameFormat("DiscoveryClient-HeartbeatExecutor-%d")
        .setDaemon(true)
        .build()
    );  // use direct handoff

    // 3. 执行 CacheRefreshThread 线程池,定时刷新服务列表
    cacheRefreshExecutor = new ThreadPoolExecutor(
        1, clientConfig.getCacheRefreshExecutorThreadPoolSize(), 0, TimeUnit.SECONDS,
        new SynchronousQueue<Runnable>(),
        new ThreadFactoryBuilder()
        .setNameFormat("DiscoveryClient-CacheRefreshExecutor-%d")
        .setDaemon(true)
        .build()
    );  // use direct handoff

    // 4. Eureka Server 服务端,用于 HTTP 通信
    eurekaTransport = new EurekaTransport();
    scheduleServerEndpointTask(eurekaTransport, args);
    ...
    // 5. 启动定时任务
    initScheduledTasks();
}

总结: DiscoveryClient 代码有删减,只保留了最核心的功能,从上面的代码来看还是很简单的。下面再看一下 initScheduledTasks 干了些什么。至于每 4 步装配 Http Client 会在每 5 小章具体讲解。

1.2 initScheduledTasks 启动定时任务

initScheduledTasks 启动了以下几个任务:一每 30s 同步一次服务列表;二每 30s 发送一次心跳信息;三是如果当前 InstanceInfo 发生变更,同步到 Eureka Server,默认 40s。

private void initScheduledTasks() {
    // 1. 定时刷新服务列表,服务发现,默认 30s
    if (clientConfig.shouldFetchRegistry()) {
        // registry cache refresh timer
        int registryFetchIntervalSeconds = clientConfig.getRegistryFetchIntervalSeconds();
        int expBackOffBound = clientConfig.getCacheRefreshExecutorExponentialBackOffBound();
        scheduler.schedule(
            new TimedSupervisorTask(
                "cacheRefresh",
                scheduler,
                cacheRefreshExecutor,
                registryFetchIntervalSeconds,
                TimeUnit.SECONDS,
                expBackOffBound,
                new CacheRefreshThread()
            ),
            registryFetchIntervalSeconds, TimeUnit.SECONDS);
    }

    // 2. 定时发送心跳,默认 30s
    if (clientConfig.shouldRegisterWithEureka()) {
        int renewalIntervalInSecs = instanceInfo.getLeaseInfo().getRenewalIntervalInSecs();
        int expBackOffBound = clientConfig.getHeartbeatExecutorExponentialBackOffBound();
        logger.info("Starting heartbeat executor: " + "renew interval is: {}", renewalIntervalInSecs);

        // Heartbeat timer
        scheduler.schedule(
            new TimedSupervisorTask(
                "heartbeat",
                scheduler,
                heartbeatExecutor,
                renewalIntervalInSecs,
                TimeUnit.SECONDS,
                expBackOffBound,
                new HeartbeatThread()
            ),
            renewalIntervalInSecs, TimeUnit.SECONDS);

        // InstanceInfo replicator
        instanceInfoReplicator = new InstanceInfoReplicator(
            this, instanceInfo,
            clientConfig.getInstanceInfoReplicationIntervalSeconds(),
            2); // burstSize

    // 3. 监听 instance 状态
	statusChangeListener = new ApplicationInfoManager.StatusChangeListener() {
        @Override
        public String getId() {
            return "statusChangeListener";
        }
        @Override
        public void notify(StatusChangeEvent statusChangeEvent) {
            if (InstanceStatus.DOWN == statusChangeEvent.getStatus() ||
                InstanceStatus.DOWN == statusChangeEvent.getPreviousStatus()) {
            }
            instanceInfoReplicator.onDemandUpdate();
        }
    };
	if (clientConfig.shouldOnDemandUpdateStatusChange()) {
        applicationInfoManager.registerStatusChangeListener(statusChangeListener);
    }

    // 4. 定时同步当前 Eureka Client 信息(变更时)给 Eureka Server,默认 40s
	instanceInfoReplicator.start(
        clientConfig.getInitialInstanceInfoReplicationIntervalSeconds());
    }
}

总结: Eureka DiscoveryClient 通过 CacheRefreshThread 和 HeartbeatThread 这两个定时任务保证的服务的有效性。

2. 服务注册与下线

图2:DiscoveryClient 服务注册与下线

sequenceDiagram
participant DiscoveryClient
participant AbstractJerseyEurekaHttpClient
loop 1)服务注册
note left of DiscoveryClient : register 服务注册
DiscoveryClient ->> AbstractJerseyEurekaHttpClient : register(instanceInfo) -> POST:"apps/{appName}"
AbstractJerseyEurekaHttpClient -->> DiscoveryClient : httpResponse.statusCode
end
loop 2)服务下线
note left of DiscoveryClient : unregister 服务下线
DiscoveryClient ->> AbstractJerseyEurekaHttpClient : cancel(appName,id) -> DELETE:"apps/{appName}/{id}"
AbstractJerseyEurekaHttpClient -->> DiscoveryClient : httpResponse.statusCode
end

总结: 服务的注册与下线 OPEN API:

  1. 服务注册(POST):http://{ip}:{port}/eureka/apps/{appName}
  2. 服务下线(DELETE): http://{ip}:{port}/eureka/apps/{appName}/{id}
boolean register() throws Throwable {
    EurekaHttpResponse<Void> httpResponse;
    try {
        httpResponse = eurekaTransport.registrationClient.register(instanceInfo);
    } catch (Exception e) {
        throw e;
    }
    return httpResponse.getStatusCode() == Status.NO_CONTENT.getStatusCode();
}

3. 服务发现

图3:DiscoveryClient 服务发现(默认30s)

graph LR
fetchRegistry -- true --> getAndStoreFullRegistry
getAndStoreFullRegistry -- 全量 --> getApplications
getApplications -- set --> localRegionApps
fetchRegistry -- false --> getAndUpdateDelta
getAndUpdateDelta -- 增量 --> getDelta
getDelta -- 增量 --> updateDelta
updateDelta -- update --> localRegionApps

总结: 服务发现默认每 30s 同步一次数据,更新到本地缓存 localRegionApps 中。数据同步分两种情况:

  1. 全量同步(GET):http://{ip}:{port}/eureka/apps/ ,参数是 regions。这个 API 会获取该 regions 下的全部服务实例 InstanceInfo,如果实例数很多会对网络造成压力,最好是按需要拉取,即 Client 需要订阅那个服务就返回那个服务的实例。

    全量同步很简单,getAndStoreFullRegistry 方法调用上述 API,获取全量的 Applications 数据,直接设置给本地缓存 localRegionApps 即可。

  2. 增量同步(GET):http://{ip}:{port}/eureka/apps/delta ,参数是 regions。返回发生变化的服务实例,eg: ADDED、MODIFIED、DELETED。

    增量同步比全量同步要麻烦一些,getAndUpdateDelta 调用上述 API 返回发生变化的服务实例信息,与本地缓存 localRegionApps 进行对比,更新本地缓存。

思考: 增量同步失败,返回数据为空,或者由于网络等原因导致本地缓存和 Eureka Server 无法通过增量同步保持数据一致性时怎么办?

DiscoveryClient 在进行增量同步时,有对应的补偿机制,当增量同步失败时回滚到全量同步。那如何判断本地缓存和服务端数据不一致呢?Eureka DiscoveryClient 通过计算本地缓存和服务端的 hashcode,如果出现不一致的情况,则同样回滚到全量同步。

 private boolean fetchRegistry(boolean forceFullRegistryFetch) {
     Stopwatch tracer = FETCH_REGISTRY_TIMER.start();
     try {
         Applications applications = getApplications();
         // 1. 全量同步,基本上除了配置选项,第一次同步时全量同步,之后增量同步
         if (clientConfig.shouldDisableDelta()
             || (!Strings.isNullOrEmpty(clientConfig.getRegistryRefreshSingleVipAddress()))
             || forceFullRegistryFetch
             || (applications == null)
             || (applications.getRegisteredApplications().size() == 0)
             || (applications.getVersion() == -1))  {
             getAndStoreFullRegistry();
         // 2. 增量同步
         } else {
             getAndUpdateDelta(applications);
         }
         applications.setAppsHashCode(applications.getReconcileHashCode());
         logTotalInstances();
     } catch (Throwable e) {
         return false;
     } finally {
     }

     // 发布事件CacheRefreshedEvent,同时更新状态
     onCacheRefreshed();
     updateInstanceRemoteStatus();
     return true;
 }

总结: 参数 forceFullRegistryFetch 表示强制全量同步。除了配置选项,基本第一次同步是全量同步,之后都增量同步。全量同步很简单就不看了,看一下增量同步是怎么做的?

private void getAndUpdateDelta(Applications applications) throws Throwable {
    // 1. 通过增量同步,获取改变的服务实例列表
    long currentUpdateGeneration = fetchRegistryGeneration.get();
    Applications delta = null;
    EurekaHttpResponse<Applications> httpResponse = eurekaTransport.queryClient.getDelta(remoteRegionsRef.get());
    if (httpResponse.getStatusCode() == Status.OK.getStatusCode()) {
        delta = httpResponse.getEntity();
    }

    // 2. 增量同步失败,回滚到全量同步
    if (delta == null) {
        getAndStoreFullRegistry();
    // 3. 增量同步,对比本地缓存和delta信息,更新本地缓存
    } else if (fetchRegistryGeneration.compareAndSet(currentUpdateGeneration, currentUpdateGeneration + 1)) {
        String reconcileHashCode = "";
        if (fetchRegistryUpdateLock.tryLock()) {
            try {
    			// 3. 增量同步,对比本地缓存和delta信息,更新本地缓存
                updateDelta(delta);
                reconcileHashCode = getReconcileHashCode(applications);
            } finally {
                fetchRegistryUpdateLock.unlock();
            }
        }
        // 4. 由于未知原因导致实例数不一致(此时hashcode会不一致)
        //    无法通过增量同步,回滚到全量同步
        if (!reconcileHashCode.equals(delta.getAppsHashCode()) || clientConfig.shouldLogDeltaDiff()) {
            reconcileAndLogDifference(delta, reconcileHashCode);  // this makes a remoteCall
        }
    }
}

总结: 增量同步考虑到了增量同步失败或数据出现不一致的情况,进行了补偿。其实这种补偿机制也很简单,以后做设计时可以考虑这种补偿机制,提高代码的健壮性。

4. 心跳检测

图4:DiscoveryClient 心跳检测(默认30s)

graph LR
renew -- 发送心跳包 --> sendHeartBeat
sendHeartBeat -- OK --> 结束
sendHeartBeat -- NOT_FOUND --> register

总结: 健康检测,一般都是 TTL(Time To Live) 机制。eg: 客户端每 5s 发送心跳,服务端 15s 没收到心跳包,更新实例状态为不健康, 30s 未收到心跳包,从服务列表中删除。 Eureka Server 是每 30s 发送心跳包,90s 未收心跳则删除。

boolean renew() {
    EurekaHttpResponse<InstanceInfo> httpResponse;
    try {
        // 1. 发送心跳包
        httpResponse = eurekaTransport.registrationClient.sendHeartBeat(instanceInfo.getAppName(), instanceInfo.getId(), instanceInfo, null);
        // 2. 如果服务端实例不存在,则重新注册实例
        if (httpResponse.getStatusCode() == Status.NOT_FOUND.getStatusCode()) {
            REREGISTER_COUNTER.increment();
            long timestamp = instanceInfo.setIsDirtyWithTime();
            boolean success = register();
            if (success) {
                instanceInfo.unsetIsDirty(timestamp);
            }
            return success;
        }
        return httpResponse.getStatusCode() == Status.OK.getStatusCode();
    } catch (Throwable e) {
        return false;
    }
}

5. 高可用客户端(HA Client)

高可用客户端(HA Client)多用于生产环境,客户端应用关联或配置注册中心服务器集群,避免注册中心单点故障。

常见配置手段:①多注册中心主机;②注册中心 DNS;③广播

如果 Eureka 客户端应用配置多个 Eureka 注册服务器,那么默认情况只有第一台可用的服务器,存在注册信息。如果第一台可用的 Eureka 服务器 Down 掉了,那么 Eureka 客户端应用将会选择下台可用的 Eureka 服务器。

客户端配置如下:

eureka.client.service-url.defaultZone=   http://peer1:10001/eureka,http://peer2:10001/eureka

思考: 那 Eureka Client 到底是访问那台 Eureka Server 呢?如果其中一台 Eureka Server 宕机后怎么处理呢?

5.1 EurekaHttpClient 初始化

DiscoveryClient(ApplicationInfoManager applicationInfoManager,
                EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args,
                Provider<BackupRegistry> backupRegistryProvider) {
    // 4. Eureka Server 服务端,用于 HTTP 通信
    eurekaTransport = new EurekaTransport();
    scheduleServerEndpointTask(eurekaTransport, args);
    ...
}

scheduleServerEndpointTask 最终初始化 EurekaTransport,EurekaTransport 最重要的属性有两个:一是 ClosableResolver,用于 Eureka Server 发现;二是 EurekaHttpClient 用于与 Eureka Server 通信。

private static final class EurekaTransport {
    // Eureka Server 地址发现
    private ClosableResolver bootstrapResolver;
    private TransportClientFactory transportClientFactory;

    // Eureka 注册
    private EurekaHttpClient registrationClient;
    private EurekaHttpClientFactory registrationClientFactory;

    // Eureka 查询
    private EurekaHttpClient queryClient;
    private EurekaHttpClientFactory queryClientFactory;
}

总结: scheduleServerEndpointTask 的方法很长,我们只看最核心的代码,即 bootstrapResolver 和 queryClient 的初始化。

private void scheduleServerEndpointTask(EurekaTransport eurekaTransport,
	AbstractDiscoveryClientOptionalArgs args) {
    // 1. ClusterResolver#getClusterEndpoints 可以获取所的 endpoints
    eurekaTransport.bootstrapResolver = EurekaHttpClients.newBootstrapResolver(
        clientConfig,
        transportConfig,
        eurekaTransport.transportClientFactory,
        applicationInfoManager.getInfo(),
        applicationsSource
    );
    // 2. 初始化 queryClient,默认实现是 RetryableEurekaHttpClient
    //    registrationClient 初始化类似,就省略了
    if (clientConfig.shouldFetchRegistry()) {
        EurekaHttpClientFactory newQueryClientFactory = null;
        EurekaHttpClient newQueryClient = null;
        try {
            newQueryClientFactory = EurekaHttpClients.queryClientFactory(
                eurekaTransport.bootstrapResolver,
                eurekaTransport.transportClientFactory,
                clientConfig,
                transportConfig,
                applicationInfoManager.getInfo(),
                applicationsSource
            );
            newQueryClient = newQueryClientFactory.newClient();
        } catch (Exception e) {
        }
        eurekaTransport.queryClientFactory = newQueryClientFactory;
        eurekaTransport.queryClient = newQueryClient;
    }
    ...
}

总结: scheduleServerEndpointTask 方法是重要的工作:

  1. 一是初始化 ClusterResolver,用于获取所有的 Eureka Server。默认实现是 ConfigClusterResolver,调用 EurekaClientConfig#getEurekaServerServiceUrls() 方法获取配置的 Eureka 地址。
  2. 二是初始化 EurekaHttpClient,用于发送请求。默认实现是 RetryableEurekaHttpClient,这个类会通过轮询的方式 Eureka Server。需要注意的是只有第一台宕机时,才会轮询,否则正常情况下永远只访问第一台。

5.2 EurekaHttpClient 执行流程

EurekaHttpClient 的默认实现是 RetryableEurekaHttpClient,会通过 ConfigClusterResolver 解析获取所有配置的 Eureka ServerUrls,默认只会调用每一台 Eureka Server,只有当第一台宕机时才会调用下一台。 也就是通过 EurekaClientConfig#getEurekaServerServiceUrls 获取 eureka.client.service-url.defaultZone=http://peer1:10001/eureka,http://peer2:10001/eureka 配置的集群地址。

图5:EurekaHttpClient 执行流程

sequenceDiagram
participant RetryableEurekaHttpClient
participant ConfigClusterResolver
participant EndpointUtils
participant EurekaClientConfig
participant TransportClientFactory
participant EurekaHttpClient
note over RetryableEurekaHttpClient : execute
RetryableEurekaHttpClient ->> RetryableEurekaHttpClient : 1.1 getHostCandidates
RetryableEurekaHttpClient ->> ConfigClusterResolver : 1.2 getClusterEndpoints
ConfigClusterResolver ->> EndpointUtils : 1.3 getServiceUrlsMapFromConfig
RetryableEurekaHttpClient ->> TransportClientFactory : 2. 根据server创建EurekaHttpClient:newClient
RetryableEurekaHttpClient ->> EurekaHttpClient : 3. 真正执行:execute

总结: RetryableEurekaHttpClient 通过轮询的方式保证客户端的高可用,主要的执行流程分三步:

  1. 获取所有的 Eureka Server。ConfigClusterResolver 获取所有的地址后,通过轮询算法选择一台 Server。
  2. 根据这个 Server 构建一个真实发送请求的 EurekaHttpClient。
  3. EurekaHttpClient 发送请求,如果失败则重试。
protected <R> EurekaHttpResponse<R> execute(RequestExecutor<R> requestExecutor) {
    List<EurekaEndpoint> candidateHosts = null;
    int endpointIdx = 0;
    for (int retry = 0; retry < numberOfRetries; retry++) {
        EurekaHttpClient currentHttpClient = delegate.get();
        EurekaEndpoint currentEndpoint = null;
        if (currentHttpClient == null) {
            // 获取所有的 Endpoint
            if (candidateHosts == null) {
                candidateHosts = getHostCandidates();
                if (candidateHosts.isEmpty()) {
                    throw new TransportException("There is no known eureka server; cluster server list is empty");
                }
            }
            if (endpointIdx >= candidateHosts.size()) {
                throw new TransportException("Cannot execute request on any known server");
            }
            // 2. 轮询获取 currentEndpoint,注意只有每一台无法访问时才会访问下一台
            //    currentHttpClient 才是真实发送请求的 EurekaHttpClient
            //    在 spring cloud(sc) 中默认的实现是 RestTemplateEurekaHttpClient
            currentEndpoint = candidateHosts.get(endpointIdx++);
            currentHttpClient = clientFactory.newClient(currentEndpoint);
        }

        // 3. 发送请求,成功则返回,失败则是重试
        try {
            EurekaHttpResponse<R> response = requestExecutor.execute(currentHttpClient);
            if (serverStatusEvaluator.accept(response.getStatusCode(), requestExecutor.getRequestType())) {
                delegate.set(currentHttpClient);
                return response;
            }
        } catch (Exception e) {
        }
        delegate.compareAndSet(currentHttpClient, null);
        if (currentEndpoint != null) {
            quarantineSet.add(currentEndpoint);
        }
    }
    throw new TransportException("Retry limit reached; giving up on completing the request");
}

总结: RetryableEurekaHttpClient 通过轮询的方式保证高可用客户端(HA Client)

  1. RetryableEurekaHttpClient 继承关系:RetryableEurekaHttpClient -> EurekaHttpClientDecorator -> EurekaHttpClient。EurekaHttpClientDecorator 只是一个包装类,具体的发送请求过程都委托给子类 RetryableEurekaHttpClient#execute(EurekaHttpClient delegate) 完成。
  2. RetryableEurekaHttpClient 通过轮询的方式获取 currentEndpoint,再通过 clientFactory.newClient(currentEndpoint) 构建一个真正用于发送请求的 EurekaHttpClient,在 Spring Cloud(SC) 中的默认实现是 RestTemplateEurekaHttpClient。

6. 总结

6.1 Eureka OPEN API

表1:Eureka OPEN API

操作 请求方式 路径 参数
注册(register) POST apps/{appName} instanceInfo
下线(unregister) DELETE apps/{appName}/{id} --
全量同步(unregister) GET apps/ regions
增量同步(unregister) GET apps/delta regions
心跳(sendHeartBeat) PUT apps/{appName}/{id} --

6.2 实例注册

Eureka DiscoveryClient 默认延迟 40s 注册实例信息,之后如果实例信息发生变化,则每 30s 同步一次数据。

表2:Eureka 实例注册配置参数

参数 功能 默认值
registerWithEureka 是否将本机实例注册到 Eureka Server 上 true
initialInstanceInfoReplicationIntervalSeconds 初始化注册的延迟时间 40s
instanceInfoReplicationIntervalSeconds 定时更新本机实例信息到 Eureka Server 的时间间隔 30s

6.3 数据同步 - 服务发现

Eureka DiscoveryClient 默认每 30s 同步一次数据,更新本地缓存 localRegionApps 。数据同步分为全量同步和增量同步。

  1. 全量同步:获取该 regions 下的全部服务实例 InstanceInfo,如果实例数很多会对网络造成压力,最好是按需要拉取,即 Client 需要订阅那个服务就返回那个服务的实例。
  2. 增量同步:返回发生变化的服务实例,eg: ADDED、MODIFIED、DELETED。

    增量同步比全量同步要麻烦一些,getAndUpdateDelta 调用上述 API 返回发生变化的服务实例信息,与本地缓存 localRegionApps 进行对比,更新本地缓存。如果增量同步失败回滚到全量同步。

    表3:Eureka 服务发现配置参数

参数 功能 默认值
fetchRegistry 是否从 Eureka Server 获取注册信息 true
registryFetchIntervalSeconds 定时同步本地的服务实例信息缓存的时间间隔 30s

6.4 健康检查 - 心跳机制

Eureka DiscoveryClient 默认每 30s 发送心跳包,Server 如果 90s 未收心跳则删除。

表4:Eureka 心跳机制配置参数

参数 功能 默认值 来源
renewalIntervalInSecs 心跳的时间间隔 30s LeaseInfo
durationInSecs 定时同步本地的服务实例信息缓存的时间间隔 90s LeaseInfo

6.4 思考

  1. 当注册应用之间存在相互关联时,那么上层应用如何感知下层服务的存在?
  2. 如果上层应用感知到下层服务,那么它是怎么同步下层服务信息?
  3. 如果应用需要实时地同步信息,那么确保一致性?


每天用心记录一点点。内容也许不重要,但习惯很重要!

原文地址:https://www.cnblogs.com/binarylei/p/11605734.html

时间: 2024-10-06 11:56:11

Eureka 系列(02)客户端源码分析的相关文章

RMI 系列(02)源码分析

目录 RMI 系列(02)源码分析 1. 架构 2. 服务注册 2.1 服务发布整体流程 2.2 服务暴露入口 exportObject 2.3 生成本地存根 2.4 服务监听 2.5 ObjectTable 注册与查找 2.6 服务绑定 2.7 总结 3. 服务发现 3.1 注册中心 Stub 3.2 普通服务 Stub RMI 系列(02)源码分析 1. 架构 RMI 中有三个重要的角色:注册中心(Registry).客户端(Client).服务端(Server). 图1 RMI 架构图 在

SequoiaDB 系列之七 :源码分析之catalog节点

这一篇紧接着上一篇SequoiaDB 系列之六 :源码分析之coord节点来讲 在上一篇中,分析了coord转发数据包到catalog节点(也有可能是data节点,视情况而定).这一次,我们继续分析上一篇中的rtnCoordCMDListCollectionSpace的消息包被转发到catalog节点上的处理流程. catalog节点的进程,同样sequoiadb进程,只是角色不一样,运行的服务有区别. 这里就不再赘述catalog节点的启动过程. 在SequoiaDB/engine/cat/c

Eoe客户端源码分析---SlidingMenu的使用

Eoe客户端源码分析及代码注释 使用滑动菜单SlidingMenu,单击滑动菜单的不同选项,可以通过ViewPager和PagerIndicator显示对应的数据内容. 0  BaseSlidingFragmentActivity.java 主要函数: (1)showMenu() /** * Opens the menu and shows the menu view.*/ public void showMenu() { showMenu(true); } (2)showContent() /

Java并发系列[2]----AbstractQueuedSynchronizer源码分析之独占模式

在上一篇<Java并发系列[1]----AbstractQueuedSynchronizer源码分析之概要分析>中我们介绍了AbstractQueuedSynchronizer基本的一些概念,主要讲了AQS的排队区是怎样实现的,什么是独占模式和共享模式以及如何理解结点的等待状态.理解并掌握这些内容是后续阅读AQS源码的关键,所以建议读者先看完我的上一篇文章再回过头来看这篇就比较容易理解.在本篇中会介绍在独占模式下结点是怎样进入同步队列排队的,以及离开同步队列之前会进行哪些操作.AQS为在独占模

Java并发系列[5]----ReentrantLock源码分析

在Java5.0之前,协调对共享对象的访问可以使用的机制只有synchronized和volatile.我们知道synchronized关键字实现了内置锁,而volatile关键字保证了多线程的内存可见性.在大多数情况下,这些机制都能很好地完成工作,但却无法实现一些更高级的功能,例如,无法中断一个正在等待获取锁的线程,无法实现限定时间的获取锁机制,无法实现非阻塞结构的加锁规则等.而这些更灵活的加锁机制通常都能够提供更好的活跃性或性能.因此,在Java5.0中增加了一种新的机制:Reentrant

Eureka 客户端源码分析

Eureka作为服务注册中心,主要的功能是服务注册和服务发现,是微服务框架的基础功能和核心功能. Eureka的使用可参考: Eureka服务端:Spring Cloud Eureka Server使用(注册中心), Eureka客户端:Eureka Client的使用, Eureka服务端:Eureka的高可用 Eureka分为客户端和服务端,这里主要介绍客户端源码 1.Eureka客户端主要使用EnableDiscoveryClient注解 1)@EnableDiscoveryClient源

Java集合系列:-----------03ArrayList源码分析

上一章,我们学习了Collection的架构.这一章开始,我们对Collection的具体实现类进行讲解:首先,讲解List,而List中ArrayList又最为常用.因此,本章我们讲解ArrayList.先对ArrayList有个整体认识,再学习它的源码,最后再通过例子来学习如何使用它.内容包括: ArrayList简介 ArrayList 是一个数组队列,相当于 动态数组.与Java中的数组相比,它的容量能动态增长.它继承于AbstractList,实现了List, RandomAccess

Java集合系列之HashMap源码分析

一.HashMap简介 HashMap是基于哈希表的Map接口实现的,它存储的是内容是键值对<key,value>映射.此类不保证映射的顺序,假定哈希函数将元素适当的分布在各桶之间,可为基本操作(get和put)提供稳定的性能. ps:本文中的源码来自jdk1.8.0_45/src. 1.重要参数 HashMap的实例有两个参数影响其性能. 初始容量:哈希表中桶的数量 加载因子:哈希表在其容量自动增加之前可以达到多满的一种尺度 当哈希表中条目数超出了当前容量*加载因子(其实就是HashMap的

开源中国 OsChina Android 客户端源码分析(9)下载APK功能

源码中用以下载客户端的类为DownloadService,是一个服务.如果你对android服务不够理解的话,建议先查阅下有关服务的知识点.源码分析如下: 1首先我们先来看下该服务中几个重写的方法: 1.1onCreate()中 首先声明了自定义的绑定器对象,并在自定义的绑定器中添加了几个界面可以访问服务的方法,我们发现在这几个方法中,目前实际用到的start()方法用以开始下载APK,其他的没有用到.获取通知管理器.设置服务为 非前台服务.代码注释中,火蚁表明了不确定性. 其实如果将服务设置为