SpringCloud源码学习笔记之Eureka客户端——服务注册

1、服务注册入口

  在《Eureka客户端——初始化》一篇中,我们知道,在DiscoveryClient对象的构造函数中的initScheduledTasks()方法中,实现了服务注册。具体实现如下:

// 创建注册线程,主要用于服务注册和节点间的数据同步
instanceInfoReplicator = new InstanceInfoReplicator(
        this,
        instanceInfo,
        clientConfig.getInstanceInfoReplicationIntervalSeconds(),
        2); // burstSize
//创建状态监听器,维护响应状态
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()) {
            // log at warn level if DOWN was involved
            logger.warn("Saw local status change event {}", statusChangeEvent);
        } else {
            logger.info("Saw local status change event {}", statusChangeEvent);
        }
        instanceInfoReplicator.onDemandUpdate();
    }
};
//注册监听器到applicationInfoManager实例对象(默认注册,即判断条件默认为true)
if (clientConfig.shouldOnDemandUpdateStatusChange()) {
    applicationInfoManager.registerStatusChangeListener(statusChangeListener);
}

//启用服务注册线程
instanceInfoReplicator.start(clientConfig.getInitialInstanceInfoReplicationIntervalSeconds());

  在上述服务注册的实现主要是通过InstanceInfoReplicator类实现。首先,创建instanceInfoReplicator 实例对象(实现了Runnable接口的类的实例),然后创建了statusChangeListener监听器,用来监听服务实例的状态变化,如果发生变化将调用instanceInfoReplicator.onDemandUpdate()方法,之后,判断当前配置项onDemandUpdateStatusChange是否等于true(默认即为true),如果是,则会把上面创建的StatusChangeListener监听器,通过applicationInfoManager对象的registerStatusChangeListener()方法进行注册,即启用监听器,最后,通过调用instanceInfoReplicator的start()方法,启动服务注册的线程。

2、InstanceInfoReplicator类

  InstanceInfoReplicator类是实现服务注册的关键所在,该类实现了Runnable接口,代表了一个实现服务注册的任务。该任务有以下特征:

  1. 单线程,保证服务注册的顺序性
  2. 通过onDemandUpdate()方法实现了定期按需进行服务注册
  3. 服务注册频率,受限于burstSize参数
  4. 新的计划任务会弃用原来的计划任务。
2.1、构造函数

  通过前面了解,我们知道服务注册时通过InstanceInfoReplicator类实现,首先就是创建一个InstanceInfoReplicator 对象,而InstanceInfoReplicator类的构造函数实现如下:

class InstanceInfoReplicator implements Runnable {

	//省略……

	InstanceInfoReplicator(DiscoveryClient discoveryClient, InstanceInfo instanceInfo, int replicationIntervalSeconds, int burstSize) {
		//用来与Eureka服务交互的组件
	   this.discoveryClient = discoveryClient;
	   //当前需要注册的服务实例
	   this.instanceInfo = instanceInfo;
	   //初始化了一个单线程的调度器,保证服务注册的顺序性
	   this.scheduler = Executors.newScheduledThreadPool(1,
	           new ThreadFactoryBuilder()
	                   .setNameFormat("DiscoveryClient-InstanceInfoReplicator-%d")
	                   .setDaemon(true)
	                   .build());
		//保存最新的一次异步调用结果的处理结果
	   this.scheduledPeriodicRef = new AtomicReference<Future>();
		//当前状态
	   this.started = new AtomicBoolean(false);
	   this.rateLimiter = new RateLimiter(TimeUnit.MINUTES);
	   //更新实例信息到Eureka服务的间隔时间,单位为秒,默认30s
	   this.replicationIntervalSeconds = replicationIntervalSeconds;
	   //任务处理速度控制,默认值2
	   this.burstSize = burstSize;
		//计算每分钟可以处理次数
	   this.allowedRatePerMinute = 60 * this.burstSize / this.replicationIntervalSeconds;
	   logger.info("InstanceInfoReplicator onDemand update allowed rate per min is {}", allowedRatePerMinute);
	}
	//省略……
}
2.2、onDemandUpdate()方法

  该方法在InstanceInfoReplicator 类中实现,主要是在实现了ApplicationInfoManager.StatusChangeListener监听器接口的匿名类中使用,当发送服务实例状态变化是会调用该方法。

