【Eureka源码解析-2】eureka-server模块源码解析(一)

1、Netflix Eureka源码项目结构说明

在这里插入图片描述
对核心模块简要说明:
(1)eureka-client:这个就是指的eureka的客户端,注册到eureka上面去的一个服务,就是一个eureka client,无论是你要注册,还是要发现别的服务,无论是服务提供者还是服务消费者,都是一个eureka客户端。

(2)eureka-core:这个就是指的eureka的服务端,其实就是eureka的注册中心。

(3)eureka-resources:这个是基于jsp开发的eureka控制台,web页面,上面你可以看到各种注册服务。

(4)eureka-server:这是把eureka-client、eureka-core、eureka-resources打包成了一个war包,也就是说eureka-server自己本身也是一个eureka-client,同时也是注册中心,同时也提供eureka控制台。真正的使用的注册中心。

(5)eureka-examples:eureka使用的例子。

(6)eureka-test-utils:eureka的单元测试工具类。

咱们来一点点儿看,首先要看的哪个模块?,我推荐eureka-server,因为就是用eureka-server先启动注册中心的,然后人家才能来注册服务和发现服务。

官方源码网址

2、eureka-server模块源码刨析

先来看一下eureka-server模块的结构:
在这里插入图片描述
可以看到,eureka-server模块只包含了一些配置文件、web.xml、build.gradle(类似Maven项目中的pom.xml文件)核心文件。

2.1、build.gradle文件

在这里插入图片描述
如下几行说明了eureka-server集群中,每个eureka-server即是注册中心,也是客户端的原因。
apply plugin: 'war'
compile project(':eureka-client')
compile project(':eureka-core')

表示eureka-server打成war的时候,会把eureka-resources模块下面的那些jsp、js、css给搞到这个war包里面去,然后就可以跑起来,提供一个index页面。项目中启动了eureka-server之后,第一件事儿就是访问他的控制台,可以去看看有谁来注册了。控制台的代码,就在jsp里面,就是eureka-resources的jsp提供的。

war {
    from (project(':eureka-resources').file('build/resources/main'))
}

2.2、web.xml文件

配置了一个监听器,五个过滤器,由此可见,Eureka Server是一个简单的Servlet应用。

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5"
         xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
	http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
	
  <listener>
    <listener-class>com.netflix.eureka.EurekaBootStrap</listener-class>
  </listener>

  <filter>
    <filter-name>statusFilter</filter-name>
    <filter-class>com.netflix.eureka.StatusFilter</filter-class>
  </filter>

  <filter>
    <filter-name>requestAuthFilter</filter-name>
    <filter-class>com.netflix.eureka.ServerRequestAuthFilter</filter-class>
  </filter>
  <filter>
    <filter-name>rateLimitingFilter</filter-name>
    <filter-class>com.netflix.eureka.RateLimitingFilter</filter-class>
  </filter>
  <filter>
    <filter-name>gzipEncodingEnforcingFilter</filter-name>
    <filter-class>com.netflix.eureka.GzipEncodingEnforcingFilter</filter-class>
  </filter>

  <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>statusFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

  <filter-mapping>
    <filter-name>requestAuthFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

  <!-- Uncomment this to enable rate limiter filter.
  <filter-mapping>
    <filter-name>rateLimitingFilter</filter-name>
    <url-pattern>/v2/apps</url-pattern>
    <url-pattern>/v2/apps/*</url-pattern>
  </filter-mapping>
  -->

  <filter-mapping>
    <filter-name>gzipEncodingEnforcingFilter</filter-name>
    <url-pattern>/v2/apps</url-pattern>
    <url-pattern>/v2/apps/*</url-pattern>
  </filter-mapping>

  <filter-mapping>
    <filter-name>jersey</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

  <welcome-file-list>
    <welcome-file>jsp/status.jsp</welcome-file>
  </welcome-file-list>

</web-app>

监听器,在eureka-server应用启动的时候就会执行,负责初始化一些逻辑,即执行EurekaBootStrap类,后面会重点对该启动类进行详解。

  <listener>
    <listener-class>com.netflix.eureka.EurekaBootStrap</listener-class>
  </listener>

五个过滤器:

  • StatusFilter:负责状态相关的处理逻辑。
  • ServerRequestAuthFilter:一看就是,对请求进行授权认证的处理的。
  • RateLimitingFilter:负责限流相关的逻辑的(很有可能成为eureka-server里面的一个技术亮点,看看人家eureka-server作为一个注册中心,是怎么做限流的,先留意算法是什么,留到后面去看)。
  • GzipEncodingEnforcingFilter:gzip,压缩相关的;encoding,编码相关的。
  • ·jersey:配置jersey框架核心filter。

jersey框架:(类似于mvc框架,比如说struts2和spring web mvc),都会搞一个自己的核心filter,或者是核心servlet,配置在web.xml里,用了框架之后,相当于就是将web请求的处理入口交给框架了,框架会根据你的配置,自动帮你干很多事儿,最后调用你的一些处理逻辑。

jersey的这个ServletContainer就是一个核心filter,接收所有的请求,作为请求的入口,处理之后来调用你写的代码逻辑。

<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>
  • StatusFilter、RequestAuthFilter、jersey:是通用的处理逻辑,是对所有的请求都开放的。
  • GzipEncodingEnforcingFilter,/v2/apps相关的请求,会走这里,仅仅对部分特殊的请求生效。
<filter-mapping>
    <filter-name>statusFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

  <filter-mapping>
    <filter-name>requestAuthFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

  <filter-mapping>
    <filter-name>gzipEncodingEnforcingFilter</filter-name>
    <url-pattern>/v2/apps</url-pattern>
    <url-pattern>/v2/apps/*</url-pattern>
  </filter-mapping>

  <filter-mapping>
    <filter-name>jersey</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

在web.xml文件中有这么一块被注释的代码RateLimitingFilter,默认是不开启的,如果你要打开eureka-server内置的限流功能,你需要自己把RateLimitingFilter的的注释打开,让这个filter生效。

<!-- Uncomment this to enable rate limiter filter.
  <filter-mapping>
    <filter-name>rateLimitingFilter</filter-name>
    <url-pattern>/v2/apps</url-pattern>
    <url-pattern>/v2/apps/*</url-pattern>
  </filter-mapping>
  -->

welcome-file-list,是配置了status.jsp是欢迎页面,即eureka服务端的控制台页面,展示注册服务的信息。

<welcome-file-list>
    <welcome-file>jsp/status.jsp</welcome-file>
  </welcome-file-list>

2.3、启动类EurekaBootStrap解析

/**
 * The class that kick starts the eureka server.
 *
 * <p>
 * The eureka server is configured by using the configuration
 * {@link EurekaServerConfig} specified by <em>eureka.server.props</em> in the
 * classpath.  The eureka client component is also initialized by using the
 * configuration {@link EurekaInstanceConfig} specified by
 * <em>eureka.client.props</em>. If the server runs in the AWS cloud, the eureka
 * server binds it to the elastic ip as specified.
 * </p>
 *
 * @author Karthik Ranganathan, Greg Kim, David Liu
 *
 */
