欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 在文章《Spring Cloud源码分析之Eureka篇第四章:服务注册是如何发起的 》的分析中,我们
欢迎访问我的GitHub
这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos
概览
- 以下图片来自Netflix官方,图中显示Eureka Client会发起Renew向注册中心做周期性续约,这样其他Eureka client通过Get Registry请求就能获取到新注册应用的相关信息:
关于源码版本
- 本次分析的Spring Cloud版本为Edgware.RELEASE,对应的eureka-client版本为1.7.0;
来自官方文档的指导信息
- 最准确的说明信息来自Netflix的官方文档,地址:https://github.com/Netflix/eureka/wiki/Understanding-eureka-client-server-communication#renew
- 学习源码之前先看文档可以确定大方向,不会因为陷入源码细节导致偏离学习目标,如下图所示:
- 我的理解:
- Eureka client每隔三十秒发送一次心跳到Eureka server,这就是续约;
- Eureka client续约的目的是告诉Eureka server自己还活着;
- Eureka server若90秒内未收到心跳,就从自己的服务列表中剔除该Eureka client;
- 建议不要改变心跳间隔,因为Eureka server是通过心跳来判断Eureka client是否正常;
源码分析
- 首先回顾com.netflix.discovery.DiscoveryClient类的initScheduledTasks方法,Eureka client在启动的时侯都会执行此方法,如下方所示,已经略去了周期性更新服务列表相关的代码:
private void initScheduledTasks() {
//略去获取服务列表相关代码
...
if (clientConfig.shouldRegisterWithEureka()) {
//心跳间隔
int renewalIntervalInSecs = instanceInfo.getLeaseInfo().getRenewalIntervalInSecs();
//周期性任务执行超时的时候,会将超时时间翻倍,但不会超过最大时间,这个最大时间是由expBackOffBound决定的
int expBackOffBound = clientConfig.getHeartbeatExecutorExponentialBackOffBound();
logger.info("Starting heartbeat executor: " + "renew interval is: " + renewalIntervalInSecs);
//启动心跳的周期性任务
scheduler.schedule(
new TimedSupervisorTask(
"heartbeat",
scheduler,
heartbeatExecutor,
renewalIntervalInSecs,
TimeUnit.SECONDS,
expBackOffBound,
new HeartbeatThread()
),
renewalIntervalInSecs, TimeUnit.SECONDS);
//略去服务注册相关代码
...
/**
* Renew with the eureka service by making the appropriate REST call
*/
boolean renew() {
EurekaHttpResponse<InstanceInfo> httpResponse;
try {
//发Restful请求,即心跳
httpResponse = eurekaTransport.registrationClient.sendHeartBeat(instanceInfo.getAppName(), instanceInfo.getId(), instanceInfo, null);
logger.debug("{} - Heartbeat status: {}", PREFIX + appPathIdentifier, httpResponse.getStatusCode());
//404错误会触发注册逻辑
if (httpResponse.getStatusCode() == 404) {
REREGISTER_COUNTER.increment();
logger.info("{} - Re-registering apps/{}", PREFIX + appPathIdentifier, instanceInfo.getAppName());
return register();
}
//返回码200表示心跳成功
return httpResponse.getStatusCode() == 200;
} catch (Throwable e) {
logger.error("{} - was unable to send heartbeat!", PREFIX + appPathIdentifier, e);
return false;
}
}
- 继续展开上面代码段中的eurekaTransport.registrationClient.sendHeartBeat方法,源码在EurekaHttpClientDecorator类中:
@Override
public EurekaHttpResponse<InstanceInfo> sendHeartBeat(final String appName,
final String id,
final InstanceInfo info,
final InstanceStatus overriddenStatus) {
return execute(new RequestExecutor<InstanceInfo>() {
@Override
public EurekaHttpResponse<InstanceInfo> execute(EurekaHttpClient delegate) {
//网络处理委托给代理类完成
return delegate.sendHeartBeat(appName, id, info, overriddenStatus);
}
@Override
public RequestType getRequestType() {
//请求类型为心跳
return RequestType.SendHeartBeat;
}
});
}
- 继续展开delegate.sendHeartBeat,多层调用一路展开,最终由JerseyApplicationClient类来完成操作,对应源码在父类AbstractJerseyEurekaHttpClient中,如下所示,主要工作是利用jersey库的Restful Api将自身的信息PUT到Eureka server,注意:这里不是POST,也不是GET,而是PUT:
@Override
public EurekaHttpResponse<InstanceInfo> sendHeartBeat(String appName, String id, InstanceInfo info, InstanceStatus overriddenStatus) {
String urlPath = "apps/" + appName + '/' + id;
ClientResponse response = null;
try {
//请求参数有两个:Eureka client自身状态、自身关键信息(状态、元数据等)最后一次变化的时间
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);
//注意:这里不是POST,也不是GET,而是PUT
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();
}
}
}
- 至此,Eureka client向服务续租的源码就分析完毕了,过程相对简单,DiscoveryClient、TimedSupervisorTask、JerseyApplicationClient等实例各司其职,定时发送PUT请求到Eureka server;
欢迎关注51CTO博客:程序员欣宸
学习路上,你不孤单,欣宸原创一路相伴...
【转自:
香港高防 http://www.558idc.com/stgf.html转载请说明出处】