Kylin运行过程中会生成很多不同的Job,这些job是如何调度执行的呢?本文从源码的层面分析kylin是如何完成多个任务的并发执行,以及如何在其他项目中使用这种多线程的模型。
初始化流程
首先,kylin使用了spring框架提供RESTful接口,JobController这个controller实现了InitializingBean接口,这就意味着在初始化bean的时候spring会调用这个类实现的afterPropertiesSet方法执行初始化操作。在初始化函数中会加载kylin的配置文件(文件名为kylin.properties),配置文件的加载首先查找JVM的环境变量KYLIN_CONF,如果不存在则进一步查找KYLIN_HOME,找到其下面的conf目录,然后加载配置文件,并且还会查找该目录下的kylin.properties.override文件是否存在,如果存在则将后者的配置覆盖kylin.properties文件的配置。
在kylin server中分为三种运行模式(可以通过kylin.server.mode配置项配置,默认为all),分别为all、job和query,前两种是可以执行任务的,而query模式下kylin server只提供元数据的操作以及SQL查询,不能执行构建cube、合并cube之类的任务。因此可以看到只有在前两种模式下,该函数会启动一个线程创建一个DefaultScheduler对象,该对象是全局唯一的,然后执行该对象的init方法。
init函数中会首先需要从zookeeper中获取一个lock,这个锁是互斥的,相同的zookeeper的路径只能由一个kylin server实例持有,这个锁是由hbase和zookeeper共同标识的,这意味着不同的kylin server必须使用不同的hbase的元数据表,zookeeper的地址由kylin.storage.url配置项标识,指定quorum和端口,然后再根据kylin.metadata.url配置项查找kylin server使用的元数据存储在hbase的表,默认的hbase表为kylin_metadata,如果这个配置项是hbase:开头的则使用这个配置,否则使用默认的表,加锁的路径为/kylin/job_engine/lock/hbase表名。成功获取锁之后会初始化一个线程池:
//根据配置对象获取manager对象
executableManager = ExecutableManager.getInstance(jobEngineConfig.getConfig());
//创建一个大小为1的线程池,这个线程池中周期性的调度查看是否有可执行的任务。
fetcherPool = Executors.newScheduledThreadPool(1);
//真正调度任务执行的线程池的大小,默认为10个,使用的队列是无最大上限的。
int corePoolSize = jobEngineConfig.getMaxConcurrentJobLimit();
jobPool = new ThreadPoolExecutor(corePoolSize, corePoolSize, Long.MAX_VALUE, TimeUnit.DAYS, new SynchronousQueue<Runnable>());
//所有正在执行的任务都会保存在context中。
context = new DefaultContext(Maps.<String, Executable> newConcurrentMap(), jobEngineConfig.getConfig());
//从元数据库中获取所有的READY状态的任务,置为ERROR,以供接下来重新调度执行。
for (AbstractExecutable ex