Eureka Server 启动过程源码分析

Eureka Server 启动过程

SpringBoot 自动装配

入口:SpringCloud 充分利用了 SpringBoot 的自动装配特点,在 eureka-server 的jar包下,META-INFO 文件夹下有配置文件 spring.factories

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

SpringBoot 应用启动时会加载 EurekaServerAutoConfiguration

EurekaServerAutoConfiguration类

@Configuration
@Import(EurekaServerInitializerConfiguration.class)
@ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class)
@EnableConfigurationProperties({ EurekaDashboardProperties.class,
      InstanceRegistryProperties.class })
@PropertySource("classpath:/eureka/server.properties")
public class EurekaServerAutoConfiguration extends WebMvcConfigurerAdapter {
	......
}

开启EurekaServer

EurekaServerAutoConfiguration 中:

  • @ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class) 表示要装配该类的前提条件是需要有 Marker 这个 Bean。

    Marker 这个 Bean 其实是由 @EnableEurekaServer 注解导入了 @EurekaServerMarkerConfiguration

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Import(EurekaServerMarkerConfiguration.class)
    public @interface EnableEurekaServer {
    
    }
    
    @Configuration
    public class EurekaServerMarkerConfiguration {
    
       @Bean
       public Marker eurekaServerMarkerBean() {
          return new Marker();
       }
    
       class Marker {
       }
    }
    

也就是说只有添加了 @EnableEurekaServer 注解,才会有后续的动作,这是成为⼀个EurekaServer的前提

EurekaServerAutoConfiguration 详解

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

该方法能生成一个对外的接口(仪表盘/后台界面),可以在配置文件中 eureka.dashboard.enabled=false 关闭

对等节点感知实例注册器
@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());
}

集群模式下注册服务使用到的注册器。

EurekaServer 集群中各个节点是对等的,没有主从之分。

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

注入 PeerEurekaNodes ,辅助封装对等节点相关的信息和操作(比如更新集群当中的对等节点)

com.netflix.eureka.cluster.PeerEurekaNodes#start 方法中

public void start() {
    taskExecutor = Executors.newSingleThreadScheduledExecutor(
            new ThreadFactory() {
                @Override
                public Thread newThread(Runnable r) {
                    Thread thread = new Thread(r, "Eureka-PeerNodesUpdater");
                    thread.setDaemon(true);
                    return thread;
                }
            }
    );
    try {
        updatePeerEurekaNodes(resolvePeerUrls());
        Runnable peersUpdateTask = new Runnable() {
            @Override
            public void run() {
                try {
                    updatePeerEurekaNodes(resolvePeerUrls());
                } catch (Throwable e) {
                    logger.error("Cannot update the replica Nodes", e);
                }

            }
        };
        taskExecutor.scheduleWithFixedDelay(
                peersUpdateTask,
                serverConfig.getPeerEurekaNodesUpdateIntervalMs(),
                serverConfig.getPeerEurekaNodesUpdateIntervalMs(),
                TimeUnit.MILLISECONDS
        );
    } catch (Exception e) {
        throw new IllegalStateException(e);
    }
    for (PeerEurekaNode node : peerEurekaNodes) {
        logger.info("Replica node URL:  {}", node.getServiceUrl());
    }
}

Executors.newSingleThreadScheduledExecutor 构建了线程池

updatePeerEurekaNodes(resolvePeerUrls()) :更新对等节点信息(因为 EurekaServer 集群节点可能发生变化)

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

进入看看 DefaultEurekaServerContext 上下文做了些什么

@Singleton
public class DefaultEurekaServerContext implements EurekaServerContext {
	......

    @PostConstruct
    @Override
    public void initialize() {
        logger.info("Initializing ...");
        peerEurekaNodes.start();
        try {
            registry.init(peerEurekaNodes);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        logger.info("Initialized");
    }

	......
}

com.netflix.eureka.DefaultEurekaServerContext#initialize 方法中,DefaultEurekaServerContext 对象构建完毕后会执行 initialize() 方法,启动刚刚的 PeerEurekaNodes#start()

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

注入 EurekaServerBootstrap 类,后续启动要使用该对象

发布restful 服务接口
@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;
}

Jersey 是一个 rest 框架帮助Eureka Server 发布 restful 服务接口,类似 SpringMVC

导入EurekaServerInitializerConfiguration
@Import(EurekaServerInitializerConfiguration.class)

EurekaServerInitializerConfiguration 的类头部,还导入了一个 EurekaServerInitializerConfiguration

该类实现了 SmartLifecycle 接口,可以在 Spring 容器的 Bean 创建完成之后做一些事情(start方法)

@Configuration
public class EurekaServerInitializerConfiguration
      implements ServletContextAware, SmartLifecycle, Ordered {
	......
        
   @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();
   }


}

重点关注 eurekaServerBootstrap.contextInitialized() ,这里是 初始化 EurekaServerContext 的细节

