flink源码分析之启动过程分析

今天阅读了一下flink v1.14的代码,首先分析一下flink启动的过程。
首先分2种,一种是SessionClusterEntrypoint,一种是JobClusterEntrypoint。分别对应session 模式和per-job模式。
session模式就是一次启动,可以执行多个job,执行完job还有后台进程在等待用户提交新的job。
per-job模式就是一次启动,执行一个job,执行完成就退出全部程序。

SessionClusterEntrypoint又分为(1)YarnSessionClusterEntryPoint;(2) KubernetesSessionClusterEntrypoint;(3)StandaloneSessionClusterEntrypoint;
见名知意:
第一个是基于Yarn的ResourceManager来管理集群,然后是启动后可以执行多次job;
第二个是基于Kubernetes来管理集群,然后是启动后可以执行多长job;
第三个是基于Flink自己的ResourceManager来管理集群,然后也是启动后可以执行多次job;

JobClusterEntrypoint又分为(1)YarnJobClusterEntrypoint;(2)StandaloneJobClusterEntrypoint;(3)MesosJobClusterEntrypoint;
见名知意:
第一个是基于Yarn的ResourceManager来管理集群,然后是一次运行一个job,运行完成一个job就退出整个集群;
第二个是基于Flink自己的ResourceManager来管理集群,然后是一次运行一个job,运行完成一个job就退出整个集群;
第三个是基于Mesos来管理集群,然后是一次运行一个job,运行完成一个job后就退出整个集群;

我们今天用StandaloneSessionClusterEntrypoint类来分析flink如何启动的。

打开StandaloneSessionClusterEntrypoint类,找到main()方法;

        // startup checks and logging
        EnvironmentInformation.logEnvironmentInfo(
                LOG, StandaloneSessionClusterEntrypoint.class.getSimpleName(), args);
        SignalHandler.register(LOG);
        JvmShutdownSafeguard.installAsShutdownHook(LOG);

        final EntrypointClusterConfiguration entrypointClusterConfiguration =
                ClusterEntrypointUtils.parseParametersOrExit(
                        args,
                        new EntrypointClusterConfigurationParserFactory(),
                        StandaloneSessionClusterEntrypoint.class);
        Configuration configuration = loadConfiguration(entrypointClusterConfiguration);

        StandaloneSessionClusterEntrypoint entrypoint =
                new StandaloneSessionClusterEntrypoint(configuration);

        ClusterEntrypoint.runClusterEntrypoint(entrypoint);
    } 

这里就是启动的所有代码,最核心的我们看最后一行

 ClusterEntrypoint.runClusterEntrypoint(entrypoint);

这行代码,我们点runClusterEntrypoint进入ClusterEntrypoint类的runClusterEntrypoint(ClusterEntrypoint clusterEntrypoint) 方法;
runClusterEntrypoint(ClusterEntrypoint clusterEntrypoint) 方法中有

clusterEntrypoint.startCluster();

这行代码,我们点击startCluster进入ClusterEntrypoint类的startCluster()方法;
startCluster()方法中有

runCluster(configuration, pluginManager);

这行代码,我们点击runCluster进入ClusterEntrypoint类的runCluster(Configuration configuration, PluginManager pluginManager)方法;
runCluster(Configuration configuration, PluginManager pluginManager)方法中有

initializeServices(configuration, pluginManager);

这行代码,我们点击initializeServices进入ClusterEntrypoint类的initializeServices(Configuration configuration, PluginManager pluginManager)方法;
initializeServices(Configuration configuration, PluginManager pluginManager)方法中有几个重要的核心启动方法,截图如下:

上面的截图中最重要的是做了几件事情:
(1):从configuration中获取配置的RPC地址和portRange参数,根据配置地址和端口信息创建集群所需的公用的commonRpcService服务。更新configuration中的address和port配置,支持后续的集群高可用服务。

(2):创建ioExecutor线程池,用于集群组件的I/O操作,如本地文件数据读取和输出等。
(3):创建并启动haService,向集群组件提供高可用支持,集群中的组件都会通过haService创建高可用服务。
(4):创建并启动blobServer,存储集群所需要的Blob对象数据,例如JobGraph中的Jar包等。blobServer中存储的数据能够被JobMaster和TaskManager访问。
(5):创建heartbeatServices,主要用于创建集群组件之间的心跳检测,例如ResourceManager与JobManager之间的心跳服务。
(6):创建metricRegistry服务,用于注册集群监控指标收集。
(7):创建archivedExecutionGraphStore服务,用于压缩并存储集群中的ExecutionGraph,主要有FileArchivedExecutionGraphStore和MemoryArchivedExecutionGraphStore两种实现类型。

执行完initializeServices方法后,回到上面的runCluster(Configuration configuration, PluginManager pluginManager)方法里,这个方法里还有一行代码比较重要:

            final DispatcherResourceManagerComponentFactory
                    dispatcherResourceManagerComponentFactory =
                            createDispatcherResourceManagerComponentFactory(configuration);

核心是调用了createDispatcherResourceManagerComponentFactory(configuraion)方法,这个方法点进去,会发现它是一个抽象类方法,具体的实现类有好多种:

实现类:

我们今天只分析StandaloneSessionClusterEntrypoint类,所以进去StandaloneSessionClusterEntrypoint类,找到createDispatcherResourceManagerComponentFactory(Configuration configuration) 方法:

    @Override
    protected DefaultDispatcherResourceManagerComponentFactory
            createDispatcherResourceManagerComponentFactory(Configuration configuration) {
        return DefaultDispatcherResourceManagerComponentFactory.createSessionComponentFactory(
                StandaloneResourceManagerFactory.getInstance());
    }

