一 Eureka Server的启动
1.1 创建Eureka server项目
在Springboot项目中,引入如下依赖;在启动类上使用@EnableEurekaServer,添加简单的配置,就可启动eureka server了。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
<version>1.4.0.RELEASE</version>
</dependency>
eureka:
client:
registerWithEureka: false
fetchRegistry: false
serviceUrl:
defaultZone: http://localhost:8761/eureka/
我们一起来看看eureka server是如何启动的。
1.2 包依赖关系
在spring-cloud-starter-netflix-eureka-server中,引入了spring-cloud-netflix-eureka-server。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix-eureka-server</artifactId>
</dependency>
在spring-cloud-netflix-eureka-server中,又引入了eureka-core、eureka-client等。
<dependency>
<groupId>com.netflix.eureka</groupId>
<artifactId>eureka-core</artifactId>
</dependency>
<dependency>
<groupId>com.netflix.eureka</groupId>
<artifactId>eureka-client</artifactId>
</dependency>
1.3 Springboot下eureka server启动流程
1.3.1 @EnableEurekaServer注解的作用
@EnableEurekaServer,加载了EurekaServerMarkerConfiguration类。
该类仅仅创建了一个Marker类实例,用于标记启用eureka服务端。
真正的配置类,是spring.factories指明的EurekaServerAutoConfiguration类。
看到@ConditionalOnBean,你是不是明白了Marker类的作用:只在容器中存在Marker实例时,才加载配置类。
1.3.2 EurekaServerAutoConfiguration
EurekaServerAutoConfiguration中创建了EurekaServerBootstrap实例,该类的contextInitialized()方法,完成了启动过程。
这个方法在哪儿被调用呢?
来看EurekaServerInitializerConfiguration类,通过@Import引入。它实现了Spring的SmartLifecycle接口,在start()方法中调用EurekaServerBootstrap.contextInitialized方法。
至此Eureka Server启动了。
SmartLifecycle是Lifecycle接口的扩展。
LifeCycle定义了Spring容器中bean的生命周期。当
ApplicationContext本身接收启动和停止信号时,spring容器将在上下文中找出所有实现了LifeCycle及其子接口的类,并一一调用它们的start方法。
二 不使用Springboot时
2.1 EurekaBootStrap类
在eureka-core包中,有一个EurekaBootStrap类,它实现了ServletContextListener接口,复写了contextInitialized方法。
是不是很眼熟?怎么和springboot中EurekaServerBootstrap类contextInitialized方法,一模一样。
其实,EurekaServerBootstrap只是对eureka源码中EurekaBootStrap类的改写,将原本用new创建的对象,改用@Bean交由spring容器来管理。
2.2 ServletContextListener
ServletContextListener,监听ServletContext对象的创建和销毁,
ServletContext对象创建后会调用contextInitialized方法。
在eureka-server包的web.xml中,有如下配置。
<listener>
<listener-class>com.netflix.eureka.EurekaBootStrap</listener-class>
</listener>
可见,源码中eureka server通过servlet-api的ServletContextListener来启动。
三 contextInitialized的逻辑
其实,就干了两件事:初始化环境和初始化上下文
public void contextInitialized(ServletContextEvent event) {
try {
initEurekaEnvironment();
initEurekaServerContext();
// 省略非核心代码
}
}
3.1 初始化环境
initEurekaEnvironment中,主要获取配置管理类的一个单例。
String dataCenter = ConfigurationManager.getConfigInstance().getString(EUREKA_DATACENTER);
static volatile AbstractConfiguration instance。使用双重检测+volatile实现单例模式。
3.2 初始化上下文
3.2.1 加载eureka-server配置文件
此时,会创建DefaultEurekaServerConfig。
EurekaServerConfig eurekaServerConfig = new DefaultEurekaServerConfig();
配置文件是eureka-server.properties,在eureka-server包中。
可是,文档中内容全被注释了。真正的配置从哪儿来呢?
DefaultEurekaServerConfig中有很多get方法,为各配置项提供了默认值。
evictionIntervalTimerInMs:eureka-server清理无效节点的时间间隔
3.2.2 创建ApplicationInfoManager实例
ApplicationInfoManager类,是服务配置管理器,提供向Eureka Server注册所需的信息。
EurekaInstanceConfig中很多get方法,用于获取服务端配置项。
在集群部署时,eureka server间可以相互注册;某个eureka server节点,相对于其他节点而言,就是一个客户端。
因此,创建InstanceInfo来封装eureka server实例的服务信息,如ip、port等。
private volatile String instanceId;
private volatile String appName;
private volatile String ipAddr;
private volatile int port = DEFAULT_PORT;
3.2.3 创建eurekaClient实例
eurekaClient是包含在eureka-server 服务中的,用来跟集群中其他 eureka-server实例进行通信的。
eurekaClient的启动过程,将在随后的《Eureka Client的启动过程》一文中介绍。
3.2.4 创建PeerAwareInstanceRegistry实例
PeerAwareInstanceRegistry接口,定义了Eureka Server各节点间的数据同步方式,使集群保持最终一致性。
非AWS环境下,会创建PeerAwareInstanceRegistryImpl实例,该类的主要功能,如源码所说:
- 复制其他节点的主要操作有注册、续订、取消、过期和状态更改
- 当eureka服务器启动时,它尝试从对等eureka节点获取所有注册表信息。如果由于某种原因此操作失败,服务器不允许用户在EurekaServerConfig.getWaitTimeInMsWhenSyncEmpty()指定的一段时间内获取注册表信息。
- 自我保护机制:如果在EurekaServerConfig.getRenewalThresholdUpdateIntervalMs()的一段时间内,续订量下降超过EurekaServerConfig.getRenewalPercentThreshold()中指定的阈值,eureka将停止剔除过期实例。
3.2.5 创建上下文实例
即DefaultEurekaServerContext类。
EurekaServerContextHolder是serverContext的持有者,采用单例模式。
调用serverContext.initialize(),其中逻辑将在随后的文章中进一步介绍。
3.2.6 从相邻节点拷贝注册表
通过前面创建的eurekaClient,从集群中其他节点拉取注册表,并添加到本地注册表。
// Copy registry from neighboring eureka node
int registryCount = registry.syncUp();
3.2.7 启动eureka 监控
四 结论
- 在springboot下,提供了自动配置,并通过SmartLifecycle将容器与eureka启动流程相串联;
- 在源码中,通过ServletContextListener,在web应用上下文创建后,调用eureka启动流程。
- Eureka Server启动时,需要从集群中其他节点同步数据,是通过创建一个eureka client,来拉取注册表信息。
- Eureka Server的启动流程,比Eureka client复杂,建议先了解后者。