Eureka源码深度刨析-(1)EurekaServer启动流程

“不积跬步,无以至千里。”

这个专题开始,我们来深度刨析一下springcloud netflix技术栈核心组件的源码,虽然目前Alibaba系会更火一些,但我个人认为,微服务的始祖是netflix系组件,所谓一通百通,当我们把netflix组件原理全部吃透,彻底吸收微服务设计理念、核心思想的时候,后面的再出的所有微服务体系框架对你来说都不是问题。做技术这个事情,急不得,更不能什么火就去追,到最后什么都只是用用,一个程序员的核心竞争力应该是看源码的能力,尤其是阅读静态源码。而这些事情,我都会带着你看懂,这个专题我会采用小白都能看懂的方式,尽可能将源码解读细化,也会适当放入一些我自己手工绘制的图,帮助理解,话不多说,我们进入正题。

spring cloud eureka server和client是对netflix的eureka进行了封装,加了一些注解,对spring boot进行支持。所以看eureka的源码,应该先从netflix eureka开始看起。

本次netflix eureka解读的版本:1.10.14,其实1.7.2的版本我也看过,发现在架构设计、运行原理、核心流程上并没有太大的差别,可能某些功能在实现细节上会有些许的差别而已。

  • eureka-client:eureka客户端代码模块
  • eureka-core:eureka服务端代码模块,包括服务注册与发现,心跳,摘除故障服务实例功能
  • eureka-resources:基于jsp开发的eureka控制台
  • eureka-server:注册中心模块,包含以上三者
  • jersey:mvc框架,使服务端和客户端通过restful接口进行网络通信

Eureka工作流程

Eureka Server启动

Eureka-server是一个web应用,那么它的入口就是web.xml;

web.xml配置的Listener是EurekaBootStrap,所以这个类就是Eureka server启动的核心类,是我们重点要去看的;

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

进到EurekaBootStrap这个类里,发现它继承自ServletContextListener,那么就会调用contextInitialized方法进行初始化:

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

先说initEurekaEnvironment()这个方法,大概梳理一下它做的几件事情:

  1. 使用Double check的单例模式搞出来一个ConfigurationManager的实例对象,这个对象你可以认为是一个配置管理组件,后面会通过Properties对象来加载配置,然后对配置进行管理的这么一个东东;

    String dataCenter = ConfigurationManager.getConfigInstance().getString(EUREKA_DATACENTER);
    //顺着getConfigInstance方法往里走,最终实际上实例化的是ConcurrentCompositeConfiguration这样一个对象,在它的构造函数会初始化一些东西,然后把这个对象返回,就不细看了,不是核心。
    public static AbstractConfiguration getConfigInstance() {
        if (instance == null) {
            synchronized (ConfigurationManager.class) {
                if (instance == null) {
                    //这个instance就是AbstractConfiguration类型
                    instance = getConfigInstance(Boolean.getBoolean(DynamicPropertyFactory.DISABLE_DEFAULT_CONFIG));
                }
            }
        }
        return instance;
    }
    
  2. 初始化数据中心,如果没有配置的话,默认设置为DEFAULT ;

    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);
    }
    
  3. 初始化eurueka运行的环境,如果没有配置,默认设置为test环境。

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

再说initEurekaServerContext()这个方法,这是Eureka server初始化的一个核心,很多细节都在这个方法里:

第一步,先创建一个EurekaServerConfig的实例对象,看名字,应该是Eureka server的配置类;

EurekaServerConfig eurekaServerConfig = new DefaultEurekaServerConfig();

public DefaultEurekaServerConfig() {
    init();
}
private void init() {
    String env = ConfigurationManager.getConfigInstance().getString(
        EUREKA_ENVIRONMENT, TEST);
    ConfigurationManager.getConfigInstance().setProperty(
        ARCHAIUS_DEPLOYMENT_ENVIRONMENT, env);

    String eurekaPropsFile = EUREKA_PROPS_FILE.get();
    try {
        // ConfigurationManager
        // .loadPropertiesFromResources(eurekaPropsFile);
        //这个eurekaPropsFile就是eureka-server.properties文件
        ConfigurationManager
            .loadCascadedPropertiesFromResources(eurekaPropsFile);
    } catch (IOException e) {
        logger.warn(
            "Cannot find the properties specified : {}. This may be okay if there are other environment "
            + "specific properties or the configuration is installed with a different mechanism.",
            eurekaPropsFile);
    }
}

