Spring Cloud學習(二):Spring Cloud Eureka

1 Eureka簡介

Eureka是Spring Cloud體系中用於服務註冊與發現的組件。主要解決子項目之間的通訊問題。

1.1 主要角色

                                                                 Eureka體系調用關係圖

註冊中心(Eureka Server):主要用於服務註冊和發現的註冊中心。

  1. 失效剔除:將超出設置時間沒有收到心跳續約的服務從註冊服務列表中剔除。
  2. 自我保護:根據心跳續約失敗率在規定時間內是否低於85%的統計信息,將註冊服務實例信息保護起來。

 

服務提供者(Service Provider):向註冊中心提供服務的客戶端(如圖Eureka Client A)。

  1. 服務註冊:服務提供者啓動時,通過API調用的方式向註冊中心註冊自己的服務信息,其中包括IP、端口、調用路由等信息。
  2. 服務續約:服務註冊完畢過後,通過心跳機制向服務器更新狀態,保持註冊信息不被剔除。
  3. 服務取消(取消租約):服務提供者關閉時,通過API調用的方式向註冊中心註銷自己的服務。
  4. 獲取服務:服務提供者在註冊自己的同時也會從註冊中心獲取一份服務註冊列表信息。

 

服務消費者(Service Consumer):從註冊中心獲取服務的客戶端(如圖Eureka Client B、C)。

  1. 獲取服務:服務消費者啓動過後也會向註冊中心註冊自己,同時從中獲取最新的服務註冊列表信息。
  2. 服務調用:獲取到服務註冊信息後,通過服務註冊元數據(IP,PORT,URL等)採用HttpClient的方式調用原服務。

 

其中服務提供者可以有多個,Eureka Client自身通過負載均衡Ribbon和熔斷機制Hystrix進行具體的調用。

 

1.2 Region和Zone

region是區域,屬於地理層面上的分區。例如遊戲服務分區的華北區、華東區、華中區、中國區等等。

zone是可用區,屬於物理層面上的分區,比如一個機房屬於一個可用區。一個region裏面可以有一個或多個zone可用區。每個zone裏面可以有一臺或多臺服務器。

 

spring cloud eureka中相同的服務優先調用同一個zone中的,當同一個zone中的服務調用失敗時才調用同一個region中其他zone中的同一個服務。這樣可以在網絡延時方面節約調用時間。

 

Eureka配置文件示例:

spring:
  application:
    name: Server-1
server:
  port: 12000
eureka:
  instance:
    prefer-ip-address: true
    hostname: localhost
  client:
    #是否向服務器註冊自己
register-with-eureka: true
#默認爲true,通過registry-fetch-interval-seconds=30配置向服務器拉取服務註冊列表
    fetch-registry: true
    prefer-same-zone-eureka: true
    #地區
region: sichuan
#可用區
    availability-zones:
      sichuan: chengdu,leshan
    service-url:
      chengdu: http://localhost:12000/eureka/
      leshan: http://localhost:12001/eureka/ 

Client配置文件示例:

spring:
  application:
    name: service
server:
  port: 12003
eureka:
  instance:
    prefer-ip-address: true
    #指定zone
metadata-map:
      zone: chengdu
  client:
    register-with-eureka: true
    fetch-registry: true
    prefer-same-zone-eureka: true
    #地區
    region: sichuan
    availability-zones:
      sichuan: chengdu,leshan
    service-url:
      chengdu: http://localhost:12000/eureka/
      leshan: http://localhost:12001/eureka/

 

1.3 啓動分析

打開Eureka客戶端啓動類註解@EnableDiscoveryClient類,通過註釋“Annotation to enable a DiscoveryClient implementation.”可知道此註解啓動了DiscoveryClient的實現類,直接全局搜索得到如下類圖。

 

 

2 服務註冊

2.1 客戶端

