[Eureka source code analysis-2] Eureka server module source code analysis

1. Netflix Eureka source code project structure description


Brief description of core modules:
(1) Eureka client: This refers to the Eureka client. A service registered on Eureka is a eureka client. Whether you want to register or find other services, whether it is a service provider or a service consumer, it is a eureka client.

(2) eureka core: This refers to the server of eureka, which is actually the registration center of eureka.

(3) Eureka resources: This is a eureka console developed based on jsp. You can see various registration services on the web page.

(4) Eureka server: Eureka client, Eureka core and Eureka resources are packaged into a war package. That is to say, Eureka server itself is also a eureka client, a registration center and a eureka console. Really use the registry.

(5) eureka examples: examples used by eureka.

(6) eureka test utils: eureka's unit test tool class.

Let's take a little look. Which module should we look at first?, I recommend Eureka server because it is used to start the registration center first, and then people can register and discover services.

Official source website

2. Analysis of Eureka server module source code

Let's take a look at the structure of Eureka server module:

You can see that the Eureka server module only contains some configuration files, web.xml, build.gradle (similar to the pom.xml file in Maven project) and core files.

2.1. build.gradle file


The following lines explain why each Eureka server in the Eureka server cluster is both a registry and a client.
apply plugin: 'war'
compile project(':eureka-client')
compile project(':eureka-core')

It means that when the Eureka server is turned into a war, the jsp, js and css under the Eureka resources module will be brought into the war package, and then it can run and provide an index page. After launching Eureka server in the project, the first thing is to visit its console and see who has registered. The console code is in the jsp, which is provided by the jsp of Eureka resources.

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

2.2. web.xml file

One listener and five filters are configured. It can be seen that Eureka Server is a simple Servlet application.

<?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>

The listener will be executed when the Eureka server application is started. It is responsible for initializing some logic, that is, executing Eureka bootstrap class. Later, we will focus on the detailed explanation of this startup class.

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

Five filters:

  • StatusFilter: responsible for status related processing logic.
  • ServerRequestAuthFilter: at first glance, it is used to process authorization and authentication requests.
  • Ratelimiting filter: it is responsible for the logic related to current limiting (it is likely to become a technical highlight in Eureka server. See how Eureka server, as a registry, does current limiting. First pay attention to the algorithm and leave it for later).
  • GzipEncodingEnforcingFilter: gzip, compression related; Encoding, encoding related.
  • ·jersey: configure the jersey framework core filter.

jersey framework: (similar to mvc framework, such as struts2 and spring web mvc), it will create a core filter, or core servlet, configured in web.xml. After using the framework, it is equivalent to hand over the processing entrance of web request to the framework. The framework will do a lot of things for you automatically according to your configuration, and finally call some of your processing logic.

The ServletContainer of jersey is a core filter, which receives all requests, serves as the request entry, and then calls the code logic you write.

<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 and jersey: they are general processing logic and are open to all requests.
  • GzipEncodingEnforcingFilter, / v2/apps related requests will go here and only take effect for some special requests.
<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>

There is a piece of annotated code in the web.xml file, RateLimitingFilter, which is not enabled by default. If you want to open the built-in current limiting function of Eureka server, you need to open the comment of RateLimitingFilter to make this filter effective.

<!-- 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 is a welcome page configured with status.jsp, that is, the console page of eureka server, which displays the information of the registration service.

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

2.3. Startup class eurekabotstrat parsing

/**
 * 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);
        }
    }
}

English Description:
The class that kick starts the eureka server.
Initializes Eureka, including synchronizing up with other Eureka peers and publishing the registry.

Eureka bootstrap implements ServletContextListener, which is used to monitor the changes in the life cycle of Servlet Context. As soon as this class is loaded, it will call the contextInitialized method to complete initialization.
When Eureka server is started, the following things are:

initEurekaEnvironment(): initializes the eureka environment
initEurekaServerContext(): initializes the context of eureka server

2.3.1. Analysis of initEurekaEnvironment() method

    /**
     * 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");
        }
    }

First look at the following line of code:

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;
    }

In fact, it is the process of initializing the instance of AbstractConfiguration, that is, the process of initializing AbstractConfiguration through configuration manager. What is configuration manager? The configuration manager manages all of eureka's own configurations and reads the configuration in the configuration file into memory for subsequent eureka server operations.
The instantiation process adopts the classic double check + volatile singleton mode.

Single instance mode principle of double check+volatile:


That is, multiple threads instantiate AbstractConfiguration at the same time. First, judge instance==null for the first time, load instance==null into the respective cache of the thread, and then compete for the lock of ConfigurationManager.class. The thread that has not obtained the lock enters the queue to wait, obtains the obtained thread, and executes the judgment of instance==null for the second time. If it is true, instantiate AbstractConfiguration, Then release the lock. Because instance is modified by volatile, when instance is instantiated, the updated value of instance will be brushed into memory, and instance==null in the cache of each thread in the queue will be invalidated. After releasing the lock, the threads in the queue compete for the lock again. After grabbing the lock, execute the instance==null judgment for the second time. If they find that the instance in their cache has expired, they will go to the memory to obtain the instance value again. Only if they find that the instance is not empty, they will not go through the AbstractConfiguration instantiation operation.

DynamicPropertyFactory.DISABLE_DEFAULT_CONFIG: system attribute. It is off by default and false.

/**
* System property to determine whether DynamicPropertyFactory should be lazily initialized with
* default configuration for {@link #getInstance()}. Default is false (not set).
*English meaning: system property to determine whether it should be used to determine whether dynamic propertyfactory should be used for lazy initialization. The default is false (not set).
*/
public static final String DISABLE_DEFAULT_CONFIG = "archaius.dynamicProperty.disableDefaultConfig";

Then enter the following method. The parameter defaultConfigDisabled is false and instance==null.

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

Enter the createDefaultConfigInstance() method:

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;
    }

Create a concurrentcommiteconfiguration instance, which is actually a general configuration class, including all the environment configurations required by eureka to initialize the environment.
DISABLE_DEFAULT_SYS_CONFIG,DISABLE_DEFAULT_ENV_CONFIG: all system attributes are closed by default. 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));

This code is to create some default configuration classes and give them to the concurrentcommiteconfiguration class, which uses the ConcurrentHashMap data structure to store these environment configurations, and then return.

Look back at this Code:

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;
        }
    }

This is to take Eureka from the ConcurrentHashMap structure in the subclass of AbstractConfiguration configuration class_ DATACENTER,EUREKA_ The configuration information corresponding to environment. It can be seen from the breakpoint that the returned data is null.

Then follow the following logic:

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");
        }

The meaning of this code is to set the default value default for eureka data center; Set the default value test for the eureka environment.

The source code analysis of initEurekaServerContext() method will be analyzed in the next article.

Tags: Java Spring Spring Boot Back-end eureka

Posted on Thu, 02 Dec 2021 21:12:52 -0500 by icecube