loadCascadedPropertiesFromResources这个方法中先去读取eureka-sever.properties这个配置文件的内容到一个Properties对象里面去,然后把这个Properties对象里的配置项再load到一个AbstractConfiguration的配置类中,这个AbstractConfiguration就是我们前面提到的配置管理组件;

public static void loadCascadedPropertiesFromResources(String configName) throws IOException {
    //读取配置到Properties配置类
    Properties props = loadCascadedProperties(configName);
    if (instance instanceof AggregatedConfiguration) {
        ConcurrentMapConfiguration config = new ConcurrentMapConfiguration();
        config.loadProperties(props);
        ((AggregatedConfiguration) instance).addConfiguration(config, configName);
    } else {
        //load Properties中的配置项的配置管理组件
        ConfigurationUtils.loadProperties(props, instance);
    }
}

然后基于DefaultEurekaServerConfig的一系列getXxx()方法提供配置的读取,实际上getXxx()方法就是从ConfigurationManager中去读取,只不过把硬编码的部分写在了方法中罢了,并且给每个属性提供了默认值;这种设计最大的好处就是,屏蔽了key的变更对调用者带来的影响,还是蛮不错的,值得借鉴。
getXxx方法

public int getPeerEurekaNodesUpdateIntervalMs() {
        return configInstance
                .getIntProperty(namespace + "peerEurekaNodesUpdateIntervalMs",
                        (10 * 60 * 1000)).get();
    }

这个configInstance是DynamicPropertyFactory类型的实例对象,实际上就是从AbstractConfiguration中加载的配置;

private static final DynamicPropertyFactory configInstance = com.netflix.config.DynamicPropertyFactory
    .getInstance();
 public static DynamicPropertyFactory getInstance() {
     if (config == null) {
         synchronized (ConfigurationManager.class) {
             if (config == null) {
                 AbstractConfiguration configFromManager = ConfigurationManager.getConfigInstance();
                 if (configFromManager != null) {
                     initWithConfigurationSource(configFromManager);
                     initializedWithDefaultConfig = !ConfigurationManager.isConfigurationInstalled();
                     logger.info("DynamicPropertyFactory is initialized with configuration sources: " + configFromManager);
                 }
             }
         }
     }
     return instance;
 }

第二步,搞了一个EurekaInstanceConfig的对象出来,可以认为是跟服务实例相关的一个配置类,跟前面类似,将eureka-client.properties文件中的配置加载到配置管理组件(AbstractConfiguration)中,然后基于EurekaInstanceConfig提供的一系列getXxx()方法来提供配置项的读取,并且提供了所有配置项的默认值;

EurekaInstanceConfig instanceConfig = isCloud(ConfigurationManager.getDeploymentContext())
                    ? new CloudInstanceConfig()
                    : new MyDataCenterInstanceConfig();
//构造里没有代码,查看父类构造方法
public MyDataCenterInstanceConfig() {
}
public PropertiesInstanceConfig(String namespace, DataCenterInfo info) {
    super(info);

    this.namespace = namespace.endsWith(".")
        ? namespace
        : namespace + ".";

    appGrpNameFromEnv = ConfigurationManager.getConfigInstance()
        .getString(FALLBACK_APP_GROUP_KEY, Values.UNKNOWN_APPLICATION);

    //public static final String CONFIG_FILE_NAME = "eureka-client";
    this.configInstance = Archaius1Utils.initConfig(CommonConstants.CONFIG_FILE_NAME);
}

基于刚才构建的EurekaInstanceConfig和InstanceInfo对象,构建了一个ApplicationManager,后面会基于这个ApplicationInfoManager对服务实例进行一些管理;

ApplicationInfoManager applicationInfoManager = null;
... ...
applicationInfoManager = new ApplicationInfoManager(
                    instanceConfig, new EurekaConfigBasedInstanceInfoProvider(instanceConfig).get());

这里也不足为奇,因为在eureka集群模式下,eureka server同时也是一个eureka client,也要向其他的eureka server去进行注册,所以会有一些Instance和Application的概念出来。

这个InstanceInfo就是当前这个服务实例自身的信息,使用了构造器模式来创建;核心的思路是,从之前的那个EurekaInstanceConfig中,读取一些服务实例相关的配置信息,再构造了几个其他的对象,最终完成了InstanceInfo的构建;使用构造器Builder模式来进行复杂对象的构造和属性赋值还是蛮好的,构造完成后使用build方法获取复杂对象,值得借鉴。