public class EurekaBootStrap implements ServletContextListener {
	/**
     * Initializes Eureka, including syncing up with other Eureka peers and publishing the registry.
     *
     * @see
     * javax.servlet.ServletContextListener#contextInitialized(javax.servlet.ServletContextEvent)
     */
    @Override
    public void contextInitialized(ServletContextEvent event) {
        try {
            initEurekaEnvironment();
            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);
        }
    }
}

英文说明:
The class that kick starts the eureka server:启动eureka服务器的类。
Initializes Eureka, including syncing up with other Eureka peers and publishing the registry.:初始化Eureka,包括与其他Eureka对等点同步并发布注册表。

EurekaBootStrap 实现了ServletContextListener,它是用来监听Servlet Context生命周期的变化的。这个类一加载,就会调用contextInitialized方法完成初始化。
eureka-server启动,主干了如下几件事情:

initEurekaEnvironment():初始化eureka环境
initEurekaServerContext():初始化eureka server的上下文

2.3.1、initEurekaEnvironment()方法解析
    /**
     * Users can override to initialize the environment themselves.
     */
    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");
        }
    }

先看下面这行代码:

ConfigurationManager.getConfigInstance()
static volatile AbstractConfiguration instance = null;
public static AbstractConfiguration getConfigInstance() {
        if (instance == null) {
            synchronized (ConfigurationManager.class) {
                if (instance == null) {
                    instance = getConfigInstance(Boolean.getBoolean(DynamicPropertyFactory.DISABLE_DEFAULT_CONFIG));
                }
            }
        }
        return instance;
    }

其实就是初始化AbstractConfiguration的实例,也就是通过ConfigurationManagerAbstractConfiguration进行初始化的这么一个过程。ConfigurationManager是什么呢?配置管理器,管理eureka自己的所有的配置,读取配置文件里的配置到内存里,供后续的eureka-server运行来使用。
而实例化的过程,采用的是经典的double-check + volatile单例模式

double check+volatile的单例模式原理:

在这里插入图片描述
即多个线程同时去实例化AbstractConfiguration,首先第一次判断instance==null,把instance==null加载到线程各自的缓存中,之后争抢ConfigurationManager.class这把锁,未获取锁的线程进入队列等待,获取到所得线程,执行第二次instance==null判断,为真,进行AbstractConfiguration实例化,然后释放锁。由于instance被volatile修饰,当instance被实例化完成后,便把instance更新的值刷到内存中去,同时把队列中各个线程缓存中的instance==null置为失效。释放锁之后,队列中的线程再次争抢锁,抢到锁之后,执行第二次instance==null判断,发现自己缓存中的instance已经失效,就会重新去内存中获取instance值,只是发现instance不为空,就不会走AbstractConfiguration实例化操作。

DynamicPropertyFactory.DISABLE_DEFAULT_CONFIG:系统属性,默认是关闭的,false。