public void contextInitialized(ServletContext context) {
   try {
      initEurekaEnvironment();// 初始化环境信息
      initEurekaServerContext(); // 初始化context细节

      context.setAttribute(EurekaServerContext.class.getName(), this.serverContext);
   }
   catch (Throwable e) {
      log.error("Cannot bootstrap eureka server :", e);
      throw new RuntimeException("Cannot bootstrap eureka server :", e);
   }
}
protected void initEurekaServerContext() throws Exception {
   // For backward compatibility
   JsonXStream.getInstance().registerConverter(new V1AwareInstanceInfoConverter(),
         XStream.PRIORITY_VERY_HIGH);
   XmlXStream.getInstance().registerConverter(new V1AwareInstanceInfoConverter(),
         XStream.PRIORITY_VERY_HIGH);

   if (isAws(this.applicationInfoManager.getInfo())) {
      this.awsBinder = new AwsBinderDelegate(this.eurekaServerConfig,
            this.eurekaClientConfig, this.registry, this.applicationInfoManager);
      this.awsBinder.start();
   }
	// 为非 IOC 容器提供获取 serverContext对象的接口
   EurekaServerContextHolder.initialize(this.serverContext);

   log.info("Initialized server context");

   // Copy registry from neighboring eureka node
    // 某一个server实例启动的时候,从集群中其他的server拷贝注册信息过来(同步),每一个server 对于其他server来说也是客户端
   int registryCount = this.registry.syncUp();
   this.registry.openForTraffic(this.applicationInfoManager, registryCount);//更改实例状态为UP,对外提供服务

   // Register all monitoring statistics.
   EurekaMonitors.registerAllStats();//注册统计器
}

syncUp() 方法

@Override
public int syncUp() {
    // Copy entire entry from neighboring DS node
    int count = 0;// 计数

    // 因为可能无法连接上远程server,做重试
    for (int i = 0; ((i < serverConfig.getRegistrySyncRetries()) && (count == 0)); i++) {
        if (i > 0) {
            try {
                Thread.sleep(serverConfig.getRegistrySyncRetryWaitMs());
            } catch (InterruptedException e) {
                logger.warn("Interrupted during registry transfer..");
                break;
            }
        }
        // 获取到其他server的注册表信息
        Applications apps = eurekaClient.getApplications();
        for (Application app : apps.getRegisteredApplications()) {
            for (InstanceInfo instance : app.getInstances()) {
                try {
                    if (isRegisterable(instance)) {
                        // 把从远程获取过来的注册信息注册到自己的注册表中(map)
                        register(instance, instance.getLeaseInfo().getDurationInSecs(), true);
                        count++;
                    }
                } catch (Throwable t) {
                    logger.error("During DS init copy", t);
                }
            }
        }
    }
    return count;
}

继续进入 com.netflix.eureka.registry.AbstractInstanceRegistry#register (提供实例注册功能)

AbstractInstanceRegistry 中,声明了 注册表

private final ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>> registry
        = new ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>>();
public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) {
    try {
        read.lock();
        Map<String, Lease<InstanceInfo>> gMap = registry.get(registrant.getAppName());
        REGISTER.increment(isReplication);
        if (gMap == null) {
            final ConcurrentHashMap<String, Lease<InstanceInfo>> gNewMap = new ConcurrentHashMap<String, Lease<InstanceInfo>>();
            gMap = registry.putIfAbsent(registrant.getAppName(), gNewMap);
            if (gMap == null) {
                gMap = gNewMap;
            }
        }
        // 判断服务中有没有该实例,没有就创建 Lease 对象
        Lease<InstanceInfo> existingLease = gMap.get(registrant.getId());
        // Retain the last dirty timestamp without overwriting it, if there is already a lease
        if (existingLease != null && (existingLease.getHolder() != null)) {
            Long existingLastDirtyTimestamp = existingLease.getHolder().getLastDirtyTimestamp();
            Long registrationLastDirtyTimestamp = registrant.getLastDirtyTimestamp();

        ......
            
    } finally {
        read.unlock();
    }
}

继续研究 com.netflix.eureka.registry.PeerAwareInstanceRegistryImpl#openForTraffic 方法

@Override
public void openForTraffic(ApplicationInfoManager applicationInfoManager, int count) {
    // Renewals happen every 30 seconds and for a minute it should be a factor of 2.
    this.expectedNumberOfClientsSendingRenews = count;
    updateRenewsPerMinThreshold();
    logger.info("Got {} instances from neighboring DS node", count);
    logger.info("Renew threshold is: {}", numberOfRenewsPerMinThreshold);
    this.startupTime = System.currentTimeMillis();
    if (count > 0) {
        this.peerInstancesTransferEmptyOnStartup = false;
    }
    DataCenterInfo.Name selfName = applicationInfoManager.getInfo().getDataCenterInfo().getName();
    boolean isAws = Name.Amazon == selfName;
    if (isAws && serverConfig.shouldPrimeAwsReplicaConnections()) {
        logger.info("Priming AWS connections for all replicas..");
        primeAwsReplicas(applicationInfoManager);
    }
    // 更改实例状态为UP
    logger.info("Changing status to UP");
    applicationInfoManager.setInstanceStatus(InstanceStatus.UP);
    // 开启定时任务,默认每隔60秒进行一次服务时效剔除
    super.postInit();
}
protected void postInit() {
    renewsLastMin.start();
    if (evictionTaskRef.get() != null) {
        evictionTaskRef.get().cancel();
    }
    // 任务逻辑
    evictionTaskRef.set(new EvictionTask());
    // 使用定时器调度
    evictionTimer.schedule(evictionTaskRef.get(),
            serverConfig.getEvictionIntervalTimerInMs(),
            serverConfig.getEvictionIntervalTimerInMs());
}

这个时候,EurekaServer 已经启动成功了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值