SpringCloud系列之Eureka Server源码分析

473 阅读8分钟

Eureka Server版本:spring-cloud-netflix-eureka-server.2.1.0.RELEASE

1、Eureka Server激活

        在SpringBoot项目启动类上添加@EnableEurekaServer 注解,即可激活eureka服务器。

EnableEurekaServer类源码

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(EurekaServerMarkerConfiguration.class)    
public @interface EnableEurekaServer {

}

        我们可以看到,注解中引入了eureka一个标记类EurekaServerMarkerConfiguration,这个类的作用就是创建一个标记bean,用以激活eureka server的自动配置。


2、Eureka 事件

    eureka有一个根抽象事件类org.springframework.context.ApplicationEvent,其继承自java的根事件类java.util.EventObject,EventObject维护了一个事件源对象,并在构造器中初始化事件源对象,源码如下:

public class EventObject implements java.io.Serializable {

    private static final long serialVersionUID = 5516075349620653480L;
    //事件源
    protected transient Object  source;

    //构造器中初始化事件源
    public EventObject(Object source) {
        if (source == null)
            throw new IllegalArgumentException("null source");

        this.source = source;
    }

    //对外暴露事件源
    public Object getSource() {
        return source;
    }

    public String toString() {
        return getClass().getName() + "[source=" + source + "]";
    }
}

        查看eureka根抽象事件类ApplicationEvent源码,我们会发现其构造器中调用了其父类EventObject的构造器,将事件源交给EventObject来维护。ApplicationEvent构造源码如下:

public ApplicationEvent(Object source) {
    super(source);  //调用父类构造器,用以初始化事件源对象
    this.timestamp = System.currentTimeMillis();
}


        从源码中可知,eureka server有五种事件,这些事件都继承自ApplicationEvent根抽象事件,分别是:

EurekaServerStartedEvent      - Eureka服务端启动事件
EurekaRegistryAvailableEvent  - Eureka服务端可用事件
EurekaInstanceRegisteredEvent - Eureka客户端服务注册事件
EurekaInstanceRenewedEvent    - Eureka客户端续约事件
EurekaInstanceCanceledEvent   - Eureka客户端下线事件

        我们来看看这五种事件源码:

    2.1、服务端启动事件 - EurekaServerStartedEvent

源码:

public class EurekaServerStartedEvent extends ApplicationEvent {
	public EurekaServerStartedEvent(EurekaServerConfig eurekaServerConfig) {
		super(eurekaServerConfig);
	}
}

        从源码中,这个类很简单,只有一个构造器,其中调用父类构造器,传入了一个eureka服务器的配置对象。从上面的分析中,我们知道,此配置对象eurekaServerConfig将作为事件源,交给EventObject维护。

    2.2、客户端服务注册事件 - EurekaInstanceRegisteredEvent

重要部分源码:

//客户端实例注册信息对象
private InstanceInfo instanceInfo;
//客户端实例租约间隔,默认为90秒
private int leaseDuration;
//是否同步,在eureka配置为高可用时,其中的一个服务器节点中,客户端实例信息可能是直接注册到此节点,
//也可能是其他节点同步过来的。此标志起区分作用,以避免二次注册
private boolean replication;

public EurekaInstanceRegisteredEvent(Object source, InstanceInfo instanceInfo,
    int leaseDuration, boolean replication) {
    super(source);  //事件源对象
    this.instanceInfo = instanceInfo;
    this.leaseDuration = leaseDuration;
    this.replication = replication;
}

    2.3、服务端可用事件 - EurekaRegistryAvailableEvent

源码:

public class EurekaRegistryAvailableEvent extends ApplicationEvent {
	public EurekaRegistryAvailableEvent(EurekaServerConfig eurekaServerConfig) {
		super(eurekaServerConfig);  //初始化事件源对象
	}
}

        从源码中,我们看到,此事件中,只有一个构造器,初始化了事件源对象。事件源对象和服务器启动事件中初始化的事件源是同一类型,都是EurekaServerConfig。

    2.4、客户端续约事件 - EurekaInstanceRenewedEvent

重要部分源码:

//app名称
private String appName;
//服务id
private String serverId;
//客户端实例注册信息
private InstanceInfo instanceInfo;
//是否同步。和客户端注册事件一样,用以解决二次续约问题
private boolean replication;

public EurekaInstanceRenewedEvent(Object source, String appName, String serverId,
		InstanceInfo instanceInfo, boolean replication) {
	super(source);  //初始化事件源
	this.appName = appName;
	this.serverId = serverId;
	this.instanceInfo = instanceInfo;
	this.replication = replication;
}

    2.5、客户端下线事件 - EurekaInstanceCanceledEvent

部分重要源码:

//app名称
private String appName;
//服务id
private String serverId;
//是否同步。表示此客户端实例信息是注册信息,还是从其他节点同步来的
private boolean replication;

