1. Eureka架构图

image.png

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) {

    1. SpringApplication.run(EurekaApplication.class, args);

    }

}

  1. ```java
  2. @Target(ElementType.TYPE)
  3. @Retention(RetentionPolicy.RUNTIME)
  4. @Documented
  5. @Import(EurekaServerMarkerConfiguration.class)
  6. public @interface EnableEurekaServer {
  7. }
  8. @Configuration
  9. public class EurekaServerMarkerConfiguration {
  10. @Bean
  11. public Marker eurekaServerMarkerBean() {
  12. return new Marker();
  13. }
  14. class Marker {
  15. }
  16. }

看上面的代码可以得知通过@Import 注解将EurekaServerMarkerConfiguration这个类导入到spring容器中,

然后EurekaServerMarkerConfiguration类中声明了Marker类的bean对象。这个bean对象下面会用到。

当注册中心启动时,了解springboot自动装配我们知道,系统会加载META-INF/spring.factories下的配置

  1. org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  2. org.springframework.cloud.netflix.eureka.server.EurekaServerAutoConfiguration

根据spring.factories配置文件会加载EurekaServerAutoConfiguration这个类

  1. @Configuration
  2. @Import(EurekaServerInitializerConfiguration.class)
  3. // 有Marker的这个bean才会将EurekaServerAutoConfiguration注入到spring容器中
  4. @ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class)
  5. @EnableConfigurationProperties({ EurekaDashboardProperties.class,
  6. InstanceRegistryProperties.class })
  7. @PropertySource("classpath:/eureka/server.properties")
  8. public class EurekaServerAutoConfiguration extends WebMvcConfigurerAdapter {

通过代码可以得知,spring会判断是否存在Marker的bean对象,存在则将EurekaServerAutoConfiguration这个类注入到spring容器中。在EurekaServerAutoConfiguration类中我们并没有发现启动Eureka的代码,下面我们看下EurekaServerInitializerConfiguration这个类

  1. /*
  2. * Copyright 2013-2020 the original author or authors.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * https://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package org.springframework.cloud.netflix.eureka.server;
  17. import javax.servlet.ServletContext;
  18. import com.netflix.eureka.EurekaServerConfig;
  19. import org.apache.commons.logging.Log;
  20. import org.apache.commons.logging.LogFactory;
  21. import org.springframework.beans.factory.annotation.Autowired;
  22. import org.springframework.cloud.netflix.eureka.server.event.EurekaRegistryAvailableEvent;
  23. import org.springframework.cloud.netflix.eureka.server.event.EurekaServerStartedEvent;
  24. import org.springframework.context.ApplicationContext;
  25. import org.springframework.context.ApplicationEvent;
  26. import org.springframework.context.SmartLifecycle;
  27. import org.springframework.context.annotation.Configuration;
  28. import org.springframework.core.Ordered;
  29. import org.springframework.web.context.ServletContextAware;
  30. /**
  31. * @author Dave Syer
  32. */
  33. @Configuration(proxyBeanMethods = false)
  34. public class EurekaServerInitializerConfiguration
  35. implements ServletContextAware, SmartLifecycle, Ordered {
  36. private static final Log log = LogFactory
  37. .getLog(EurekaServerInitializerConfiguration.class);
  38. @Autowired
  39. private EurekaServerConfig eurekaServerConfig;
  40. private ServletContext servletContext;
  41. @Autowired
  42. private ApplicationContext applicationContext;
  43. @Autowired
  44. private EurekaServerBootstrap eurekaServerBootstrap;
  45. private boolean running;
  46. private int order = 1;
  47. @Override
  48. public void setServletContext(ServletContext servletContext) {
  49. this.servletContext = servletContext;
  50. }
  51. @Override
  52. public void start() {
  53. new Thread(() -> {
  54. try {
  55. // TODO: is this class even needed now?
  56. eurekaServerBootstrap.contextInitialized(
  57. EurekaServerInitializerConfiguration.this.servletContext);
  58. log.info("Started Eureka Server");
  59. publish(new EurekaRegistryAvailableEvent(getEurekaServerConfig()));
  60. EurekaServerInitializerConfiguration.this.running = true;
  61. publish(new EurekaServerStartedEvent(getEurekaServerConfig()));
  62. }
  63. catch (Exception ex) {
  64. // Help!
  65. log.error("Could not initialize Eureka servlet context", ex);
  66. }
  67. }).start();
  68. }
  69. private EurekaServerConfig getEurekaServerConfig() {
  70. return this.eurekaServerConfig;
  71. }
  72. private void publish(ApplicationEvent event) {
  73. this.applicationContext.publishEvent(event);
  74. }
  75. @Override
  76. public void stop() {
  77. this.running = false;
  78. eurekaServerBootstrap.contextDestroyed(this.servletContext);
  79. }
  80. @Override
  81. public boolean isRunning() {
  82. return this.running;
  83. }
  84. @Override
  85. public int getPhase() {
  86. return 0;
  87. }
  88. @Override
  89. public boolean isAutoStartup() {
  90. return true;
  91. }
  92. @Override
  93. public void stop(Runnable callback) {
  94. callback.run();
  95. }
  96. @Override
  97. public int getOrder() {
  98. return this.order;
  99. }
  100. }

通过上面代码可以看出EurekaServerInitializerConfiguration也是一个配置类,同时它实现了ServletContextAware接口,可以在Servlet容器启动后得到ServletContext容器上下文;它还实现了SmartLifecycle,这样在spring 生命周期中会调用这个类相关的方法。比如在spring初始化时,会调用它start方法。

  1. @Override
  2. public void start() {
  3. new Thread(new Runnable() {
  4. @Override
  5. public void run() {
  6. try {
  7. //TODO: is this class even needed now?
  8. eurekaServerBootstrap.contextInitialized(EurekaServerInitializerConfiguration.this.servletContext);
  9. log.info("Started Eureka Server");
  10. publish(new EurekaRegistryAvailableEvent(getEurekaServerConfig()));
  11. EurekaServerInitializerConfiguration.this.running = true;
  12. publish(new EurekaServerStartedEvent(getEurekaServerConfig()));
  13. }
  14. catch (Exception ex) {
  15. // Help!
  16. log.error("Could not initialize Eureka servlet context", ex);
  17. }
  18. }
  19. }).start();
  20. }

start方法中启动了一个后台线程,它会执行这一行代码。

  1. eurekaServerBootstrap.contextInitialized(EurekaServerInitializerConfiguration.this.servletContext);
  1. public void contextInitialized(ServletContext context) {
  2. try {
  3. initEurekaEnvironment();// 初始化eureka环境
  4. initEurekaServerContext();// 初始化eureka上下文
  5. context.setAttribute(EurekaServerContext.class.getName(), this.serverContext);
  6. }
  7. catch (Throwable e) {
  8. log.error("Cannot bootstrap eureka server :", e);
  9. throw new RuntimeException("Cannot bootstrap eureka server :", e);
  10. }
  11. }

上面的代码会调用initEurekaEnvironment方法初始化eureka环境,调用initEurekaServerContext方法初始化eureka上下文, 至此Eureka Server就随着Spring容器的一起启起了。