1. Eureka架构图
2. Eureka核心功能点
- 服务注册(register):Eureka Client 会通过发送 REST 请求的方式向 Eureka Server 注册自己的服务,提供自身的元数据,比如 ip 地址、端口、运行状况指标的url、主页地址等信息。Eureka Server接收到注册请求后,就会把这些元数据信息存储在一个双层的Map中。
- 服务续约(renew):在服务注册后,Eureka Client 会维护一个心跳来持续通知 Eureka Server,说明服务一直处于可用状态,防止被剔除。Eureka Client在默认的情况下会每隔30秒(
eureka.instance.leaseRenewallIntervalInSeconds
) 发送一次心跳来进行服务续约。 - 服务同步(replicate):Eureka Server 之间会互相进行注册,构建 Eureka Server 集群,不同 Eureka Server 之间会进行服务同步,用来保证服务信息的一致性。
- 获取服务(get registry):服务消费者(Eureka Client)在启动的时候,会发送一个 REST 请求给 Eureka Server,获取上面注册的服务清单,并且缓存在 Eureka Client 本地,默认缓存30秒 (eureka.client.registryFetchIntervalSeconds)。同时,为了性能考虑,Eureka Server 也会维护一份只读的服务清单缓存,该缓存每隔30秒更新一次。
- 服务调用:服务消费者在获取到服务清单后,就可以根据清单中的服务列表信息,查找到其他服务的地址,从而进行远程调用。Eureka 有 Region 和 Zone 的概念,一个 Region 可以包含多个 Zone,在进行服务调用时,优先访问处于同一个 Zone 中的服务提供者。
- 服务下线(cancel):当 Eureka Client 需要关闭或重启时,就不希望在这个时间段内再有请求进来,所以,就需要提前先发送 REST 请求给 Eureka Server,告诉Eureka Server自己要下线了,Eureka Server 在收到请求后,就会把该服务状态置为下线(DOWN),并把该下线事件传播出去。
- 服务剔除(evict):有时候,服务实例可能会因为网络故障等原因导致不能提供服务,而此时该实例也没有发送请求给 Eureka Server 来进行服务下线,所以,还需要有服务剔除的机制。Eureka Server 在启动的时候会创建一个定时任务,每隔一段时间(默认60秒),从当前服务清单中把超时没有续约(默认90秒,
eureka.instance.leaseExpirationDurationInSeconds
)的服务剔除。 自我保护:既然 Eureka Server 会定时剔除超时没有续约的服务,那就有可能出现一种场景,网络一段时间内发生了异常,所有的服务都没能够进行续约,Eureka Server 就把所有的服务都剔除了,这样显然不太合理。所以,就有了自我保护机制,当短时间内,统计续约失败的比例,如果达到一定阈值,则会触发自我保护的机制,在该机制下,Eureka Server 不会剔除任何的微服务,等到正常后,再退出自我保护机制。自我保护开关(
eureka.server.enable-self-preservation: false
)3. @EnableEurekaServer注解分析
在服务的启动类上添加@EnableEurekaServer 注解后,这个服务就是一个注册中心。下面分析下为何加了 @EnableEurekaServer 注解后为何就成为了服务的注册中心。 ```java @SpringBootApplication @EnableEurekaServer public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class, args);
}
}
```java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(EurekaServerMarkerConfiguration.class)
public @interface EnableEurekaServer {
}
@Configuration
public class EurekaServerMarkerConfiguration {
@Bean
public Marker eurekaServerMarkerBean() {
return new Marker();
}
class Marker {
}
}
看上面的代码可以得知通过@Import 注解将EurekaServerMarkerConfiguration这个类导入到spring容器中,
然后EurekaServerMarkerConfiguration类中声明了Marker类的bean对象。这个bean对象下面会用到。
当注册中心启动时,了解springboot自动装配我们知道,系统会加载META-INF/spring.factories下的配置
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.netflix.eureka.server.EurekaServerAutoConfiguration
根据spring.factories配置文件会加载EurekaServerAutoConfiguration这个类
@Configuration
@Import(EurekaServerInitializerConfiguration.class)
// 有Marker的这个bean才会将EurekaServerAutoConfiguration注入到spring容器中
@ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class)
@EnableConfigurationProperties({ EurekaDashboardProperties.class,
InstanceRegistryProperties.class })
@PropertySource("classpath:/eureka/server.properties")
public class EurekaServerAutoConfiguration extends WebMvcConfigurerAdapter {
通过代码可以得知,spring会判断是否存在Marker的bean对象,存在则将EurekaServerAutoConfiguration这个类注入到spring容器中。在EurekaServerAutoConfiguration类中我们并没有发现启动Eureka的代码,下面我们看下EurekaServerInitializerConfiguration这个类
/*
* Copyright 2013-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.netflix.eureka.server;
import javax.servlet.ServletContext;
import com.netflix.eureka.EurekaServerConfig;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.netflix.eureka.server.event.EurekaRegistryAvailableEvent;
import org.springframework.cloud.netflix.eureka.server.event.EurekaServerStartedEvent;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.SmartLifecycle;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.web.context.ServletContextAware;
/**
* @author Dave Syer
*/
@Configuration(proxyBeanMethods = false)
public class EurekaServerInitializerConfiguration
implements ServletContextAware, SmartLifecycle, Ordered {
private static final Log log = LogFactory
.getLog(EurekaServerInitializerConfiguration.class);
@Autowired
private EurekaServerConfig eurekaServerConfig;
private ServletContext servletContext;
@Autowired
private ApplicationContext applicationContext;
@Autowired
private EurekaServerBootstrap eurekaServerBootstrap;
private boolean running;
private int order = 1;
@Override
public void setServletContext(ServletContext servletContext) {
this.servletContext = servletContext;
}
@Override
public void start() {
new Thread(() -> {
try {
// TODO: is this class even needed now?
eurekaServerBootstrap.contextInitialized(
EurekaServerInitializerConfiguration.this.servletContext);
log.info("Started Eureka Server");
publish(new EurekaRegistryAvailableEvent(getEurekaServerConfig()));
EurekaServerInitializerConfiguration.this.running = true;
publish(new EurekaServerStartedEvent(getEurekaServerConfig()));
}
catch (Exception ex) {
// Help!
log.error("Could not initialize Eureka servlet context", ex);
}
}).start();
}
private EurekaServerConfig getEurekaServerConfig() {
return this.eurekaServerConfig;
}
private void publish(ApplicationEvent event) {
this.applicationContext.publishEvent(event);
}
@Override
public void stop() {
this.running = false;
eurekaServerBootstrap.contextDestroyed(this.servletContext);
}
@Override
public boolean isRunning() {
return this.running;
}
@Override
public int getPhase() {
return 0;
}
@Override
public boolean isAutoStartup() {
return true;
}
@Override
public void stop(Runnable callback) {
callback.run();
}
@Override
public int getOrder() {
return this.order;
}
}
通过上面代码可以看出EurekaServerInitializerConfiguration也是一个配置类,同时它实现了ServletContextAware接口,可以在Servlet容器启动后得到ServletContext容器上下文;它还实现了SmartLifecycle,这样在spring 生命周期中会调用这个类相关的方法。比如在spring初始化时,会调用它start方法。
@Override
public void start() {
new Thread(new Runnable() {
@Override
public void run() {
try {
//TODO: is this class even needed now?
eurekaServerBootstrap.contextInitialized(EurekaServerInitializerConfiguration.this.servletContext);
log.info("Started Eureka Server");
publish(new EurekaRegistryAvailableEvent(getEurekaServerConfig()));
EurekaServerInitializerConfiguration.this.running = true;
publish(new EurekaServerStartedEvent(getEurekaServerConfig()));
}
catch (Exception ex) {
// Help!
log.error("Could not initialize Eureka servlet context", ex);
}
}
}).start();
}
start方法中启动了一个后台线程,它会执行这一行代码。
eurekaServerBootstrap.contextInitialized(EurekaServerInitializerConfiguration.this.servletContext);
public void contextInitialized(ServletContext context) {
try {
initEurekaEnvironment();// 初始化eureka环境
initEurekaServerContext();// 初始化eureka上下文
context.setAttribute(EurekaServerContext.class.getName(), this.serverContext);
}
catch (Throwable e) {
log.error("Cannot bootstrap eureka server :", e);
throw new RuntimeException("Cannot bootstrap eureka server :", e);
}
}
上面的代码会调用initEurekaEnvironment方法初始化eureka环境,调用initEurekaServerContext方法初始化eureka上下文, 至此Eureka Server就随着Spring容器的一起启起了。