public EurekaInstanceCanceledEvent(Object source, String appName, String serverId,
		boolean replication) {
	super(source);  //初始化事件源
	this.appName = appName;
	this.serverId = serverId;
	this.replication = replication;
}


3、Eureka Server自动配置

        Eureka Server自动配置中有一个很重要的配置类EurekaServerAutoConfiguration,在Eureka Server启动时,需要加载此配置类,以完成一些重要组件的初始化,下面我们看看其源码:

@Configuration(proxyBeanMethods = false)
@Import(EurekaServerInitializerConfiguration.class)
@ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class)
@EnableConfigurationProperties({ EurekaDashboardProperties.class,
		InstanceRegistryProperties.class })
@PropertySource("classpath:/eureka/server.properties")
public class EurekaServerAutoConfiguration implements WebMvcConfigurer {
   ...
}

        EurekaServerAutoConfiguration类中,标注了五个注解。第一个注解@Configuration是一个spring配置注解;第二个注解@Import是一个导入注解,用以导入EurekaServerInitializerConfiguration配置类;第三个注解@ConditionalOnBean是一个条件注解,含义为条件中的bean存在,才会实例化目标bean;第四个注解@EnableConfigurationProperties是一个使注解@ConfigurationProperties标注的配置类生效的注解;第五个注解@PropertySource用以加载指定的属性资源文件。

        我们重点来了解一下第二,第三,第四个注解。

        @Import注解导入了一个Eureka Server初始化配置类EurekaServerInitializerConfiguration,我们来看看其源码:

@Configuration
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(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();
	}

	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);
	}

   ...
}

    首先,我们可以看到本类中自动注入了eureka服务器配置类EurekaServerConfig、servlet上下文类ServletContext、应用上下文类ApplicationContext以及eureka服务器启动引导类EurekaServerBootstrap。还有两个最重要的start和stop方法,它们都是通过eureka服务器启动引导类启动或关闭eureka服务器的。在start方法中,我们可以看到其发布了两个eureka事件,分别是服务端可用事件和服务器启动事件,事件源为同一个eureka服务器配置对象eurekaServerConfig。

    @ConditionalOnBean注解是一个条件注解,其意为条件bean存在时,才加载目标bean。这里我们看到条件bean的类型为EurekaServerMarkerConfiguration.Marker,我们来看看其源码:

@Configuration
public class EurekaServerMarkerConfiguration {

	@Bean
	public Marker eurekaServerMarkerBean() {
		return new Marker();
	}

	class Marker {
	}
}

    我们发现,EurekaServerMarkerConfiguration类中声明了一个名为Marker的内部类,没什么具体含义,然后创建此注册内部类的bean到spring容器中。查看注释,我们可以看到这个bean是一个标记,起激活eureka server的作用。从文章刚开始,我们就看了@EnableEurekaServer注解的源码,其有个@Import注解,正是导入类此标记类。联想@EnableEurekaServer注解的使用,我们可以得出结论,在eureka server的主类启动时,@EnableEurekaServer注解被加载,其中@EnableEurekaServer注解被加载时,又要加载标记类EurekaServerMarkerConfiguration,而此类是个配置类,其中Marker类型的bean将被注册到spring容器,也就是说,当EurekaServerInitializerConfiguration配置类加载时,其条件bean已经存在于spring bean容器中,EurekaServerInitializerConfiguration配置类满足加载条件,即可加载自身。

    @EnableConfigurationProperties注解表示使被@ConfigurationProperties注解标注的属性类生效的注解,在这里,它有两个值,一个是EurekaDashboardProperties类,其为eureka dashboard的属性类,其中有关于eureka dashboard的相关属性配置;另一个是InstanceRegistryProperties类,其中有关于客户端实例注册的属性配置。

    EurekaServerAutoConfiguration类上标注的重要注解介绍完,我们来看看其中注入的重要bean,以及那些需要注册的bean。

    EurekaServerAutoConfiguration类中有5个注入的bean

  • 第一个被注入的bean为ApplicationInfoManager,其作用是初始化eureka server需要的注册信息以及向其他组件提供接口
  • 第二个被注入的bean为EurekaServerConfig接口类,提供一些eureka server的配置信息
  • 第三个被注入的bean为EurekaClientConfig接口类,提供了eureka客户端向eureka服务器注册实例需要的配置信息
  • 第四个被注入的bean为EurekaClient接口类,提供了获取客户端实例、注册和获取客户端心跳检查处理器的能力
  • 第五个被注入的bean为InstanceRegistryProperties属性类,提供客户端实例注册相关属性

    

    EurekaServerAutoConfiguration类中有11个将要注册的bean

  • HasFeatures,用以actuator收集关键信息

@Bean
public HasFeatures eurekaServerFeature() {
	return HasFeatures.namedFeature("Eureka Server",
		EurekaServerAutoConfiguration.class);
}

  • EurekaServerConfig,提前注册eureka服务器配置bean,我们发现此为在一个内部静态类中优先加载

