Eureka源码深度刨析-(2)EurekaClient启动流程

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

说完了Eureka Server的启动,现在再来看看Eureka Client是怎么启动的。

出发点是eureka-examples这个模块的一个Example类:ExampleEurekaClient

这个类相当于模拟了我们平时写的客户端程序,所以我们找到这个main()方法里的代码,启动流程的逻辑就在这个main()方法里;

public static void main(String[] args) {
    ExampleEurekaClient sampleClient = new ExampleEurekaClient();

    // create the client
    ApplicationInfoManager applicationInfoManager = initializeApplicationInfoManager(new MyDataCenterInstanceConfig());
    EurekaClient client = initializeEurekaClient(applicationInfoManager, new DefaultEurekaClientConfig());

    // use the client
    sampleClient.sendRequestToServiceUsingEureka(client);


    // shutdown the client
    eurekaClient.shutdown();
}

同样,我在这里梳理这个启动方法做的几件事情:

(1)读取eureka-client.properties配置文件,形成一个服务实例的配置,基于接口对外提供服务实例的配置项的读取,这个上一篇文章讲过,基于getXxx()的方法提供配置项读取是Eureka配置管理的套路;

new MyDataCenterInstanceConfig()

(2)基于服务实例的配置,构造了一个服务实例(InstanceInfo)

(3)基于服务实例的配置和服务实例,构造了一个服务实例管理器(ApplicationInfoManager)

private static synchronized ApplicationInfoManager initializeApplicationInfoManager(EurekaInstanceConfig instanceConfig) {
    if (applicationInfoManager == null) {
        //构造服务实例
        InstanceInfo instanceInfo = new EurekaConfigBasedInstanceInfoProvider(instanceConfig).get();
        //构造服务实例管理器
        applicationInfoManager = new ApplicationInfoManager(instanceConfig, instanceInfo);
    }

    return applicationInfoManager;
}

(4)读取eureka-client.properites配置文件,构建为一个DynamicPropertyFactory的配置类,接口对外提供eureka client的配置项的读取;

然后基于EurekaClient配置类构建了网络通信组件的配置类:EurekaTransportConfig,Eureka client启动,必然要向Eureka Server去注册服务以及抓取注册表,所以在这里一定会构建网络通信相关的东西;

public DefaultEurekaClientConfig(String namespace) {
    this.namespace = namespace.endsWith(".")
        ? namespace
        : namespace + ".";

    //private final DynamicPropertyFactory configInstance;
    //public static final String CONFIG_FILE_NAME = "eureka-client";
    this.configInstance = Archaius1Utils.initConfig(CommonConstants.CONFIG_FILE_NAME);
    this.transportConfig = new DefaultEurekaTransportConfig(namespace, configInstance);
}

(5)把上面构建的ApplicationInfoManager和EurekaClientConfig作为参数,初始化Eureka client,使用的是子类DiscoveryClient

EurekaClient client = initializeEurekaClient(applicationInfoManager, new DefaultEurekaClientConfig());
private static synchronized EurekaClient initializeEurekaClient(ApplicationInfoManager applicationInfoManager, EurekaClientConfig clientConfig) {
    if (eurekaClient == null) {
        eurekaClient = new DiscoveryClient(applicationInfoManager, clientConfig);
    }

    return eurekaClient;
}

我们来看一下DiscoveryClient的构造流程,看看初始化eureka客户端都干了些啥:

this.applicationInfoManager = applicationInfoManager;
InstanceInfo myInfo = applicationInfoManager.getInfo();
//config就是EurekaClientConfig
clientConfig = config;
staticClientConfig = clientConfig;
transportConfig = config.getTransportConfig();
instanceInfo = myInfo;

先保存ApplicationInfoManager、EurekaClientConfig、EurekaTransportConfig、InstanceInfo这些东西,可能后面会使用;

然后判断是否要注册以及抓取注册表,如果不要的话,就会在这里释放一些资源;

if (!config.shouldRegisterWithEureka() && !config.shouldFetchRegistry()) {
    logger.info("Client configured to neither register nor query for data.");
    scheduler = null;
    heartbeatExecutor = null;
    cacheRefreshExecutor = null;
    eurekaTransport = null;
    ... ...
}

然后创建了一些调度线程池;

