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 {}@Configurationpublic class EurekaServerMarkerConfiguration {@Beanpublic 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 EurekaServerInitializerConfigurationimplements ServletContextAware, SmartLifecycle, Ordered {private static final Log log = LogFactory.getLog(EurekaServerInitializerConfiguration.class);@Autowiredprivate EurekaServerConfig eurekaServerConfig;private ServletContext servletContext;@Autowiredprivate ApplicationContext applicationContext;@Autowiredprivate EurekaServerBootstrap eurekaServerBootstrap;private boolean running;private int order = 1;@Overridepublic void setServletContext(ServletContext servletContext) {this.servletContext = servletContext;}@Overridepublic 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);}@Overridepublic void stop() {this.running = false;eurekaServerBootstrap.contextDestroyed(this.servletContext);}@Overridepublic boolean isRunning() {return this.running;}@Overridepublic int getPhase() {return 0;}@Overridepublic boolean isAutoStartup() {return true;}@Overridepublic void stop(Runnable callback) {callback.run();}@Overridepublic int getOrder() {return this.order;}}
通过上面代码可以看出EurekaServerInitializerConfiguration也是一个配置类,同时它实现了ServletContextAware接口,可以在Servlet容器启动后得到ServletContext容器上下文;它还实现了SmartLifecycle,这样在spring 生命周期中会调用这个类相关的方法。比如在spring初始化时,会调用它start方法。
@Overridepublic void start() {new Thread(new Runnable() {@Overridepublic 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容器的一起启起了。