@Configuration
protected static class EurekaServerConfigBeanConfiguration {
	@Bean
	@ConditionalOnMissingBean
	public EurekaServerConfig eurekaServerConfig(EurekaClientConfig clientConfig) {
		EurekaServerConfigBean server = new EurekaServerConfigBean();
		if (clientConfig.shouldRegisterWithEureka()) {
			// Set a sensible default if we are supposed to replicate
			server.setRegistrySyncRetries(5);
		}
		return server;
	}
}

  • EurekaController,eureka dashboard控制器bean

@Bean
@ConditionalOnProperty(prefix = "eureka.dashboard", name = "enabled", matchIfMissing = true)
public EurekaController eurekaController() {
	return new EurekaController(this.applicationInfoManager);
}

  • ServerCodecs,服务器编解码器,用以读取json或xml类型文件编码解码

@Bean
public ServerCodecs serverCodecs() {
	return new CloudServerCodecs(this.eurekaServerConfig);
}

  • PeerAwareInstanceRegistry,其包为com.netflix.eureka.registry。而其实际注册的bean类型为InstanceRegistry,其包为org.springframework.cloud.netflix.eureka.server。Eureka原为netflix的项目,spring将其集成进springcloud中。

@Bean
public PeerAwareInstanceRegistry peerAwareInstanceRegistry(
		ServerCodecs serverCodecs) {
	this.eurekaClient.getApplications(); // force initialization
	return new InstanceRegistry(this.eurekaServerConfig, this.eurekaClientConfig,
			serverCodecs, this.eurekaClient,
			this.instanceRegistryProperties.getExpectedNumberOfClientsSendingRenews(),
			this.instanceRegistryProperties.getDefaultOpenForTrafficCount());
}

  • PeerEurekaNodes,eureka管理同辈节点生命周期的帮助类

@Bean
@ConditionalOnMissingBean
public PeerEurekaNodes peerEurekaNodes(PeerAwareInstanceRegistry registry,
	ServerCodecs serverCodecs) {
	return new RefreshablePeerEurekaNodes(registry, this.eurekaServerConfig,
		this.eurekaClientConfig, serverCodecs, this.applicationInfoManager);
}

  • EurekaServerContext,eureka服务器上下文

@Bean
public EurekaServerContext eurekaServerContext(ServerCodecs serverCodecs,
		PeerAwareInstanceRegistry registry, PeerEurekaNodes peerEurekaNodes) {
	return new DefaultEurekaServerContext(this.eurekaServerConfig, serverCodecs,
			registry, peerEurekaNodes, this.applicationInfoManager);
}

  • EurekaServerBootstrap,eureka server启动引导,用以初始化或销毁eureka server上下文,环境,

@Bean
public EurekaServerBootstrap eurekaServerBootstrap(PeerAwareInstanceRegistry registry,
		EurekaServerContext serverContext) {
	return new EurekaServerBootstrap(this.applicationInfoManager,
			this.eurekaClientConfig, this.eurekaServerConfig, registry,
			serverContext);
}

  • FilterRegistrationBean,注册jersey过滤器

@Bean
public FilterRegistrationBean jerseyFilterRegistration(
		javax.ws.rs.core.Application eurekaJerseyApp) 
        
	FilterRegistrationBean bean = new FilterRegistrationBean();
	bean.setFilter(new ServletContainer(eurekaJerseyApp));
	bean.setOrder(Ordered.LOWEST_PRECEDENCE);
	bean.setUrlPatterns(
		Collections.singletonList(EurekaConstants.DEFAULT_PREFIX + "/*"));

	return bean;
}

  • Application,注册一个jersey应用

@Bean
public javax.ws.rs.core.Application jerseyApplication(
    Environment environment,ResourceLoader resourceLoader) {
    ...
	return rc;
}

  • FilterRegistrationBean,用以跟踪http请求

@Bean
public FilterRegistrationBean traceFilterRegistration(
		@Qualifier("httpTraceFilter") Filter filter) {
	FilterRegistrationBean bean = new FilterRegistrationBean();
	bean.setFilter(filter);
	bean.setOrder(Ordered.LOWEST_PRECEDENCE - 10);
	return bean;
}


4、eureka server启动加载流程总结

  • eureka server项目主类启动
  • SpringApplication类调用run方法,秒表计时开始
  • 获取spring application运行时监听器,其中会加载目录META-INF下spring.factories文件中的配置类
  • 运行时监听器中设置环境信息
  • 使用环境信息创建Banner
  • 根据web应用类型(默认为servlet类型),创建基于注解的servlet web上下文AnnotationConfigServletWebServerApplicationContext
  • 上下文准备
  • 上下文刷新
  • 上下文刷新后操作
  • 秒表计时结束
  • 监听器启动上下文,发布已启动事件ApplicationStartedEvent
  • 调用运行程序,spring提供了两种运行程序接口,分别是ApplicationRunner接口和CommandLineRunner接口,以供开发者在此处扩展。