通過上圖,全局搜索找到實現類DiscoveryClient.class。通過瀏覽此類的構造函數,找到如下代碼:

@Inject
DiscoveryClient(ApplicationInfoManager applicationInfoManager, EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args,
                    Provider<BackupRegistry> backupRegistryProvider) {
        //......省略
       
        initScheduledTasks();

        //......省略
}

private void initScheduledTasks() {
    //......省略
    if (clientConfig.shouldRegisterWithEureka()) {
        //......省略
        // InstanceInfo replicator
        instanceInfoReplicator = new InstanceInfoReplicator(
                this,
               instanceInfo,
                clientConfig.getInstanceInfoReplicationIntervalSeconds(),
                2); // burstSize
        //......省略
        instanceInfoReplicator.start(clientConfig.getInitialInstanceInfoReplicationIntervalSeconds());
    } else {
        logger.info("Not registering with Eureka server per configuration");
    }
}

 

由代碼可知, initScheduledTasks();方法中啓動了三個任務,分別是服務獲取,服務續約和服務註冊,如上所示,instanceInfoReplicator就是負責服務註冊的線程,其run方法如下:

public void run() {
    try {
        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);
    }
}

 

其中調用註冊方法register()。源碼如下:

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;
}

由源碼可知客戶端註冊是採用的http rest請求的方式註冊自己的。通過debug源碼,是通過com.netflix.discovery.shared.transport.jersey.AbstractJerseyEurekaHttpClient類的register()方法發起的rest調用,調用地址爲:http://localhost:8001/eureka/apps/API-BASE,8001端口部署的是eureka服務器項目,com.netflix.appinfo.InstanceInfo是客戶端註冊時發送給服務端的元數據,截圖如下:

由上圖可知,客戶端項目名稱爲API-BASE,啓動端口是8003,
http://192.168.6.29:8003/actuator/health爲檢查客戶端狀態的調用地址,返回結果:{"status":"UP"}
http://192.168.6.29:8003/actuator/info是查看客戶端詳情的調用地址。

 

源碼如下:

@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();
            }
        }
    }

經過上述步驟,客戶端將自己註冊到eureka服務端。

 

2.2 服務端

經過2.2我們知道客戶端是如何向服務端註冊自己的。接下來分析下服務端如何接收客戶端註冊請求然後完成服務註冊的。

服務端接收客戶端註冊請求的方法是:

com.netflix.eureka.resources.ApplicationResource#addInstance

源碼如下:

@POST
    @Consumes({"application/json", "application/xml"})
    public Response addInstance(InstanceInfo info,
                                @HeaderParam(PeerEurekaNode.HEADER_REPLICATION) String isReplication) {
        logger.debug("Registering instance {} (replication={})", info.getId(), isReplication);
        // 驗證instanceinfo包含所有必需的字段
        if (isBlank(info.getId())) {
            return Response.status(400).entity("Missing instanceId").build();
        } else if (isBlank(info.getHostName())) {
            return Response.status(400).entity("Missing hostname").build();
        } else if (isBlank(info.getIPAddr())) {
            return Response.status(400).entity("Missing ip address").build();
        } else if (isBlank(info.getAppName())) {
            return Response.status(400).entity("Missing appName").build();
        } else if (!appName.equals(info.getAppName())) {
            return Response.status(400).entity("Mismatched appName, expecting " + appName + " but was " + info.getAppName()).build();
        } else if (info.getDataCenterInfo() == null) {
            return Response.status(400).entity("Missing dataCenterInfo").build();
        } else if (info.getDataCenterInfo().getName() == null) {
            return Response.status(400).entity("Missing dataCenterInfo Name").build();
        }

        // …省略

		//調用註冊服務
        registry.register(info, "true".equals(isReplication));
        return Response.status(204).build();  // 204 to be backwards compatible
    }

服務端實際處理註冊服務的方法是:

com.netflix.eureka.registry.AbstractInstanceRegistry#register

