注册中心 Eureka 源码解析 —— Eureka-Server 启动(二)之 EurekaBootStrap

本文主要基于 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
  • 666. 彩蛋

1. 概述

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

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

  • ServerConfig
  • 【本文】EurekaBootStrap

推荐 Spring Cloud 书籍

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

推荐 Spring Cloud 视频

  • Java 微服务实践 - Spring Boot
  • Java 微服务实践 - Spring Cloud
  • Java 微服务实践 - Spring Boot / Spring Cloud

2. EurekaBootStrap

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

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 源码解析 —— 注册表 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 实现监控信息采集。实现代码如下:

```Java
// 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》

  • 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>   
  <!-- GZIP content encoding/decoding -->   
 <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>

原文发布于微信公众号 - 芋道源码(YunaiV)

原文发表时间:2018-03-28

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏容器云生态

使用Dockerfile构建自己的etcd镜像

本篇文章手把手教你如何使用Dockerfile构建自己etcd镜像,并且已经提供基于etcd3.0.10 的image供读者使用,用户可以快速使用docker...

3056
来自专栏后台及大数据开发

kubernetes集群搭建(6):kubernetes基本使用演示

流程: 用户访问client应用,client应用中调用server应用,由于部署了多节点,client在访问server时应该配置server 暴露的虚拟IP...

1392
来自专栏Hongten

How to create a project with Oracle Policy Modeling

This blog is about how to create a project with Oracle Policy Modeling.

772
来自专栏听雨堂

网页链接打开程序

就像电驴那样: 网页链接地址是 : ed2k:// ¦file ¦Dracula.1992.CE.2AUDIO.DVDRip.DTS.X264.GUEVA...

21610
来自专栏扎心了老铁

springboot kafka集成(实现producer和consumer)

本文介绍如何在springboot项目中集成kafka收发message。 1、先解决依赖 springboot相关的依赖我们就不提了,和kafka相关的只依赖...

9445
来自专栏用户2442861的专栏

cmake教程4(find_package使用)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/haluoluo211/article/d...

4463
来自专栏Python与爬虫

佛系编程[如何创建一个安全可靠的应用程序]

推荐最近在GitHub上很火的一个项目,按照介绍,你也可以创建一个安全可靠的应用程序 项目地址在>>> nocode https://github.com/k...

4459
来自专栏软件开发

Spring MVC 学习总结(十一)——IDEA+Maven+多模块实现SSM框架集成

与SSH(Struts/Spring/Hibernate/)一样,Spring+SpringMVC+MyBatis也有一个简称SSM,Spring实现业务对象管...

2902
来自专栏Laoqi's Linux运维专列

LAMP安装mysql 时遇到的问题汇总

1: 缺少 libaio 包, libaio是Linux下的一个异步非阻塞方式读写文件的接口。 1 2 3 [[email protect...

3926
来自专栏菩提树下的杨过

Spring Security笔记:使用数据库进行用户认证(form login using database)

在前一节,学习了如何自定义登录页,但是用户名、密码仍然是配置在xml中的,这样显然太非主流,本节将学习如何把用户名/密码/角色存储在db中,通过db来实现用户认...

1981

扫码关注云+社区

领取腾讯云代金券