前面章节介绍了 Eureka Client 的启动过程,从 EnableDiscoveryClient 的注释中我们可以看到,它最终主要是用来开启 com.netflix.discovery.DiscoveryClient 的实例。源码如下:
/** * Annotation to enable a DiscoveryClient implementation. * @author Spencer Gibb */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @Import(EnableDiscoveryClientImportSelector.class) public @interface EnableDiscoveryClient { //... }
从 DiscoveryClient 类的注释可以看出,该类包含服务注册、服务续约、服务下线、获取服务等功能,下面将逐一进行源码分析。
服务提供者在启动时会将自己的信息(比如IP地址、端口,运行状况指示符URL,主页等)注册到 Eureka Server,Eureka Server 收到信息后,会将数据信息存储在一个双层结构的 Map 中,其中第一层的key是服务名,第二层的key是具体服务的实例名。
下面代码通过发送 HTTP REST 注册请求到注册中心注册服务,源码如下:
/** * Register with the eureka service by making the appropriate REST call. */ boolean register() throws Throwable { logger.info(PREFIX + "{}: registering service...", appPathIdentifier); EurekaHttpResponse<Void> httpResponse; try { // 主要的注册功能,实现在 RestTemplateEurekaHttpClient.register() httpResponse = eurekaTransport.registrationClient.register(instanceInfo); } catch (Exception e) { logger.warn(PREFIX + "{} - registration failed {}", appPathIdentifier, e.getMessage(), e); throw e; } if (logger.isInfoEnabled()) { logger.info(PREFIX + "{} - registration status: {}", appPathIdentifier, httpResponse.getStatusCode()); } return httpResponse.getStatusCode() == Status.NO_CONTENT.getStatusCode(); }
跟进进入 RestTemplateEurekaHttpClient.register() 方法,源码如下:
@Override public EurekaHttpResponse<Void> register(InstanceInfo info) { String urlPath = serviceUrl + "apps/" + info.getAppName(); // 构造 HTTP 请求头 HttpHeaders headers = new HttpHeaders(); headers.add(HttpHeaders.ACCEPT_ENCODING, "gzip"); headers.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE); // 发起 HTTP 请求 ResponseEntity<Void> response = restTemplate.exchange(urlPath, HttpMethod.POST, new HttpEntity<>(info, headers), Void.class); // 返回响应数据 return anEurekaHttpResponse(response.getStatusCodeValue()).headers(headersOf(response)).build(); }
在注册完成后,Eureka 客户会每隔 30 秒发送一次心跳来续约。通过续约来告知 Eureka Server 该 Eureka 客户仍然存在,没有出现问题。正常情况下,如果 Eureka Server 在 90 秒没有收到 Eureka 客户的续约,它会将实例从其注册表中删除。建议不要更改续约间隔。
下面代码通过发送 HTTP REST 请求到注册中心进行服务续约,源码如下:
/** * Renew with the eureka service by making the appropriate REST call */ boolean renew() { EurekaHttpResponse<InstanceInfo> httpResponse; try { // 发送心跳请求 // 实现为 RestTemplateEurekaHttpClient.sendHeartBeat() httpResponse = eurekaTransport.registrationClient.sendHeartBeat(instanceInfo.getAppName(), instanceInfo.getId(), instanceInfo, null); logger.debug(PREFIX + "{} - Heartbeat status: {}", appPathIdentifier, httpResponse.getStatusCode()); // 发送心跳请求 if (httpResponse.getStatusCode() == Status.NOT_FOUND.getStatusCode()) { REREGISTER_COUNTER.increment(); logger.info(PREFIX + "{} - Re-registering apps/{}", appPathIdentifier, instanceInfo.getAppName()); long timestamp = instanceInfo.setIsDirtyWithTime(); // 注册服务 boolean success = register(); if (success) { instanceInfo.unsetIsDirty(timestamp); } return success; } return httpResponse.getStatusCode() == Status.OK.getStatusCode(); } catch (Throwable e) { logger.error(PREFIX + "{} - was unable to send heartbeat!", appPathIdentifier, e); return false; } }
RestTemplateEurekaHttpClient.sendHeartBeat() 的代码如下:
@Override public EurekaHttpResponse<InstanceInfo> sendHeartBeat(String appName, String id, InstanceInfo info, InstanceStatus overriddenStatus) { String urlPath = serviceUrl + "apps/" + appName + '/' + id + "?status=" + info.getStatus().toString() + "&lastDirtyTimestamp=" + info.getLastDirtyTimestamp().toString() + (overriddenStatus != null ? "&overriddenstatus=" + overriddenStatus.name() : ""); // 发起HTTP请求 ResponseEntity<InstanceInfo> response = restTemplate.exchange(urlPath, HttpMethod.PUT, null, InstanceInfo.class); // 解析响应 EurekaHttpResponseBuilder<InstanceInfo> eurekaResponseBuilder = anEurekaHttpResponse( response.getStatusCodeValue(), InstanceInfo.class).headers(headersOf(response)); if (response.hasBody()) { eurekaResponseBuilder.entity(response.getBody()); } return eurekaResponseBuilder.build(); }
服务调用本质就是获取调用服务名所对应的服务提供者实例信息,包括IP、端口等。源代码如下:
@Override public List<InstanceInfo> getInstancesByVipAddress(String vipAddress, boolean secure) { return getInstancesByVipAddress(vipAddress, secure, instanceRegionChecker.getLocalRegion()); } @Override public List<InstanceInfo> getInstancesByVipAddress(String vipAddress, boolean secure, @Nullable String region) { if (vipAddress == null) { throw new IllegalArgumentException( "Supplied VIP Address cannot be null"); } Applications applications; // 判断服务提供方是否当前region,若是的话直接从localRegionApps中获取 if (instanceRegionChecker.isLocalRegion(region)) { applications = this.localRegionApps.get(); } else { // 否则的话从远程region获取 applications = remoteRegionVsApps.get(region); if (null == applications) { logger.debug("No applications are defined for region {}, so returning an empty instance list for vip " + "address {}.", region, vipAddress); return Collections.emptyList(); } } // 从applications中获取服务名称对应的实例名称列表 if (!secure) { return applications.getInstancesByVirtualHostName(vipAddress); } else { return applications.getInstancesBySecureVirtualHostName(vipAddress); } }
当服务实例正常关闭时,它会发送一个服务下线的消息给注册中心,注册中心收到信息后,会将该服务实例状态置为下线,并把该信息传播出去。该下线请求不会自动完成,它需要调用以下代码:
DiscoveryManager.getInstance().shutdownComponent()
DiscoveryClient 类通过 unregister() 方法实现服务下线,源码如下:
/** * unregister w/ the eureka service. */ void unregister() { // It can be null if shouldRegisterWithEureka == false if(eurekaTransport != null && eurekaTransport.registrationClient != null) { try { logger.info("Unregistering ..."); // 发起 HTTP REST 请求,实现服务下线 // 具体实现见 RestTemplateEurekaHttpClient.cancel() 方法 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); } } }
RestTemplateEurekaHttpClient.cancel() 源码如下:
@Override public EurekaHttpResponse<Void> cancel(String appName, String id) { String urlPath = serviceUrl + "apps/" + appName + '/' + id; // 发起 HTTP 请求 ResponseEntity<Void> response = restTemplate.exchange(urlPath, HttpMethod.DELETE, null, Void.class); return anEurekaHttpResponse(response.getStatusCodeValue()).headers(headersOf(response)).build(); }
通过以上分析可知,服务的注册、下线等操作实际上就是通过发送 HTTP 请求到注册中心来实现的。那么这些操作的执行时机是什么时候呢?是什么时候服务注册操作会被调用?下线操作是如何被触发的?请阅读下一篇文章……