通過分析源碼,eureka服務端是通過一個

private final ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>> registry = new ConcurrentHashMap();

對象中,它是一個兩層Map結構,第一層的key存儲服務名:InstanceInfo中的appName屬性,第二層的key存儲實例名:InstanceInfo中的instanceId屬性。

源碼如下:

public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) {
        try {
            this.read.lock();
            Map<String, Lease<InstanceInfo>> gMap = (Map)this.registry.get(registrant.getAppName());
            EurekaMonitors.REGISTER.increment(isReplication);
            if (gMap == null) {
                ConcurrentHashMap<String, Lease<InstanceInfo>> gNewMap = new ConcurrentHashMap();
                gMap = (Map)this.registry.putIfAbsent(registrant.getAppName(), gNewMap);
                if (gMap == null) {
                    gMap = gNewMap;
                }
            }

            Lease<InstanceInfo> existingLease = (Lease)((Map)gMap).get(registrant.getId());
            if (existingLease != null && existingLease.getHolder() != null) {
                Long existingLastDirtyTimestamp = ((InstanceInfo)existingLease.getHolder()).getLastDirtyTimestamp();
                Long registrationLastDirtyTimestamp = registrant.getLastDirtyTimestamp();
                logger.debug("Existing lease found (existing={}, provided={}", existingLastDirtyTimestamp, registrationLastDirtyTimestamp);
                if (existingLastDirtyTimestamp > registrationLastDirtyTimestamp) {
                    logger.warn("There is an existing lease and the existing lease's dirty timestamp {} is greater than the one that is being registered {}", existingLastDirtyTimestamp, registrationLastDirtyTimestamp);
                    logger.warn("Using the existing instanceInfo instead of the new instanceInfo as the registrant");
                    registrant = (InstanceInfo)existingLease.getHolder();
                }
            } else {
                synchronized(this.lock) {
                    if (this.expectedNumberOfRenewsPerMin > 0) {
                        this.expectedNumberOfRenewsPerMin += 2;
                        this.numberOfRenewsPerMinThreshold = (int)((double)this.expectedNumberOfRenewsPerMin * this.serverConfig.getRenewalPercentThreshold());
                    }
                }

                logger.debug("No previous lease information found; it is new registration");
            }

            Lease<InstanceInfo> lease = new Lease(registrant, leaseDuration);
            if (existingLease != null) {
                lease.setServiceUpTimestamp(existingLease.getServiceUpTimestamp());
            }

            ((Map)gMap).put(registrant.getId(), lease);
            synchronized(this.recentRegisteredQueue) {
                this.recentRegisteredQueue.add(new Pair(System.currentTimeMillis(), registrant.getAppName() + "(" + registrant.getId() + ")"));
            }

            if (!InstanceStatus.UNKNOWN.equals(registrant.getOverriddenStatus())) {
                logger.debug("Found overridden status {} for instance {}. Checking to see if needs to be add to the overrides", registrant.getOverriddenStatus(), registrant.getId());
                if (!this.overriddenInstanceStatusMap.containsKey(registrant.getId())) {
                    logger.info("Not found overridden id {} and hence adding it", registrant.getId());
                    this.overriddenInstanceStatusMap.put(registrant.getId(), registrant.getOverriddenStatus());
                }
            }

            InstanceStatus overriddenStatusFromMap = (InstanceStatus)this.overriddenInstanceStatusMap.get(registrant.getId());
            if (overriddenStatusFromMap != null) {
                logger.info("Storing overridden status {} from map", overriddenStatusFromMap);
                registrant.setOverriddenStatus(overriddenStatusFromMap);
            }

            InstanceStatus overriddenInstanceStatus = this.getOverriddenInstanceStatus(registrant, existingLease, isReplication);
            registrant.setStatusWithoutDirty(overriddenInstanceStatus);
            if (InstanceStatus.UP.equals(registrant.getStatus())) {
                lease.serviceUp();
            }

            registrant.setActionType(ActionType.ADDED);
            this.recentlyChangedQueue.add(new AbstractInstanceRegistry.RecentlyChangedItem(lease));
            registrant.setLastUpdatedTimestamp();
            this.invalidateCache(registrant.getAppName(), registrant.getVIPAddress(), registrant.getSecureVipAddress());
            logger.info("Registered instance {}/{} with status {} (replication={})", new Object[]{registrant.getAppName(), registrant.getId(), registrant.getStatus(), isReplication});
        } finally {
            this.read.unlock();
        }

    }