这里会发现StandaloneResourceManagerFactory.getInstance(),会返回一个StandaloneResourceManagerFactory类单例对象。
StandaloneResourceManagerFactory顾名思义就是用来创建StandaloneResourceManager的一个工厂对象。这是后边要用到的一个工厂对象,不过呢现在被包在更大的DefaultDispatcherResourceManagerComponentFactory类里了

可以看到createDispatcherResourceManagerComponentFactory方法返回的类型是DefaultDispatcherResourceManagerComponentFactory类,顾名思义,DefaultDispatcherResourceManagerComponentFactory是用于创建DefaultDispatcherResourceManagerComponent对象的工厂。
接着我们去看看DefaultDispatcherResourceManagerComponentFactory这个工厂对象的create()方法,在这个方法里,首先会创建和启动WebMointorEntrypoint对象,作为Dispatcher对应的Rest entrypoint,通过Rest API将JobGraph提交到Dispatcher上,同时,WebMonitorEndpoint也会提供Web UI需要的Rest API接口实现。

紧接着,DefaultDispatcherResourceManagerComponentFactory这个工厂对象的create()方法里还调用了创建ResourceManager组件的方法,创建了ResourceManager并启动。如下图

紧接着,DefaultDispatcherResourceManagerComponentFactory这个工厂对象的create()方法里还调用了创建DispatcherRunner组件的方法,创建了DispatcherRunner并启动。如下图

在上图中还有

resourceManagerService.start();

这句代码点进去看看,会进入到ResourceManagerService接口的start()方法,ResourceManagerService接口类中有一个实现类是ResourceManagerServiceImpl类。
所以看一下ResourceManagerServiceImpl类的start()方法

    @Override
    public void start() throws Exception {
        synchronized (lock) {
            if (running) {
                LOG.debug("Resource manager service has already started.");
                return;
            }
            running = true;
        }

        LOG.info("Starting resource manager service.");

        leaderElectionService.start(this);
    }

最后一行代码leaderElectionService.start(this);点进去
会进入到LeaderElectionService接口类的start()方法;该接口类有几种类的实现,其中一种是DefaultLeaderElectionService类。
我们看看DefaultLeaderElectionService类的start()方法:

    @Override
    public final void start(LeaderContender contender) throws Exception {
        checkNotNull(contender, "Contender must not be null.");
        Preconditions.checkState(leaderContender == null, "Contender was already set.");

        synchronized (lock) {
            leaderContender = contender;
            leaderElectionDriver =
                    leaderElectionDriverFactory.createLeaderElectionDriver(
                            this,
                            new LeaderElectionFatalErrorHandler(),
                            leaderContender.getDescription());
            LOG.info("Starting DefaultLeaderElectionService with {}.", leaderElectionDriver);

            running = true;
        }
    }

在该方法中,我们重点关注一下createLeaderElectionDriver(···)方法,我们跟踪进去:
会进入到LeaderElectionDriverFactory接口类的createLeaderElectionDriver()方法,该接口类也有几种实现,其中一种是ZooKeeperLeaderElectionDriverFactory类,我们来看看这个类的createLeaderElectionDriver方法实现。

    @Override
    public ZooKeeperLeaderElectionDriver createLeaderElectionDriver(
            LeaderElectionEventHandler leaderEventHandler,
            FatalErrorHandler fatalErrorHandler,
            String leaderContenderDescription)
            throws Exception {
        return new ZooKeeperLeaderElectionDriver(
                client, path, leaderEventHandler, fatalErrorHandler, leaderContenderDescription);
    }

可以看到最核心的代码是

new ZooKeeperLeaderElectionDriver(
                client, path, leaderEventHandler, fatalErrorHandler, leaderContenderDescription);

我们跟踪进去ZooKeeperLeaderElectionDriver(···)方法,会进入到ZooKeeperLeaderElectionDriver类的ZooKeeperLeaderElectionDriver()方法,该方法的具体实现我们看一下:

    public ZooKeeperLeaderElectionDriver(
            CuratorFramework client,
            String path,
            LeaderElectionEventHandler leaderElectionEventHandler,
            FatalErrorHandler fatalErrorHandler,
            String leaderContenderDescription)
            throws Exception {
        checkNotNull(path);
        this.client = checkNotNull(client);
        this.connectionInformationPath = ZooKeeperUtils.generateConnectionInformationPath(path);
        this.leaderElectionEventHandler = checkNotNull(leaderElectionEventHandler);
        this.fatalErrorHandler = checkNotNull(fatalErrorHandler);
        this.leaderContenderDescription = checkNotNull(leaderContenderDescription);

        leaderLatchPath = ZooKeeperUtils.generateLeaderLatchPath(path);
        leaderLatch = new LeaderLatch(client, leaderLatchPath);
        this.cache =
                ZooKeeperUtils.createTreeCache(
                        client,
                        connectionInformationPath,
                        this::retrieveLeaderInformationFromZooKeeper);

        running = true;

        leaderLatch.addListener(this);
        leaderLatch.start();

        cache.start();

        client.getConnectionStateListenable().addListener(listener);
    }

这里核心关注2行代码:

        leaderLatchPath = ZooKeeperUtils.generateLeaderLatchPath(path);
        leaderLatch = new LeaderLatch(client, leaderLatchPath);

可以看到选举也是用了LeaderLatch机制,这个机制其实好多分布式软件都在用,大家可以去了解一下LeaderLatch选主节点的机制。

posted @ 2023-06-06 09:34  田野与天  阅读(284)  评论(0编辑  收藏  举报