在《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()方法,启动服务注册的线程。
InstanceInfoReplicator类是实现服务注册的关键所在,该类实现了Runnable接口,代表了一个实现服务注册的任务。该任务有以下特征:
通过前面了解,我们知道服务注册时通过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);
}
//省略……
}
该方法在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;
}
}
该方法是启动线程的方法,并通过调度器执行任务,任务对象就是当前对象,实际会执行该实例的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);
}
}
该方法就是服务注册任务实际执行的方法,首先,调用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);
}
}
该方法主要用来刷新服务实例的状态信息。
首先,调用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);
}
}
该方法主要用来刷新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();
}
该方法主要用来刷新租约。
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();
}
}
主要用来设置服务实例状态,并通知服务实例状态监听器。
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);
}
}
}
}
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();
}
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();
}
}
}