從這裏也可以看出,客戶端向eureka服務器註冊服務等操作都是採用restfull請求方式。

3 查詢服務列表

3.1 客戶端

通過服務註冊服務我們知道,在initScheduledTasks()方法中共啓動了三個任務,其中有兩個定時任務分別爲:cacheRefresh scheduler服務獲取和heartbeat scheduler租期續約。

源碼如下:   

 private void initScheduledTasks() {
        if (clientConfig.shouldFetchRegistry()) {
            //...省略
			//cacheRefresh timer 服務獲取定時器
            scheduler.schedule(
                    new TimedSupervisorTask(
                            "cacheRefresh",
                            scheduler,
                            cacheRefreshExecutor,
                            registryFetchIntervalSeconds,
                            TimeUnit.SECONDS,
                            expBackOffBound,
                            new CacheRefreshThread()
                    ),
                    registryFetchIntervalSeconds, TimeUnit.SECONDS);
        }

        if (clientConfig.shouldRegisterWithEureka()) {
            //...省略
            // Heartbeat timer 服務續約定時器
            scheduler.schedule(
                    new TimedSupervisorTask(
                            "heartbeat",
                            scheduler,
                            heartbeatExecutor,
                            renewalIntervalInSecs,
                            TimeUnit.SECONDS,
                            expBackOffBound,
                            new HeartbeatThread()
                    ),
                    renewalIntervalInSecs, TimeUnit.SECONDS);

            //...省略
			//註冊服務線程
            instanceInfoReplicator.start(clientConfig.getInitialInstanceInfoReplicationIntervalSeconds());
        } else {
            logger.info("Not registering with Eureka server per configuration");
        }
}

兩個定時器中分別執行了CacheRefreshThreadHeartbeatThread線程。

先看看CacheRefreshThread的run方法: 

/**
     * The task that fetches the registry information at specified intervals.
     *
     */
    class CacheRefreshThread implements Runnable {
        public void run() {
            refreshRegistry();
        }
}
@VisibleForTesting
    void refreshRegistry() {
        try {
            //...省略
            //獲取服務信息
            boolean success = fetchRegistry(remoteRegionsModified);
            //...省略
        } catch (Throwable e) {
           logger.error("Cannot fetch registry from server", e);
        }
    }

 

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:封裝由eureka服務器返回的所有註冊表信息的類。
            Applications applications = getApplications();

            if (clientConfig.shouldDisableDelta()
                    || (!Strings.isNullOrEmpty(clientConfig.getRegistryRefreshSingleVipAddress()))
                    || forceFullRegistryFetch
                    || (applications == null)
                    || (applications.getRegisteredApplications().size() == 0)
                    || (applications.getVersion() == -1)) 
					//主要就是判斷是不是第一次獲取服務
            {
                //...省略
                getAndStoreFullRegistry();
            } else {
                getAndUpdateDelta(applications);
            }
            //...省略
        } catch (Throwable e) {
            //...省略
        } finally {
            if (tracer != null) {
                tracer.stop();
            }
        }

        // 在更新實例遠程狀態之前通知緩存刷新
        onCacheRefreshed();

        // 根據緩存中刷新的數據更新遠程狀態
        updateInstanceRemoteStatus();

        // 服務獲取成功
        return true;
    }

        

                 

