在微服务开发过程中,始终少不了注册中心的存在,注册中心提供了服务的注册与发现机制,能够不需要代码改动,实现服务的横向扩展,同时也为微服务之间的调用解耦,避免了服务调用之间的高度依赖。本文主要从源码的角度出发,对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
的整体装配过程,下一篇文章将具体介绍每个核心类型的作用以及三级缓存的作用。