InstanceInfo.Builder builder = InstanceInfo.Builder.newBuilder(vipAddressResolver);
... ...
//使用构造器模式set属性
builder.setNamespace(config.getNamespace())
                    .setInstanceId(instanceId)
                    .setAppName(config.getAppname())
... ...
//构造这个instanceInfo
instanceInfo = builder.build();
instanceInfo.setLeaseInfo(leaseInfoBuilder.build());

第三步,搞出来一个EurekaClientConfig,跟之前一样,读取eureka-client.properties文件中跟EurekaClient相关的配置项,同样是基于getXxx()的一系列方法提供配置项的读取,并提供默认值(看样子是准备一条路走到黑… …);

EurekaClientConfig eurekaClientConfig = new DefaultEurekaClientConfig();

第四步,基于ApplicationInfoManager和eureka client相关的配置构建了一个EurekaClient,这个Eureka Client就是代表了Eureka的客户端,使用的是子类DiscoveryClient,这个DiscoveryClient的构造方法里涉及到了EurekaClient的启动流程,我们放在下一篇将,这里我们只要来看看Server的启动;是不是有些疑惑,为什么Server启动时会有Client的初始化流程??当然会有,Eureka集群模式下,每个Server同时也是Client,一方面提供注册表的抓取,客户端的心跳等等;另一方面也要向其他的Server去注册,以及抓取其他节点的注册表,合并到本地。

private EurekaClient eurekaClient;
... ...
eurekaClient = new DiscoveryClient(applicationInfoManager, eurekaClientConfig);

第五步,构造了一个PeerAwareInstanceRegistry,这个东西,就是所谓的服务实例注册表,这个东西,我在后面会详细说,也是Eureka的一块核心机制,先知道这是什么就行了;

PeerAwareInstanceRegistry registry;
registry = new PeerAwareInstanceRegistryImpl(
     eurekaServerConfig,
     eurekaClient.getEurekaClientConfig(),
     serverCodecs,
     eurekaClient
);

第六步,构造Eureka集群,PeerEurekaNodes;每个Eureka的节点,顾名思义,就是Eureka集群相关的东西;

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

第七步,构造一个Eureka server服务器上下文,EurekaServerContext,然后放入一个EurekaServerContextHolder中,后续需要使用,从holder中获取即可;把上下文这种可能需要全局使用的东西放进一个Holder中,在需要的时候从Holder中获取,也值得借鉴;

protected volatile EurekaServerContext serverContext;
serverContext = new DefaultEurekaServerContext(
    eurekaServerConfig,
    serverCodecs,
    registry,
    peerEurekaNodes,
    applicationInfoManager
);
EurekaServerContextHolder.initialize(serverContext);

第八步,初始化serverContext,主要是把Eureka集群启动起来,让当前的Eureka server感知到所有的其他的Eureka server;

然后搞一个定时调度任务,就一个后台线程,每隔一定的时间,更新Eureka server集群的信息。

serverContext.initialize();

public void initialize() {
    logger.info("Initializing ...");
    //启动Eureka集群
    peerEurekaNodes.start();
    try {
        registry.init(peerEurekaNodes);
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
    logger.info("Initialized");
}

//进入start方法中,可以看到定时更新Eureka server集群信息的代码
updatePeerEurekaNodes(resolvePeerUrls());

然后基于Eureka server集群的信息,来初始化注册表,将集群中所有的Eureka server的注册表的信息都抓取到本地,这个逻辑都在init中,感兴趣可以去看看,这里就不贴出来了;

registry.init(peerEurekaNodes);

第九步,从相邻的一个Eureka server节点拷贝注册表的信息,如果拷贝失败,就找下一个;

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

    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;
            }
        }
        Applications apps = eurekaClient.getApplications();
        for (Application app : apps.getRegisteredApplications()) {
            for (InstanceInfo instance : app.getInstances()) {
                try {
                    if (isRegisterable(instance)) {
                        register(instance, instance.getLeaseInfo().getDurationInSecs(), true);
                        count++;
                    }
                } catch (Throwable t) {
                    logger.error("During DS init copy", t);
                }
            }
        }
    }
    return count;
}

第十步,注册监控状态。

EurekaMonitors.registerAllStats();

到此为止,Eureka server就启动并完成了初始化的工作;

eureka server启动流程

读源码,千万不要有强迫症, 很多时候,刚开始看源码的时候,要允许自己对很多细节都不太清楚,但是能大体把握住大的流程就ok了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值