/**
* System property to determine whether DynamicPropertyFactory should be lazily initialized with
* default configuration for {@link #getInstance()}. Default is false (not set).
* 英文的含义:系统属性,以确定是否应该用以确定是否应该使用DynamicPropertyFactory进行惰性初始化。默认为false(未设置)。
*/
public static final String DISABLE_DEFAULT_CONFIG = "archaius.dynamicProperty.disableDefaultConfig";

然后进入到下面这个方法,参数defaultConfigDisabled为false,instance==null

private static AbstractConfiguration getConfigInstance(boolean defaultConfigDisabled) {
        if (instance == null && !defaultConfigDisabled) {
            instance = createDefaultConfigInstance();
            registerConfigBean();
        }
        return instance;        
    }

进入createDefaultConfigInstance()方法里面:

private static AbstractConfiguration createDefaultConfigInstance() {
        ConcurrentCompositeConfiguration config = new ConcurrentCompositeConfiguration();  
        try {
            DynamicURLConfiguration defaultURLConfig = new DynamicURLConfiguration();
            config.addConfiguration(defaultURLConfig, URL_CONFIG_NAME);
        } catch (Throwable e) {
            logger.warn("Failed to create default dynamic configuration", e);
        }
        if (!Boolean.getBoolean(DISABLE_DEFAULT_SYS_CONFIG)) {
            SystemConfiguration sysConfig = new SystemConfiguration();
            config.addConfiguration(sysConfig, SYS_CONFIG_NAME);
        }
        if (!Boolean.getBoolean(DISABLE_DEFAULT_ENV_CONFIG)) {
            EnvironmentConfiguration envConfig = new EnvironmentConfiguration();
            config.addConfiguration(envConfig, ENV_CONFIG_NAME);
        }
        ConcurrentCompositeConfiguration appOverrideConfig = new ConcurrentCompositeConfiguration();
        config.addConfiguration(appOverrideConfig, APPLICATION_PROPERTIES);
        config.setContainerConfigurationIndex(config.getIndexOfConfiguration(appOverrideConfig));
        return config;
    }

创建一个ConcurrentCompositeConfiguration实例,这个东西,其实就是总的一个配置类,包括了eureka初始化环境需要的所有的环境配置。
DISABLE_DEFAULT_SYS_CONFIG、DISABLE_DEFAULT_ENV_CONFIG:都是系统属性,默认都是关闭的,false。

try {
            DynamicURLConfiguration defaultURLConfig = new DynamicURLConfiguration();
            config.addConfiguration(defaultURLConfig, URL_CONFIG_NAME);
        } catch (Throwable e) {
            logger.warn("Failed to create default dynamic configuration", e);
        }
        if (!Boolean.getBoolean(DISABLE_DEFAULT_SYS_CONFIG)) {
            SystemConfiguration sysConfig = new SystemConfiguration();
            config.addConfiguration(sysConfig, SYS_CONFIG_NAME);
        }
        if (!Boolean.getBoolean(DISABLE_DEFAULT_ENV_CONFIG)) {
            EnvironmentConfiguration envConfig = new EnvironmentConfiguration();
            config.addConfiguration(envConfig, ENV_CONFIG_NAME);
        }
        ConcurrentCompositeConfiguration appOverrideConfig = new ConcurrentCompositeConfiguration();
        config.addConfiguration(appOverrideConfig, APPLICATION_PROPERTIES);
        config.setContainerConfigurationIndex(config.getIndexOfConfiguration(appOverrideConfig));

这块代码,就是创建一些默认的配置类,给到ConcurrentCompositeConfiguration类,该类中用ConcurrentHashMap数据结构来存储这些环境配置,之后返回。

回过头来,来看看这块代码:

String dataCenter = ConfigurationManager.getConfigInstance().getString(EUREKA_DATACENTER);
String environment = ConfigurationManager.getConfigInstance().getString(EUREKA_ENVIRONMENT);
  public String getString(String key)
    {
        String s = getString(key, null);
        if (s != null)
        {
            return s;
        }
        else if (isThrowExceptionOnMissing())
        {
            throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object");
        }
        else
        {
            return null;
        }
    }

就是从AbstractConfiguration配置类的子类中的ConcurrentHashMap结构中取出EUREKA_DATACENTER、EUREKA_ENVIRONMENT对应的配置信息,走断点可以看出,返回数据为null。

之后走如下逻辑:

if (dataCenter == null) {
            logger.info("Eureka data center value eureka.datacenter is not set, defaulting to default");
            ConfigurationManager.getConfigInstance().setProperty(ARCHAIUS_DEPLOYMENT_DATACENTER, DEFAULT);
        } 
   
        if (environment == null) {
            ConfigurationManager.getConfigInstance().setProperty(ARCHAIUS_DEPLOYMENT_ENVIRONMENT, TEST);
            logger.info("Eureka environment value eureka.environment is not set, defaulting to test");
        }

这块代码的意思就是,给eureka数据中心设置默认值 default;给eureka环境设置默认值test

initEurekaServerContext()方法源码解析放到下一篇文章进行分析。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值