图解+源码讲解 Eureka Client 启动流程分析

451 阅读6分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第2天,点击查看活动详情

图解+源码讲解 Eureka Client 启动流程分析

人,就像钉子一样,一旦失去了方向,开始向阻力屈身,那么就失去了他们存在的价值 —兰道 相关文章
eureka-server 项目结构分析
图解+源码讲解 Eureka Server 启动流程分析
图解+源码讲解 Eureka Client 启动流程分析
图解+源码讲解 Eureka Server 注册表缓存逻辑
图解+源码讲解 Eureka Client 拉取注册表流程
图解+源码讲解 Eureka Client 服务注册流程
图解+源码讲解 Eureka Client 心跳机制流程
图解+源码讲解 Eureka Client 下线流程分析
图解+源码讲解 Eureka Server 服务剔除逻辑
图解+源码讲解 Eureka Server 集群注册表同步机制

核心逻辑流程图

    eureka-client 启动的整个核心逻辑,比如初始化的时候需要什么对象,ApplicationInfoManagerEurekaClientConfig 等,以及创建调度器线程池拉取注册表发送心跳等等image.png

从哪里开始分析

     进入eureka 项目中里面有一个eureka-examples 项目,一般这个就是项目的测试例子,去找到ExampleEurekaClient 文件中去看看整个 eureka-client 项目构造以及初始化的整个流程
image.png
这个类的主要就是创建一个eureka-client 之后向eureka-server 发起请求的操作

public class ExampleEurekaClient {
    private static ApplicationInfoManager applicationInfoManager;
    private static EurekaClient eurekaClient;
    // 主要就是在进行初始化赋值,为了给 eurekaClient 初始化的时候提供信息
    private static synchronized ApplicationInfoManager
        initializeApplicationInfoManager(EurekaInstanceConfig instanceConfig) {
        InstanceInfo instanceInfo = 
                new EurekaConfigBasedInstanceInfoProvider(instanceConfig).get();
        applicationInfoManager = 
                new ApplicationInfoManager(instanceConfig, instanceInfo);
        return applicationInfoManager;
    }
    // 初始化 eurekaClient 客户端
    private static synchronized EurekaClient 
        initializeEurekaClient(ApplicationInfoManager applicationInfoManager,
                               EurekaClientConfig clientConfig) {
        if (eurekaClient == null) {
            /* 创建客户端 */
            eurekaClient = new DiscoveryClient(applicationInfoManager, clientConfig);
        }
        return eurekaClient;
    }
    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();
    }
}

    new DiscoveryClient(applicationInfoManager, clientConfig) 这段代码才是真正的核心,我们后续就对这个类进行深入研究

new DiscoveryClient 核心流程

public class DiscoveryClient implements EurekaClient

    其实这个 DiscoveryClient 实现了 EurekaClient 接口,看看 new DiscoveryClient 的时候主要做了什么操作,这块主要是注意传入的备份注册表的操作,如果没有获取到注册表的情况下会走备份的注册表逻辑,但是一般是没有进行配置的,默认值也是null的

public DiscoveryClient(ApplicationInfoManager applicationInfoManager, 
                    final EurekaClientConfig config, 
                    AbstractDiscoveryClientOptionalArgs args, 
                    EndpointRandomizer randomizer) {
    this(applicationInfoManager, config, args, new Provider<BackupRegistry>() {
        private volatile BackupRegistry backupRegistryInstance;
        @Override
        public synchronized BackupRegistry get() {
            if (backupRegistryInstance == null) { // 注册表备份策略
                String backupRegistryClassName = config.getBackupRegistryImpl();
                if (backupRegistryInstance == null) {
                    backupRegistryInstance = new NotImplementedRegistryImpl();
                }
            }
            return backupRegistryInstance;
        }
    }, randomizer);
}

1. 初始化配置信息

    其实就是根据传进来的一些参数 applicationInfoManagerconfig等信息进行初始化配置,之后在本地创建了一个本地注册表的数据,不过这份数据是空的

this.applicationInfoManager = applicationInfoManager; // 传进来的应用配置管理
InstanceInfo myInfo = applicationInfoManager.getInfo();// 获取 InstanceInfo 信息
clientConfig = config;// 传进来的客户端配置
staticClientConfig = clientConfig;
transportConfig = config.getTransportConfig(); // 客户端的通信配置
instanceInfo = myInfo;
appPathIdentifier = instanceInfo.getAppName() + "/" + instanceInfo.getId();
this.backupRegistryProvider = backupRegistryProvider;// 备份注册表
localRegionApps.set(new Applications());// 设置本地的所有注册表信息,初始化一个null的

2. 释放一些没必要的资源

    如果当前客户端不需要注册到服务中去,也不需要拉取注册表的情况下那么就释放一些没有用的资源并进行返回

if (!config.shouldRegisterWithEureka() && !config.shouldFetchRegistry()) {
    scheduler = null;
    heartbeatExecutor = null;
    cacheRefreshExecutor = null;
    eurekaTransport = null;
    instanceRegionChecker = new InstanceRegionChecker(...);
    DiscoveryManager.getInstance().setDiscoveryClient(this);
    DiscoveryManager.getInstance().setEurekaClientConfig(config);
    logger.info("Discovery Client initialized ... ");
    return;
}

3. 创建核心线程以及调度器

    创建了一个线程调度器,这个调度器的核心线程数就是2个就是为了调度下面这两个线程池的、两个线程池,一个是心跳线程池、一个是缓存线程池,默认的核心线程数就是5个,而且这些线程都是守护线程