public boolean onDemandUpdate() {
    if (rateLimiter.acquire(burstSize, allowedRatePerMinute)) {//通过令牌同算法控制处理速度
        if (!scheduler.isShutdown()) {//当前调度器没有被关闭时,重新提交一个新的任务
            scheduler.submit(new Runnable() {
                @Override
                public void run() {
                    logger.debug("Executing on-demand update of local InstanceInfo");

                    Future latestPeriodic = scheduledPeriodicRef.get();
                    if (latestPeriodic != null && !latestPeriodic.isDone()) {//如果没有处理完成,就取消处理
                        logger.debug("Canceling the latest scheduled update, it will be rescheduled at the end of on demand update");
                        latestPeriodic.cancel(false);
                    }
					//然后重新启动一个新的线程进行运行
                    InstanceInfoReplicator.this.run();
                }
            });
            return true;
        } else {
            logger.warn("Ignoring onDemand update due to stopped scheduler");
            return false;
        }
    } else {
        logger.warn("Ignoring onDemand update due to rate limiter");
        return false;
    }
}
2.3、start()方法

  该方法是启动线程的方法,并通过调度器执行任务,任务对象就是当前对象,实际会执行该实例的run()方法。

public void start(int initialDelayMs) {
   if (started.compareAndSet(false, true)) {//设置当前启动状态为true
        instanceInfo.setIsDirty();  // for initial register
        //把当前对象作为任务,进行调度
        Future next = scheduler.schedule(this, initialDelayMs, TimeUnit.SECONDS);
        //并把结果存储到变量scheduledPeriodicRef中
        scheduledPeriodicRef.set(next);
    }
}
2.4、run()方法

  该方法就是服务注册任务实际执行的方法,首先,调用discoveryClient.refreshInstanceInfo()方法刷新本地实例,然后调用discoveryClient.register()方法进行服务实例注册,最后在finally 代码块中实现下一次服务实例信息更新的调用,进而实现循环执行。

public void run() {
    try {
        discoveryClient.refreshInstanceInfo();
		//根据dirtyTimestamp 判断服务实例是否发生了变化,有变化,则进行重写注册
        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);
    }
}

3、DiscoveryClient的refreshInstanceInfo()方法

  该方法主要用来刷新服务实例的状态信息。
  首先,调用applicationInfoManager.refreshDataCenterInfoIfRequired()判断服务实例的hostname信息是否发生变化,如果变化,修改服务实例的值,并在下次心跳时传递到Eureka服务;
  然后,调用applicationInfoManager.refreshLeaseInfoIfRequired()方法,刷新租约信息;
  然后,使用HealthCheckHandler对象,获取当前服务实例的状态,如果获取抛出异常,则设置为InstanceStatus.DOWN;
  最后,把当前状态,通过applicationInfoManager.setInstanceStatus()方法,设置到当前服务实例中。

void refreshInstanceInfo() {
    applicationInfoManager.refreshDataCenterInfoIfRequired();
    applicationInfoManager.refreshLeaseInfoIfRequired();

    InstanceStatus status;
    try {
        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);
    }
}
3.1、refreshDataCenterInfoIfRequired()方法

  该方法主要用来刷新hostName相关信息。通过判断服务实例的hostname信息是否发生变化,如果变化,修改服务实例的值,并在下次心跳时传递到Eureka服务;