//第一次獲取服務時,從eureka服務器獲取完整的註冊表信息,並將其存儲在本地
	private void getAndStoreFullRegistry() throws Throwable {
        long currentUpdateGeneration = fetchRegistryGeneration.get();

        logger.info("Getting all instance registry info from the eureka server");

        Applications apps = null;
		//通過rest方式調用http://localhost:8001/eureka/apps/  獲取所有的服務註冊信息
		//clientConfig.getRegistryRefreshSingleVipAddress()指示客戶端是否只獲取單個VIP的註冊表信息。返回VIP的地址信息
		//如果設置了vip地址信息,則通過rest方式調用http://localhost:8001/eureka/vips/  獲取單個VIP的註冊表信息
        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());

        if (apps == null) {
            logger.error("The application is null for some reason. Not storing this information");
        } else if (fetchRegistryGeneration.compareAndSet(currentUpdateGeneration, currentUpdateGeneration + 1)) {
            localRegionApps.set(this.filterAndShuffle(apps));
            logger.debug("Got full registry with apps hashcode {}", apps.getAppsHashCode());
        } else {
            logger.warn("Not updating applications as another thread is updating it already");
        }
    }

        

//如果不是第一次服務獲取,從eureka服務器獲取增加的服務註冊信息,並將其存儲在本地
	private void getAndUpdateDelta(Applications applications) throws Throwable {
        long currentUpdateGeneration = fetchRegistryGeneration.get();

        Applications delta = null;
		//通過rest方式調用http://localhost:8001/eureka/apps/delta 獲取新增加的服務註冊信息
        EurekaHttpResponse<Applications> httpResponse = eurekaTransport.queryClient.getDelta(remoteRegionsRef.get());
        if (httpResponse.getStatusCode() == Status.OK.getStatusCode()) {
            delta = httpResponse.getEntity();
        }

        if (delta == null) {
            logger.warn("The server does not allow the delta revision to be applied because it is not safe. "
                    + "Hence got the full registry.");
			//如果爲空,全量獲取
            getAndStoreFullRegistry();
        } else if (fetchRegistryGeneration.compareAndSet(currentUpdateGeneration, currentUpdateGeneration + 1)) {
            logger.debug("Got delta update with apps hashcode {}", delta.getAppsHashCode());
            String reconcileHashCode = "";
            if (fetchRegistryUpdateLock.tryLock()) {
                try {
				   //更新本地緩存
                    updateDelta(delta);
                    reconcileHashCode = getReconcileHashCode(applications);
                } finally {
                    fetchRegistryUpdateLock.unlock();
                }
            } else {
                logger.warn("Cannot acquire update lock, aborting getAndUpdateDelta");
            }
            // There is a diff in number of instances for some reason
            if (!reconcileHashCode.equals(delta.getAppsHashCode()) || clientConfig.shouldLogDeltaDiff()) {
                reconcileAndLogDifference(delta, reconcileHashCode);  // this makes a remoteCall
            }
        } else {
            logger.warn("Not updating application delta as another thread is updating it already");
            logger.debug("Ignoring delta update with apps hashcode {}, as another thread is updating it already", delta.getAppsHashCode());
        }
    }

         以上就是客戶端服務獲取的所有過程,以及重點源碼。

3.2 服務端

通過查看eureka服務端源碼可知,eureka client查詢全量服務註冊信息調用的接口是:

com.netflix.eureka.resources.ApplicationsResource#getApplicationResource

源碼如下:   

@Path("{appId}")
    public ApplicationResource getApplicationResource(
            @PathParam("version") String version,
            @PathParam("appId") String appId) {
        CurrentRequestVersion.set(Version.toEnum(version));
                  //返回一個Application信息
        return new ApplicationResource(appId, serverConfig, registry);
}

此處從version=“eureka”,appId=“API-BASE”(項目名稱)

 

