Eureka 服务注册与发现

core:Java功能增强 —— 事件机制(事件与监听器)

Eureka提供了服务注册与发现的功能,需要提供一个服务注册中心(我这里是在spring boot项目启动类上使用@EnableEurekaServer,再配置相关属性),然后在我们的应用服务(我这里是spring boot服务)启动类上使用@EnableDiscoveryClient启用服务注册发现功能。那么问题来了,我们的服务是怎么注册到注册中心的呢?

使用@EnableDiscoveryClient注解后启动服务,我们可以发现控制台打出了如下log:


image.png

从图中可以看出的信息有:1、是在EurekaDiscoveryClientConfiguration中将服务器注册到eureka的。2、DiscoveryClient类里面应该有许多服务器与eureka之间的通信操作,如心跳续约等。3、InstanceInfoReplicator应该是一个线程类。

既然EurekaDiscoveryClientConfiguration是最开始的地方,那就从它看起。

1、EurekaDiscoveryClientConfiguration

源码如下:

@Configuration     //使用该注解来让Spring容器发现并注册里面的Bean
@EnableConfigurationProperties
@ConditionalOnClass({EurekaClientConfig.class})
@ConditionalOnProperty(
value = {"eureka.client.enabled"},
matchIfMissing = true
)
public class EurekaDiscoveryClientConfiguration implements SmartLifecycle, Ordered {

public void start() {
    if (this.port.get() != 0 && this.instanceConfig.getNonSecurePort() == 0) {
        this.instanceConfig.setNonSecurePort(this.port.get());
    }

    if (!this.running.get() && this.instanceConfig.getNonSecurePort() > 0) {
        this.maybeInitializeClient();
        if (log.isInfoEnabled()) {
            log.info("Registering application " + this.instanceConfig.getAppname() + " with eureka with status " + this.instanceConfig.getInitialStatus());
        }

        this.applicationInfoManager.setInstanceStatus(this.instanceConfig.getInitialStatus());
        if (this.healthCheckHandler != null) {
            this.eurekaClient.registerHealthCheck(this.healthCheckHandler);
        }
        //发布了一个注册事件,在哪里监听的?还是说是留给我们开发者用的?
        this.context.publishEvent(new InstanceRegisteredEvent(this, this.instanceConfig));
        this.running.set(true);
    }

}

private void maybeInitializeClient() {
    this.applicationInfoManager.getInfo();
    this.eurekaClient.getApplications();
}

public void stop() {
    if (this.applicationInfoManager.getInfo() != null) {
        if (log.isInfoEnabled()) {
            log.info("Unregistering application " + this.instanceConfig.getAppname() + " with eureka with status DOWN");
        }

        this.applicationInfoManager.setInstanceStatus(InstanceStatus.DOWN);
    }

    this.running.set(false);
}


//监听,内置容器启动时开始注册    
@EventListener({EmbeddedServletContainerInitializedEvent.class})
public void onApplicationEvent(EmbeddedServletContainerInitializedEvent event) {
    int localPort = event.getEmbeddedServletContainer().getPort();
    if (this.port.get() == 0) {
        log.info("Updating port to " + localPort);
        this.port.compareAndSet(0, localPort);
        this.start();
    }

}

//监听,应用关闭时关闭eureka客户端
@EventListener({ContextClosedEvent.class})
public void onApplicationEvent(ContextClosedEvent event) {
    this.stop();
    this.eurekaClient.shutdown();
}
}

按猜想的来说应该有一个Rest请求什么的将服务器信息发送到eureka服务器才对,好像上面的start()方法没有做什么事情。

我们关闭了eureka注册中心,重新启动服务,发现报了如下错:


image.png

嗯,意料之中,肯定连不上啊,从这里可以看出,发请求是RedirectingEurekaHttpClient请求客户端做的,接着往下:


image.png

就像一开始猜想的,DiscoveryClient里面有与eureka的通信操作,而InstanceInfoReplicator应该是一个线程类。进去看看:

DiscoveryClient使用@Singleton修饰,里面有这么两个函数,可以看出是注册与续约:

boolean register() throws Throwable {
    logger.info("DiscoveryClient_" + this.appPathIdentifier + ": registering service...");

    EurekaHttpResponse httpResponse;
    try {
        httpResponse = this.eurekaTransport.registrationClient.register(this.instanceInfo);
    } catch (Exception var3) {
        logger.warn("{} - registration failed {}", new Object[]{"DiscoveryClient_" + this.appPathIdentifier, var3.getMessage(), var3});
        throw var3;
    }

    if (logger.isInfoEnabled()) {
        logger.info("{} - registration status: {}", "DiscoveryClient_" + this.appPathIdentifier, httpResponse.getStatusCode());
    }

    return httpResponse.getStatusCode() == 204;
}

boolean renew() {
    try {
        EurekaHttpResponse<InstanceInfo> httpResponse = this.eurekaTransport.registrationClient.sendHeartBeat(this.instanceInfo.getAppName(), this.instanceInfo.getId(), this.instanceInfo, (InstanceStatus)null);
        logger.debug("{} - Heartbeat status: {}", "DiscoveryClient_" + this.appPathIdentifier, httpResponse.getStatusCode());
        if (httpResponse.getStatusCode() == 404) {
            this.REREGISTER_COUNTER.increment();
            logger.info("{} - Re-registering apps/{}", "DiscoveryClient_" + this.appPathIdentifier, this.instanceInfo.getAppName());
            return this.register();
        } else {
            return httpResponse.getStatusCode() == 200;
        }
    } catch (Throwable var3) {
        logger.error("{} - was unable to send heartbeat!", "DiscoveryClient_" + this.appPathIdentifier, var3);
        return false;
    }
}

这个register()就是InstanceInfoReplicator的run方法里调用的,而InstanceInfoReplicator是在DiscoveryClient的构造方法中实例化的。

在eureka服务器端则是将服务信息存放在一个双层Map里,第一层的Key是服务名,第二层的Key是实例名:

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

推荐阅读更多精彩内容