// 核心调度器
scheduler = Executors.newScheduledThreadPool(2, 
                new ThreadFactoryBuilder().
                setNameFormat("DiscoveryClient-%d").
                setDaemon(true).build()); //设置为守护线程
// 心跳线程池 clientConfig.getHeartbeatExecutorThreadPoolSize() 默认值是5 */
heartbeatExecutor = new ThreadPoolExecutor(
   1, clientConfig.getHeartbeatExecutorThreadPoolSize(), 0, 
   TimeUnit.SECONDS, new SynchronousQueue<Runnable>(),
   new ThreadFactoryBuilder().setNameFormat("DiscoveryClient-HeartbeatExecutor-%d").
   setDaemon(true).build()); //设置为守护线程
// 缓存刷新线程池  clientConfig.getCacheRefreshExecutorThreadPoolSize() 默认值是5
cacheRefreshExecutor = new ThreadPoolExecutor(
    1, clientConfig.getCacheRefreshExecutorThreadPoolSize(), 0, 
    TimeUnit.SECONDS, new SynchronousQueue<Runnable>(),
    new ThreadFactoryBuilder().setNameFormat("DiscoveryClient-CacheRefreshExecutor-%d").
    setDaemon(true).build()); //设置为守护线程  

4. 创建支持底层通信的组件

     scheduleServerEndpointTask(eurekaTransport, args) 方法里面创建了两个客户端,如果需要注册的化那么就创建一个注册的客户端,如果需要拉取注册表的话那么就创建一个拉取注册表的客户端

eurekaTransport = new EurekaTransport();
scheduleServerEndpointTask(eurekaTransport, args);
// 如果需要注册的化那么就创建一个注册的客户端
if (clientConfig.shouldRegisterWithEureka()) {
    EurekaHttpClientFactory newRegistrationClientFactory = null;
    EurekaHttpClient newRegistrationClient = null;
    // 注册客户端工厂
    newRegistrationClientFactory = EurekaHttpClients.registrationClientFactory(
                eurekaTransport.bootstrapResolver,
                eurekaTransport.transportClientFactory,
                transportConfig);
    // 创建注册客户端
    newRegistrationClient = newRegistrationClientFactory.newClient();
    eurekaTransport.registrationClientFactory = newRegistrationClientFactory;
    eurekaTransport.registrationClient = newRegistrationClient;
}
// 如果需要拉取注册表的话
if (clientConfig.shouldFetchRegistry()) {
    EurekaHttpClientFactory newQueryClientFactory = null;
    EurekaHttpClient newQueryClient = null;
    // 拉取注册表的客户端工厂
    newQueryClientFactory = EurekaHttpClients.queryClientFactory(
            eurekaTransport.bootstrapResolver,
            eurekaTransport.transportClientFactory,
            clientConfig,
            transportConfig,
            applicationInfoManager.getInfo(),
            applicationsSource,
            endpointRandomizer);
    // 创建拉取注册表的客户端
    newQueryClient = newQueryClientFactory.newClient();
    eurekaTransport.queryClientFactory = newQueryClientFactory;
    eurekaTransport.queryClient = newQueryClient;
}

5. 拉取注册表核心逻辑

    拉取注册表逻辑,有增量拉取全量拉取两个逻辑,如果本地的注册表中是空的或者第一次启动的时候或者没有配置增量拉取的情况下都是走的全量拉取,除了以上情况几乎都是增量拉取,但是整个的拉取过程我们后续的文章会详细讲解

if (clientConfig.shouldFetchRegistry()) {
   // 客户端拉取注册表核心逻辑
   boolean primaryFetchRegistryResult = fetchRegistry(false);
}

6. 初始化调度任务

    初始化调度任务,定时拉取注册表,向eureka-server进行服务注册

initScheduledTasks();

    进入该方法看看里面都是什么东西,这块就是将它之前定义好的线程池和调度器结合到一块了,里面定时执行两个任务一个是缓存刷新任务new CacheRefreshThread() 一个是心跳发送任务 new HeartbeatThread()

private void initScheduledTasks() {
    /**默认进行30s抓取一次*/
    // 如果需要拉取注册表的话
    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);
    // 如果需要进行注册的话
    /**默认30s发送一次心跳*/
    int renewalIntervalInSecs = instanceInfo.getLeaseInfo().getRenewalIntervalInSecs();
    int expBackOffBound = clientConfig.getHeartbeatExecutorExponentialBackOffBound();
    // Heartbeat timer
    heartbeatTask = new TimedSupervisorTask(
            "heartbeat",
            scheduler,
            heartbeatExecutor,
            renewalIntervalInSecs,
            TimeUnit.SECONDS,
            expBackOffBound,
            new HeartbeatThread()
    );
    /**调度线程池*/
    scheduler.schedule(heartbeatTask, renewalIntervalInSecs, TimeUnit.SECONDS);
}

    剩下的就是无关紧要的代码了,所以到目前为止就已经将其初始化好了,如何拉取注册表和发送心跳的逻辑我们后续单独拿出一篇文章讲解

小结

客户端初始化的时候主要做了那些事情:

  1. 初始话配置信息
  2. 释放没有必要的资源
  3. 创建了一个调度器、两个线程池一个是心跳线程池、一个是拉取注册表更新本地缓存的线程池,默认是30s,并且都是守护线程
  4. 创建底层通信组件、拉取注册表通信组件以及发送心跳的通信组件
  5. 初始化调度任务