public void refreshDataCenterInfoIfRequired() {
	//获取服务实例当前的hostName
    String existingAddress = instanceInfo.getHostName();

	//处理AmazonInfo相关
    String existingSpotInstanceAction = null;
    if (instanceInfo.getDataCenterInfo() instanceof AmazonInfo) {
        existingSpotInstanceAction = ((AmazonInfo) instanceInfo.getDataCenterInfo()).get(AmazonInfo.MetaDataKey.spotInstanceAction);
    }
	//获取新的hostName
    String newAddress;
    if (config instanceof RefreshableInstanceConfig) {
        // Refresh data center info, and return up to date address
        newAddress = ((RefreshableInstanceConfig) config).resolveDefaultAddress(true);
    } else {
        newAddress = config.getHostName(true);
    }
    //获取IP
    String newIp = config.getIpAddress();
	//如果发生了变化,则调用updateInstanceInfo()方法进行更新
    if (newAddress != null && !newAddress.equals(existingAddress)) {
        logger.warn("The address changed from : {} => {}", existingAddress, newAddress);
        updateInstanceInfo(newAddress, newIp);
    }
	//处理AmazonInfo相关
    if (config.getDataCenterInfo() instanceof AmazonInfo) {
        String newSpotInstanceAction = ((AmazonInfo) config.getDataCenterInfo()).get(AmazonInfo.MetaDataKey.spotInstanceAction);
        if (newSpotInstanceAction != null && !newSpotInstanceAction.equals(existingSpotInstanceAction)) {
            logger.info(String.format("The spot instance termination action changed from: %s => %s",
                    existingSpotInstanceAction,
                    newSpotInstanceAction));
            updateInstanceInfo(null , null );
        }
    }        
}

  通过updateInstanceInfo()方法主要实现设置服务实例的Ip和hostname相关信息。

private void updateInstanceInfo(String newAddress, String newIp) {
      InstanceInfo.Builder builder = new InstanceInfo.Builder(instanceInfo);
    if (newAddress != null) {
        builder.setHostName(newAddress);
    }
    if (newIp != null) {
        builder.setIPAddr(newIp);
    }
    builder.setDataCenterInfo(config.getDataCenterInfo());
    instanceInfo.setIsDirty();
}
3.2、refreshLeaseInfoIfRequired()方法

  该方法主要用来刷新租约。

public void refreshLeaseInfoIfRequired() {
	//当前服务实例的租约信息
     LeaseInfo leaseInfo = instanceInfo.getLeaseInfo();
     if (leaseInfo == null) {
         return;
     }
     int currentLeaseDuration = config.getLeaseExpirationDurationInSeconds();
     int currentLeaseRenewal = config.getLeaseRenewalIntervalInSeconds();
     if (leaseInfo.getDurationInSecs() != currentLeaseDuration || leaseInfo.getRenewalIntervalInSecs() != currentLeaseRenewal) {
     	//构建新的租约信息
         LeaseInfo newLeaseInfo = LeaseInfo.Builder.newBuilder()
                 .setRenewalIntervalInSecs(currentLeaseRenewal)
                 .setDurationInSecs(currentLeaseDuration)
                 .build();
         instanceInfo.setLeaseInfo(newLeaseInfo);
         instanceInfo.setIsDirty();
     }
 }
3.3、setInstanceStatus()方法

  主要用来设置服务实例状态,并通知服务实例状态监听器。

public synchronized void setInstanceStatus(InstanceStatus status) {
    InstanceStatus next = instanceStatusMapper.map(status);
    if (next == null) {
        return;
    }
	
    InstanceStatus prev = instanceInfo.setStatus(next);
    if (prev != null) {
        for (StatusChangeListener listener : listeners.values()) {
            try {
                listener.notify(new StatusChangeEvent(prev, next));
            } catch (Exception e) {
                logger.warn("failed to notify listener: {}", listener.getId(), e);
            }
        }
    }
}

4、DiscoveryClient的register()方法

  register()方法,用来注册服务实例信息到Eureka服务端。实际上是通过AbstractJerseyEurekaHttpClient类中的register()方法实现。

boolean register() throws Throwable {
    logger.info(PREFIX + "{}: registering service...", appPathIdentifier);
    EurekaHttpResponse<Void> httpResponse;
    try {
        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();
}
4.1、AbstractJerseyEurekaHttpClient类的register()方法

  AbstractJerseyEurekaHttpClient类的register()方法,实际上就是基于Jersey框架实现,发送了一个post请求到服务端。

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

你可能感兴趣的:(Spring,Cloud,Spring,Cloud,eureka)