Eureka 源码解析 —— Eureka-Server 启动(二)之 EurekaBootStrap

摘要: 原创出处 http://www.iocoder.cn/Eureka/eureka-server-init-second/ 「芋道源码」欢迎转载,保留摘要,谢谢!

本文主要基于 Eureka 1.8.X 版本

  • 1. 概述
  • 2. EurekaBootStrap
    • 2.1 初始化 Eureka-Server 配置环境
    • 2.2 初始化 Eureka-Server 上下文

  • 3. Filter

    • 3.1 StatusFilter
    • 3.2 ServerRequestAuthFilter
    • 3.3 RateLimitingFilter
    • 3.4 GzipEncodingEnforcingFilter
    • 3.5 ServletContainer

  • 1. 概述

    本文接《Eureka 源码解析 —— Eureka-Server 启动(一)之 EurekaServerConfig》,主要分享 Eureka-Server 启动的过程的第二部分 —— EurekaBootStrap

    考虑到整个初始化的过程中涉及的代码特别多,拆分成两两篇文章:

    • ServerConfig
    • 【本文】EurekaBootStrap

    推荐 Spring Cloud 书籍

    • 请支持正版。下载盗版,等于主动编写低级 BUG
    • 程序猿DD —— 《Spring Cloud微服务实战》
    • 周立 —— 《Spring Cloud与Docker微服务架构实战》
    • 两书齐买,京东包邮。

    2. EurekaBootStrap

    com.netflix.eureka.EurekaBootStrap,Eureka-Server 启动入口

    Eureka 源码解析 —— Eureka-Server 启动(二)之 EurekaBootStrap_第1张图片

    EurekaBootStrap 实现了 javax.servlet.ServletContextListener 接口,在 Servlet 容器( 例如 Tomcat、Jetty )启动时,调用 #contextInitialized() 方法,初始化 Eureka-Server,实现代码如下:

             
            
            
            
    public class EurekaBootStrap implements ServletContextListener {
    // 省略无关变量和方法
    @Override
    public void contextInitialized(ServletContextEvent event) {
    try {
    // 初始化 Eureka-Server 配置环境
    initEurekaEnvironment();
    // 初始化 Eureka-Server 上下文
    initEurekaServerContext();
    ServletContext sc = event.getServletContext();
    sc.setAttribute(EurekaServerContext.class.getName(), serverContext);
    } catch (Throwable e) {
    logger.error( "Cannot bootstrap eureka server :", e);
    throw new RuntimeException( "Cannot bootstrap eureka server :", e);
    }
    }
    }

    • 调用 #initEurekaEnvironment() 方法,初始化 Eureka-Server 配置环境。
    • 调用 #initEurekaServerContext() 方法,初始化 Eureka-Server 上下文。

    2.1 初始化 Eureka-Server 配置环境

             
            
            
            
    // EurekaBootStrap.java
    /
    * 部署环境 - 测服
    /
    private static final String TEST = "test";
    private static final String ARCHAIUS_DEPLOYMENT_ENVIRONMENT = "archaius.deployment.environment";
    private static final String EUREKA_ENVIRONMENT = "eureka.environment";
    /
    部署数据中心 - CLOUD
    /
    private static final String CLOUD = "cloud";
    /*
    * 部署数据中心 - 默认
    */
    private static final String DEFAULT = "default";
    private static final String ARCHAIUS_DEPLOYMENT_DATACENTER = "archaius.deployment.datacenter";
    private static final String EUREKA_DATACENTER = "eureka.datacenter";
    protected void initEurekaEnvironment() throws Exception {
    logger.info( "Setting the eureka configuration..");
    // 设置配置文件的数据中心
    String dataCenter = ConfigurationManager.getConfigInstance().getString(EUREKA_DATACENTER);
    if (dataCenter == null) {
    logger.info( "Eureka data center value eureka.datacenter is not set, defaulting to default");
    ConfigurationManager.getConfigInstance().setProperty(ARCHAIUS_DEPLOYMENT_DATACENTER, DEFAULT);
    } else {
    ConfigurationManager.getConfigInstance().setProperty(ARCHAIUS_DEPLOYMENT_DATACENTER, dataCenter);
    }
    // 设置配置文件的环境
    String environment = ConfigurationManager.getConfigInstance().getString(EUREKA_ENVIRONMENT);
    if (environment == null) {
    ConfigurationManager.getConfigInstance().setProperty(ARCHAIUS_DEPLOYMENT_ENVIRONMENT, TEST);
    logger.info( "Eureka environment value eureka.environment is not set, defaulting to test");
    }
    }

    • 设置基于 Netflix Archaius 实现的配置文件的上下文,从而读取合适的配置文件。大多数情况下,只需要设置 EUREKA_ENVIRONMENT 即可,不同的服务器环境( 例如,PROD / TEST 等) 读取不同的配置文件。实现原理,在《Eureka 源码解析 —— Eureka-Client 初始化(一)之 EurekaInstanceConfig》「2.4 PropertiesInstanceConfig」有详细解析。
    • 感兴趣的也可以阅读:《Netflix Archaius 官方文档 —— Deployment context》。

    2.2 初始化 Eureka-Server 上下文

    EurekaBootStrap 的 #initEurekaServerContext() 方法的实现代码相对较多,已经将代码切块 + 中文注册,点击 EurekaBootStrap 链接,对照下面每个小结阅读理解。


    2.2.1 创建 Eureka-Server 配置

             
            
            
            
    EurekaServerConfig eurekaServerConfig = new DefaultEurekaServerConfig();
    • 在 《Eureka 源码解析 —— Eureka-Server 启动(一)之 ServerConfig》「2.3 DefaultEurekaServerConfig」 有详细解析。

    2.2.2 Eureka-Server 请求和响应的数据兼容

             
            
            
            
    // For backward compatibility
    JsonXStream.getInstance().registerConverter( new V1AwareInstanceInfoConverter(), XStream.PRIORITY_VERY_HIGH);
    XmlXStream.getInstance().registerConverter( new V1AwareInstanceInfoConverter(), XStream.PRIORITY_VERY_HIGH);

    • 目前 Eureka-Server 提供 V2 版本 API ,如上代码主要对 V1 版本 API 做兼容。可以选择跳过。

    2.2.3 创建 Eureka-Server 请求和响应编解码器

             
            
            
            
    logger.info( "Initializing the eureka client...");
    logger.info(eurekaServerConfig.getJsonCodecName());
    ServerCodecs serverCodecs = new DefaultServerCodecs(eurekaServerConfig);

    2.2.4 创建 Eureka-Client

             
            
            
            
    ApplicationInfoManager applicationInfoManager;
    if (eurekaClient == null) {
    EurekaInstanceConfig instanceConfig = isCloud(ConfigurationManager.getDeploymentContext())
    ? new CloudInstanceConfig()
    : new MyDataCenterInstanceConfig();
    applicationInfoManager = new ApplicationInfoManager(
    instanceConfig, new EurekaConfigBasedInstanceInfoProvider(instanceConfig).get());
    EurekaClientConfig eurekaClientConfig = new DefaultEurekaClientConfig();
    eurekaClient = new DiscoveryClient(applicationInfoManager, eurekaClientConfig);
    } else {
    applicationInfoManager = eurekaClient.getApplicationInfoManager();
    }

    • Eureka-Server 内嵌 Eureka-Client,用于和 Eureka-Server 集群里其他节点通信交互。
    • Eureka-Client 的初始化过程,在《Eureka 源码解析 —— Eureka-Client 初始化(三)之 EurekaClient》「3. DiscoveryClient」有详细解析。
    • Eureka-Client 也可以通过 EurekaBootStrap 构造方法传递,实现代码如下:

                 
                
                
                
      public class EurekaBootStrap implements ServletContextListener {
      private EurekaClient eurekaClient;
      public EurekaBootStrap(EurekaClient eurekaClient) {
      this.eurekaClient = eurekaClient;
      }
      }
      • 大多数情况下用不到


    2.2.5 创建应用实例信息的注册表

             
            
            
            
    PeerAwareInstanceRegistry registry;
    if (isAws(applicationInfoManager.getInfo())) { // AWS 相关,跳过
    registry = new AwsInstanceRegistry(
    eurekaServerConfig,
    eurekaClient.getEurekaClientConfig(),
    serverCodecs,
    eurekaClient
    );
    awsBinder = new AwsBinderDelegate(eurekaServerConfig, eurekaClient.getEurekaClientConfig(), registry, applicationInfoManager);
    awsBinder.start();
    } else {
    registry = new PeerAwareInstanceRegistryImpl(
    eurekaServerConfig,
    eurekaClient.getEurekaClientConfig(),
    serverCodecs,
    eurekaClient
    );
    }
    • 应用实例信息的注册表类关系图如下:

      Eureka 源码解析 —— Eureka-Server 启动(二)之 EurekaBootStrap_第2张图片

    • 本文不展开分享,在《Eureka 源码解析 —— 注册表 InstanceRegistry 类关系》详细解析。

    2.2.6 创建 Eureka-Server 集群节点集合

             
            
            
            
    PeerEurekaNodes peerEurekaNodes = getPeerEurekaNodes(
    registry,
    eurekaServerConfig,
    eurekaClient.getEurekaClientConfig(),
    serverCodecs,
    applicationInfoManager
    );

    • com.netflix.eureka.cluster.PeerEurekaNodes,Eureka-Server 集群节点集合,在《Eureka 源码解析 —— Eureka-Server 集群同步》详细解析。

    2.2.7 创建 Eureka-Server 上下文

             
            
            
            
    serverContext = new DefaultEurekaServerContext(
    eurekaServerConfig,
    serverCodecs,
    registry,
    peerEurekaNodes,
    applicationInfoManager
    );

    • com.netflix.eureka.EurekaServerContext,Eureka-Server 上下文接口,提供Eureka-Server 内部各组件对象的初始化关闭获取等方法。
    • com.netflix.eureka.EurekaServerContext.DefaultEurekaServerContext,Eureka-Server 上下文实现类,实现代码如下:

                 
                
                
                
      public class DefaultEurekaServerContext implements EurekaServerContext {
      /**
      * Eureka-Server 配置
      */
      private final EurekaServerConfig serverConfig;
      /**
      * Eureka-Server 请求和响应编解码器
      */
      private final ServerCodecs serverCodecs;
      /**
      * 应用实例信息的注册表
      */
      private final PeerAwareInstanceRegistry registry;
      /**
      * Eureka-Server 集群节点集合
      */
      private final PeerEurekaNodes peerEurekaNodes;
      /**
      * 应用实例信息管理器
      */
      private final ApplicationInfoManager applicationInfoManager;
      // .... 省略方法
      }

    2.2.8 初始化 EurekaServerContextHolder

             
            
            
            
    EurekaServerContextHolder.initialize(serverContext);

    • com.netflix.eureka.EurekaServerContextHolder,Eureka-Server 上下文持有者。通过它,可以很方便的获取到 Eureka-Server 上下文,实现代码如下:

                 
                
                
                
      public class EurekaServerContextHolder {
      /**
      * 持有者
      */
      private static EurekaServerContextHolder holder;
      /**
      * Eureka-Server 上下文
      */
      private final EurekaServerContext serverContext;
      private EurekaServerContextHolder(EurekaServerContext serverContext) {
      this.serverContext = serverContext;
      }
      public EurekaServerContext getServerContext() {
      return this.serverContext;
      }
      /**
      * 初始化
      *
      * @param serverContext Eureka-Server 上下文
      */
      public static synchronized void initialize(EurekaServerContext serverContext) {
      holder = new EurekaServerContextHolder(serverContext);
      }
      public static EurekaServerContextHolder getInstance() {
      return holder;
      }
      }

    2.2.9 初始化 Eureka-Server 上下文

             
            
            
            
    serverContext.initialize();
    logger.info( "Initialized server context");

    • 调用 ServerContext#initialize() 方法,初始化 Eureka-Server 上下文,实现代码如下:

                 
                
                
                
      // DefaultEurekaServerContext.java
      @Override
      public void initialize() throws Exception {
      logger.info( "Initializing ...");
      // 启动 Eureka-Server 集群节点集合(复制)
      peerEurekaNodes.start();
      // 初始化 应用实例信息的注册表
      registry.init(peerEurekaNodes);
      logger.info( "Initialized");
      }

    2.2.10 从其他 Eureka-Server 拉取注册信息

             
            
            
            
    // Copy registry from neighboring eureka node
    int registryCount = registry.syncUp();
    registry.openForTraffic(applicationInfoManager, registryCount);

    • 本文不展开分享,在 《Eureka 源码解析 —— Eureka-Server 集群同步》详细解析。

    2.2.11 注册监控

             
            
            
            
    // Register all monitoring statistics.
    EurekaMonitors.registerAllStats();

    • 配合 Netflix Servo 实现监控信息采集。

    3. Filter

    Eureka-Server 过滤器( javax.servlet.Filter ) 顺序如下:

    • StatusFilter
    • ServerRequestAuthFilter
    • RateLimitingFilter
    • GzipEncodingEnforcingFilter
    • ServletContainer

    3.1 StatusFilter

    com.netflix.eureka.StatusFilter,Eureka-Server 状态过滤器。当 Eureka-Server 未处于开启( InstanceStatus.UP )状态,返回 HTTP 状态码 307 重定向,实现代码如下:


             
            
            
            
    // StatusFilter.java
    private static final int SC_TEMPORARY_REDIRECT = 307;
    public void doFilter(ServletRequest request, ServletResponse response,
    FilterChain chain) throws IOException, ServletException {
    InstanceInfo myInfo = ApplicationInfoManager.getInstance().getInfo();
    InstanceStatus status = myInfo.getStatus();
    if (status != InstanceStatus.UP && response instanceof HttpServletResponse) {
    HttpServletResponse httpRespone = (HttpServletResponse) response;
    httpRespone.sendError(SC_TEMPORARY_REDIRECT,
    "Current node is currently not ready to serve requests -- current status: "
    + status + " - try another DS node: ");
    }
    chain.doFilter(request, response);
    }

    3.2 ServerRequestAuthFilter

    com.netflix.eureka.ServerRequestAuthFilter,Eureka-Server 请求认证过滤器。Eureka-Server 未实现认证。目前打印访问的客户端名和版本号,配合 Netflix Servo 实现监控信息采集。实现代码如下:


    
        
         
        
        
        
              
             
             
             
    // ServerRequestAuthFilter.java
    protected void logAuth(ServletRequest request) {
    if (serverConfig.shouldLogIdentityHeaders()) {
    if (request instanceof HttpServletRequest) {
    HttpServletRequest httpRequest = (HttpServletRequest) request;
    String clientName = getHeader(httpRequest, AbstractEurekaIdentity.AUTH_NAME_HEADER_KEY);
    String clientVersion = getHeader(httpRequest, AbstractEurekaIdentity.AUTH_VERSION_HEADER_KEY);
    DynamicCounter.increment(MonitorConfig.builder(NAME_PREFIX + clientName + "-" + clientVersion).build());
    }
    }
    }

    3.3 RateLimitingFilter

    com.netflix.eureka.RateLimitingFilter,请求限流过滤器。在《Eureka 源码解析 —— 基于令牌桶算法的 RateLimiter》详细解析。


    3.4 GzipEncodingEnforcingFilter

    com.netflix.eureka.GzipEncodingEnforcingFilter,GZIP 编码过滤器。


    3.5 ServletContainer

    com.sun.jersey.spi.container.servlet.ServletContainer,Jersey MVC 请求过滤器。

    • Jersey MVC 模式如下图:

      FROM 《Jersey框架的MVC》
      Eureka 源码解析 —— Eureka-Server 启动(二)之 EurekaBootStrap_第3张图片

    • com.netflix.eureka.resources 包里,有所有的 Eureka-Server Jersey Resource ( Controller )。

    • 过滤器在 web.xml 配置如下:

                 
                
                
                
      <filter>
      <filter-name>jersey filter-name>
      <filter-class>com.sun.jersey.spi.container.servlet.ServletContainer filter-class>
      <init-param>
      <param-name>com.sun.jersey.config.property.WebPageContentRegex param-name>
      <param-value>/(flex|images|js|css|jsp)/.* param-value>
      init-param>
      <init-param>
      <param-name>com.sun.jersey.config.property.packages param-name>
      <param-value>com.sun.jersey;com.netflix param-value>
      init-param>
      <init-param>
      <param-name>com.sun.jersey.spi.container.ContainerRequestFilters param-name>
      <param-value>com.sun.jersey.api.container.filter.GZIPContentEncodingFilter param-value>
      init-param>
      <init-param>
      <param-name>com.sun.jersey.spi.container.ContainerResponseFilters param-name>
      <param-value>com.sun.jersey.api.container.filter.GZIPContentEncodingFilter param-value>
      init-param>
      filter>
      <filter-mapping>
      <filter-name>jersey filter-name>
      <url-pattern>/* url-pattern>
      filter-mapping>

你可能感兴趣的:(源码,Eureka)