在微服务开发过程中,始终少不了注册中心的存在,注册中心提供了服务的注册与发现机制,能够不需要代码改动,实现服务的横向扩展,同时也为微服务之间的调用解耦,避免了服务调用之间的高度依赖。本文主要从源码的角度出发,对spring-cloud-netflex-eureka-server中的源码进行解读,学习eureka中服务注册实现逻辑.
启动EurekaServer
完整的demo可以通过spring-cloud-learn项目查看
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class SpringEurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(SpringEurekaServerApplication.class, args);
}
}
当启动eureka-server时,需要通过@EnableEurekaServer注解的方式显示启动spring-cloud-netflix-eureka-server装配.
启动过程
@EnableEurekaServer
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(EurekaServerMarkerConfiguration.class)
public @interface EnableEurekaServer {
}
通过该注解,将会@Import一个Configuration类型的初始化与创建工作.
EurekaServerMarkerConfiguration
@Configuration
public class EurekaServerMarkerConfiguration {
@Bean
public Marker eurekaServerMarkerBean() {
return new Marker();
}
class Marker {
}
}
该Configuration类型中,只是创建一个Marker的Bean对象,并没有做其他的操作.
EurekaClientAutoConfiguration
在装配Eureka Server时,会优先触发Eureka Client 的装配信息,通过对EurekaServerAutoConfiguration类型进行查看,是因为依赖了Eureka Client中的装配信息
@Autowired private ApplicationInfoManager applicationInfoManager; @Autowired private EurekaClientConfig eurekaClientConfig; @Autowired private EurekaClient eurekaClient;
这里依赖了Eureka Client相关的类信息,是因为在Eureka Server设计中,他本身也会作为Client端向其他的Eureka Server同步当前的Instance的信息。
EurekaClientConfigBean
在自动装配过程中, 需要从配置文件、命令行等多种渠道读取配置文件信息,Spring并没有采取Eureka Client本身的配置类,而是以EurekaClientConfgBean的方式作为配置类型, 代码如下:
@Bean
@ConditionalOnMissingBean(value = EurekaClientConfig.class, search = SearchStrategy.CURRENT)
public EurekaClientConfigBean eurekaClientConfigBean(ConfigurableEnvironment env) {
EurekaClientConfigBean client = new EurekaClientConfigBean();
if ("bootstrap".equals(this.env.getProperty("spring.config.name"))) {
// We don't register during bootstrap by default, but there will be another
// chance later.
client.setRegisterWithEureka(false);
}
return client;
}
@ConfigurationProperties(EurekaClientConfigBean.PREFIX)
public class EurekaClientConfigBean implements EurekaClientConfig, Ordered {...}
对于EurekaClientConfigBean而言,是一个Properties类型的承载类型, 将配置中eureka.client的配置,设置到当前类型中.
ApplicationInfoManager
@Configuration
// 依赖于Spring Cloud RefreshScope
@ConditionalOnRefreshScope
protected static class RefreshableEurekaClientConfiguration {
@Autowired
private ApplicationContext context;
@Autowired
private AbstractDiscoveryClientOptionalArgs<?> optionalArgs;
@Bean
@ConditionalOnMissingBean(value = ApplicationInfoManager.class, search = SearchStrategy.CURRENT)
@org.springframework.cloud.context.config.annotation.RefreshScope
@Lazy // 虽然加入了lazy, 但是eureka server在启动时,明确依赖了,因此会触发实例化
public ApplicationInfoManager eurekaApplicationInfoManager(
EurekaInstanceConfig config) {
InstanceInfo instanceInfo = new InstanceInfoFactory().create(config);
return new ApplicationInfoManager(config, instanceInfo);
}
}
ApplicationInfoManager通过InstanceInfoFactory工厂方法进行创建, 在SpringCloud中,因为Bean被RefreshScope注解修饰,能够是的ApplicationInfoManager的Bean能够在运行时环境更新,并让其他的依赖Bean使用的新的实例进行操作.
EurekaClient
@Configuration
@ConditionalOnRefreshScope
protected static class RefreshableEurekaClientConfiguration {
@Autowired
private ApplicationContext context;
@Autowired
private AbstractDiscoveryClientOptionalArgs<?> optionalArgs;
@Bean(destroyMethod = "shutdown")
@ConditionalOnMissingBean(value = EurekaClient.class, search = SearchStrategy.CURRENT)
@org.springframework.cloud.context.config.annotation.RefreshScope
@Lazy
public EurekaClient eurekaClient(ApplicationInfoManager manager,
EurekaClientConfig config, EurekaInstanceConfig instance,
@Autowired(required = false) HealthCheckHandler healthCheckHandler) {
// If we use the proxy of the ApplicationInfoManager we could run into a
// problem
// when shutdown is called on the CloudEurekaClient where the
// ApplicationInfoManager bean is
// requested but wont be allowed because we are shutting down. To avoid this
// we use the
// object directly.
ApplicationInfoManager appManager;
if (AopUtils.isAopProxy(manager)) {
appManager = ProxyUtils.getTargetObject(manager);
}
else {
appManager = manager;
}
CloudEurekaClient cloudEurekaClient = new CloudEurekaClient(appManager,
config, this.optionalArgs, this.context);
cloudEurekaClient.registerHealthCheck(healthCheckHandler);
return cloudEurekaClient;
}
}
EurekaClient与ApplicationInfoManager的创建很相似, 都是支持在运行时环境进行Bean实例的更新.
EurekaServerAutoConfiguration
在以上Eureka Client 相关基础Bean创建完成之后, 此时将会触发Eureka Server 相关的装配信息, Eureka Server中主要包含了几个重要的类型:
EurekaServerConfigBean
@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;
}
}
EurekaServerConfigBean类型,主要用于存储eureka.server相关的配置项,通过@ConfigurationProperties(EurekaServerConfigBean.PREFIX)的方式读取配置信息.
PeerAwareInstanceRegistry
@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());
}
该类主要用于存储Eureka Client上报的Instance 信息, 具体信息在后续会进行讲解.
PeerEurekaNodes
@Bean
@ConditionalOnMissingBean
public PeerEurekaNodes peerEurekaNodes(PeerAwareInstanceRegistry registry,
ServerCodecs serverCodecs,
ReplicationClientAdditionalFilters replicationClientAdditionalFilters) {
return new RefreshablePeerEurekaNodes(registry, this.eurekaServerConfig,
this.eurekaClientConfig, serverCodecs, this.applicationInfoManager,
replicationClientAdditionalFilters);
}
PeerEurekaNodes主要用来保存其他的Eureka Server节点信息,并定期做更新操作.
EurekaServerContext
@Bean
public EurekaServerContext eurekaServerContext(ServerCodecs serverCodecs,
PeerAwareInstanceRegistry registry, PeerEurekaNodes peerEurekaNodes) {
return new DefaultEurekaServerContext(this.eurekaServerConfig, serverCodecs,
registry, peerEurekaNodes, this.applicationInfoManager);
}
EurekaServerContext上下文信息,主要是在EurekaServer启动后,根据PeerEurekaNodes信息从其他Eureka Eureka Server上同步已有instance节点信息。
EurekaServerBootstrap
@Bean
public EurekaServerBootstrap eurekaServerBootstrap(PeerAwareInstanceRegistry registry,
EurekaServerContext serverContext) {
return new EurekaServerBootstrap(this.applicationInfoManager,
this.eurekaClientConfig, this.eurekaServerConfig, registry,
serverContext);
}
该类型主要负责Eureka Server启动后,从其他的Eureka Node上同步instance信息,并启动定时任务, 执行evict将已经下线的任务从registry中剔除。
将Jersey中的定义转换为Spring MVC
@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;
}
/**
* Construct a Jersey {@link javax.ws.rs.core.Application} with all the resources
* required by the Eureka server.
* @param environment an {@link Environment} instance to retrieve classpath resources
* @param resourceLoader a {@link ResourceLoader} instance to get classloader from
* @return created {@link Application} object
*/
@Bean
public javax.ws.rs.core.Application jerseyApplication(Environment environment,
ResourceLoader resourceLoader) {
ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(
false, environment);
// Filter to include only classes that have a particular annotation.
//
provider.addIncludeFilter(new AnnotationTypeFilter(Path.class));
provider.addIncludeFilter(new AnnotationTypeFilter(Provider.class));
// Find classes in Eureka packages (or subpackages)
//
Set<Class<?>> classes = new HashSet<>();
for (String basePackage : EUREKA_PACKAGES) {
Set<BeanDefinition> beans = provider.findCandidateComponents(basePackage);
for (BeanDefinition bd : beans) {
Class<?> cls = ClassUtils.resolveClassName(bd.getBeanClassName(),
resourceLoader.getClassLoader());
classes.add(cls);
}
}
// Construct the Jersey ResourceConfig
Map<String, Object> propsAndFeatures = new HashMap<>();
propsAndFeatures.put(
// Skip static content used by the webapp
ServletContainer.PROPERTY_WEB_PAGE_CONTENT_REGEX,
EurekaConstants.DEFAULT_PREFIX + "/(fonts|images|css|js)/.*");
DefaultResourceConfig rc = new DefaultResourceConfig(classes);
rc.setPropertiesAndFeatures(propsAndFeatures);
return rc;
}
Spring Cloud为了能够与Jersey的融合,一起使用,因此通过手动创建Applicaion的方式,将classpath下所有的@Path与@Provider注解的信息,映射到Servlet Filter中作统一的处理。
以上就是spring-cloud-netflix-eureka-server的整体装配过程,下一篇文章将具体介绍每个核心类型的作用以及三级缓存的作用。