//支持调度的线程池
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2, new ThreadFactoryBuilder().setNameFormat("DiscoveryClient-%d").setDaemon(true).build());
//支持心跳的线程池
ThreadPoolExecutor heartbeatExecutor = new ThreadPoolExecutor(1, clientConfig.getHeartbeatExecutorThreadPoolSize(), 0, TimeUnit.SECONDS,new SynchronousQueue<Runnable>(),new ThreadFactoryBuilder().setNameFormat("DiscoveryClient-HeartbeatExecutor-%d").setDaemon(true) .build());
//支持缓存的线程池
ThreadPoolExecutor cacheRefreshExecutor = new ThreadPoolExecutor(1, clientConfig.getCacheRefreshExecutorThreadPoolSize(), 0, TimeUnit.SECONDS,new SynchronousQueue<Runnable>(),new ThreadFactoryBuilder().setNameFormat("DiscoveryClient-CacheRefreshExecutor-%d").setDaemon(true).build());

随后初始化网络通信组件EurekaTransport,这个通信组件是一个核心组件,后面服务注册与发现、心跳等都会基于这个组件里的一系列HttpClient组件进行客户端和服务端的网络通信,这个通信使用的是Http Restful接口调用的方式,基于jersey进行的,jersey也是一种web mvc框架,和spring mvc是属于同一种类。

private final EurekaTransport eurekaTransport;
... ...
eurekaTransport = new EurekaTransport();
scheduleServerEndpointTask(eurekaTransport, args);

如果配置了要抓取注册表的话,在这里就会去抓取注册表了;

if (clientConfig.shouldFetchRegistry()) {
    try {
        boolean primaryFetchRegistryResult = fetchRegistry(false);
        if (!primaryFetchRegistryResult) {
            logger.info("Initial registry fetch from primary servers failed");
        }
    ... ...
}

如果配置了向Eureka server注册,就会在这里注册;

if (clientConfig.shouldRegisterWithEureka() && clientConfig.shouldEnforceRegistrationAtInit()) {
    try {
        if (!register() ) {
            throw new IllegalStateException("Registration error at startup. Invalid server response.");
        }
    ... ...
}

初始化调度任务:如果需要抓取注册表的话,就会开启一个定时任务,每隔一定时间(默认是30s),去执行一个CacheRefreshThread线程类的run方法,然后将这个Task放进一个调度线程池里;

如果要向Eureka server进行注册的话,同样的搞一个定时任务,每隔一定时间发送心跳,执行一个HeartbeatThread线程类的run方法;

initScheduledTasks();
private void initScheduledTasks() {
    if (clientConfig.shouldFetchRegistry()) {
        // registry cache refresh timer
        int registryFetchIntervalSeconds = clientConfig.getRegistryFetchIntervalSeconds();
        int expBackOffBound = clientConfig.getCacheRefreshExecutorExponentialBackOffBound();
        //抓取注册表任务
        cacheRefreshTask = new TimedSupervisorTask(
            "cacheRefresh",
            scheduler,
            cacheRefreshExecutor,
            registryFetchIntervalSeconds,
            TimeUnit.SECONDS,
            expBackOffBound,
            new CacheRefreshThread()
        );
        scheduler.schedule(
            cacheRefreshTask,
            registryFetchIntervalSeconds, TimeUnit.SECONDS);
    }

    if (clientConfig.shouldRegisterWithEureka()) {
        int renewalIntervalInSecs = instanceInfo.getLeaseInfo().getRenewalIntervalInSecs();
        int expBackOffBound = clientConfig.getHeartbeatExecutorExponentialBackOffBound();
        logger.info("Starting heartbeat executor: " + "renew interval is: {}", renewalIntervalInSecs);

        // Heartbeat timer
        //发送心跳任务
        heartbeatTask = new TimedSupervisorTask(
            "heartbeat",
            scheduler,
            heartbeatExecutor,
            renewalIntervalInSecs,
            TimeUnit.SECONDS,
            expBackOffBound,
            new HeartbeatThread()
        );
        scheduler.schedule(
            heartbeatTask,
            renewalIntervalInSecs, TimeUnit.SECONDS);

        // InstanceInfo replicator
        instanceInfoReplicator = new InstanceInfoReplicator(
            this,
            instanceInfo,
            clientConfig.getInstanceInfoReplicationIntervalSeconds(),
            2); // burstSize

        statusChangeListener = new ApplicationInfoManager.StatusChangeListener() {
            @Override
            public String getId() {
                return "statusChangeListener";
            }

            @Override
            public void notify(StatusChangeEvent statusChangeEvent) {
                logger.info("Saw local status change event {}", statusChangeEvent);
                instanceInfoReplicator.onDemandUpdate();
            }
        };
    ... ...
}

最后附上一张自绘制的EurekaClient启动流程图帮助理解。
EurekaClient初始化流程

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值