eureka client查詢增量服務註冊信息的接口是:

com.netflix.eureka.resources.ApplicationsResource#getContainerDifferential

源碼如下:

 @Path("delta")
    @GET
    public Response getContainerDifferential(
            @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) {

        boolean isRemoteRegionRequested = null != regionsStr && !regionsStr.isEmpty();

        // If the delta flag is disabled in discovery or if the lease expiration
        // has been disabled, redirect clients to get all instances
        if ((serverConfig.shouldDisableDelta()) || (!registry.shouldAllowAccess(isRemoteRegionRequested))) {
            return Response.status(Status.FORBIDDEN).build();
        }

       //...省略

        Key cacheKey = new Key(Key.EntityType.Application,
                ResponseCacheImpl.ALL_APPS_DELTA,
                keyType, CurrentRequestVersion.get(), EurekaAccept.fromString(eurekaAccept), regions
        );
		//獲取服務註冊的緩存信息。
        if (acceptEncoding != null
                && acceptEncoding.contains(HEADER_GZIP_VALUE)) {
            return Response.ok(responseCache.getGZIP(cacheKey))
                    .header(HEADER_CONTENT_ENCODING, HEADER_GZIP_VALUE)
                    .header(HEADER_CONTENT_TYPE, returnMediaType)
                    .build();
        } else {
            return Response.ok(responseCache.get(cacheKey))
                    .build();
        }
    }

4 服務租約續期

4.1 客戶端

由獲取服務註冊信息源碼分析中,我們知道在initScheduledTasks()任務中還執行了一個HeartbeatThread()線程,此線程主要職責就是向服務器中發送心跳續約數據,刷新自己的服務註冊信息,以防止被eureka server剔除掉。

HeartbeatThread的run方法如下:   

/**
     * 在給定間隔內更新租約的heartbeat任務
     */
    private class HeartbeatThread implements Runnable {
        public void run() {
            if (renew()) {
                lastSuccessfulHeartbeatTimestamp = System.currentTimeMillis();
            }
        }
}

 

具體的執行調用的方法爲:

com.netflix.discovery.shared.transport.jersey.AbstractJerseyEurekaHttpClient#sendHeartBeat

源碼如下:   

public EurekaHttpResponse<InstanceInfo> sendHeartBeat(String appName, String id, InstanceInfo info, InstanceStatus overriddenStatus) {
        String urlPath = "apps/" + appName + '/' + id;
//調用地址爲:
//http://localhost:8001/eureka/apps/API-BASE/192.168.6.36:8003?status=UP&lastDirtyTimestamp=1557558926742
        ClientResponse response = null;
        try {
            WebResource webResource = jerseyClient.resource(serviceUrl)
                    .path(urlPath)
                    .queryParam("status", info.getStatus().toString())
                    .queryParam("lastDirtyTimestamp", info.getLastDirtyTimestamp().toString());
            if (overriddenStatus != null) {
                webResource = webResource.queryParam("overriddenstatus", overriddenStatus.name());
            }
            Builder requestBuilder = webResource.getRequestBuilder();
            addExtraHeaders(requestBuilder);
            response = requestBuilder.put(ClientResponse.class);
            EurekaHttpResponseBuilder<InstanceInfo> eurekaResponseBuilder = anEurekaHttpResponse(response.getStatus(), InstanceInfo.class).headers(headersOf(response));
            if (response.hasEntity()) {
                eurekaResponseBuilder.entity(response.getEntity(InstanceInfo.class));
            }
            return eurekaResponseBuilder.build();
        } finally {
            if (logger.isDebugEnabled()) {
                logger.debug("Jersey HTTP PUT {}/{}; statusCode={}", serviceUrl, urlPath, response == null ? "N/A" : response.getStatus());
            }
            if (response != null) {
                response.close();
            }
        }
    }

 

4.2 服務端

eureka服務端處理租約續期的方法爲:

com.netflix.eureka.registry.AbstractInstanceRegistry#renew

源碼如下:   

/**
     *將給定應用程序名稱的給定實例標記爲已更新,並標記它是否源自複製
     *@see com.netflix.eureka.lease.LeaseManager#renew(java.lang.String, java.lang.String, boolean)
     */
    public boolean renew(String appName, String id, boolean isReplication) {
        RENEW.increment(isReplication);
        Map<String, Lease<InstanceInfo>> gMap = registry.get(appName);
        Lease<InstanceInfo> leaseToRenew = null;
        if (gMap != null) {
            leaseToRenew = gMap.get(id);
        }
        if (leaseToRenew == null) {
            RENEW_NOT_FOUND.increment(isReplication);
            logger.warn("DS: Registry: lease doesn't exist, registering resource: {} - {}", appName, id);
            return false;
        } else {
            InstanceInfo instanceInfo = leaseToRenew.getHolder();
            if (instanceInfo != null) {
                // touchASGCache(instanceInfo.getASGName());
                InstanceStatus overriddenInstanceStatus = this.getOverriddenInstanceStatus(
                        instanceInfo, leaseToRenew, isReplication);
                if (overriddenInstanceStatus == InstanceStatus.UNKNOWN) {
                    logger.info("Instance status UNKNOWN possibly due to deleted override for instance {}"
                            + "; re-register required", instanceInfo.getId());
                    RENEW_NOT_FOUND.increment(isReplication);
                    return false;
                }
                if (!instanceInfo.getStatus().equals(overriddenInstanceStatus)) {
                    logger.info(
                            "The instance status {} is different from overridden instance status {} for instance {}. "
                                    + "Hence setting the status to overridden status", instanceInfo.getStatus().name(),
                                    instanceInfo.getOverriddenStatus().name(),
                                    instanceInfo.getId());
                    instanceInfo.setStatusWithoutDirty(overriddenInstanceStatus);

                }
            }
            renewsLastMin.increment();
            leaseToRenew.renew();
            return true;
        }
}

5 取消服務租約

5.1 客戶端

取消租約的方法爲:

com.netflix.discovery.DiscoveryClient#unregister

源碼如下:   

    /**
     *取消租約
     */
    void unregister() {
        // It can be null if shouldRegisterWithEureka == false
        if(eurekaTransport != null && eurekaTransport.registrationClient != null) {
            try {
                logger.info("Unregistering ...");
                EurekaHttpResponse<Void> httpResponse = eurekaTransport.registrationClient.cancel(instanceInfo.getAppName(), instanceInfo.getId());
                logger.info(PREFIX + "{} - deregister  status: {}", appPathIdentifier, httpResponse.getStatusCode());
            } catch (Exception e) {
                logger.error(PREFIX + "{} - de-registration failed{}", appPathIdentifier, e.getMessage(), e);
            }
        }
}

5.2 服務端

服務端處理取消租約的方法爲:

com.netflix.eureka.resources.InstanceResource#cancelLease

源碼如下:        

/**
     *處理此特定實例的租約取消。
     *
     * @param isReplication
     *            a header parameter containing information whether this is
     *            replicated from other nodes.
     * @return response indicating whether the operation was a success or
     *         failure.
     */
    @DELETE
    public Response cancelLease(
            @HeaderParam(PeerEurekaNode.HEADER_REPLICATION) String isReplication) {
        try {
            boolean isSuccess = registry.cancel(app.getName(), id,
                "true".equals(isReplication));
			//...省略
        } catch (Throwable e) {
            logger.error("Error (cancel): {} - {}", app.getName(), id, e);
            return Response.serverError().build();
        }

    }

 

特別感謝:程序員DD

參考文章:http://blog.didispace.com/springcloud-sourcecode-eureka/

歡迎加入【緣·樂山】571814743,交流學習經驗,技術分享,創業孵化,軟件開發

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章