图 1‑1 PDI核心组件
Spoon是构建ETL Jobs和Transformations的工具。Spoon可以以拖拽的方式图形化设计,能够通过spoon调用专用的数据集成引擎或者集群。
Data Integration Server是一个专用的ETL Server,它的主要功能有:
功能 |
描述 |
执行 |
通过Pentaho Data Integration引擎执行ETL的作业或转换 |
安全性 |
管理用户、角色或集成的安全性 |
内容管理 |
提供一个集中的资源库,用来管理ETL的作业和转换。资源库包含所有内容和特征的历史版本。 |
时序安排 |
在spoon设计者环境中提供管理Data Integration Server上的活动的时序和监控的服务 |
Enterprise Console提供了一个小型的客户端,用于管理Pentaho Data Integration企业版的部署,包括企业版本的证书管理、监控和控制远程Pentaho Data Integration服务器上的活动、分析已登记的作业和转换的动态绩效。
名称 |
描述 |
Spoon |
通过图形接口,用于编辑作业和转换的桌面应用。 |
Pan |
一个独立的命令行程序,用于执行由Spoon编辑的转换和作业。 |
Kitchen |
一个独立的命令行程序,用于执行由Spoon编辑的作业。 |
Carte |
Carte是一个轻量级的Web容器,用于建立专用、远程的ETL Server。 |
图 1‑2 PDI概念模型图
要了解Kettle的执行分为两个层次:Job和Transformation。两个层次的最主要区别在于数据传递和运行方式。
Transformation(转换)是由一系列被称之为step(步骤)的逻辑工作的网络。转换本质上是数据流。下图是一个转换的例子,这个转换从文本文件中读取数据,过滤,然后排序,最后将数据加载到数据库。本质上,转换是一组图形化的数据转换配置的逻辑结构。
转换的两个相关的主要组成部分是step(步骤)和hops(节点连接)。
转换文件的扩展名是.ktr。
Steps(步骤)是转换的建筑模块,比如一个文本文件输入或者一个表输出就是一个步骤。在PDI中有140多个步骤,它们按不同功能进行分类,比如输入类、输出类、脚本类等。每个步骤用于完成某种特定的功能,通过配置一系列的步骤就可以完成你所需要完成的任务。
Hops(节点连接)是数据的通道,用于连接两个步骤,使得元数据从一个步骤传递到另一个步骤。在上图所示的转换中,它像似顺序执行发生的,但事实并非如此。节点连接决定了贯穿在步骤之间的数据流,步骤之间的顺序不是转换执行的顺序。当执行一个转换时,每个步骤都以自己的线程启动,并不断的接受和推送数据。
注意:所以的步骤是同步开启和运行的,所以步骤的初始化的顺序是不可知的。因为我们不能在第一个步骤中设置一个变量,然后在接下来的步骤中使用它。
在一个转换中,一个步骤可以有多个连接,数据流可以从一个步骤流到多个步骤。在Spoon中,hops就想是箭,它不仅允许数据从一个步骤流向另一个步骤,也决定了数据流的方向和所经步骤。如果一个步骤的数据输出到了多个步骤,那么数据既可以是复制的,也可以是分发的。
Jobs(工作)是基于工作流模型的,协调数据源、执行过程和相关依赖性的ETL活动。
Jobs(工作)将功能性和实体过程聚合在了一起。下图是一个工作的例子。
一个工作中展示的任务有从FTP获取文件、核查一个必须存在的数据库表是否存在、执行一个转换、发送邮件通知一个转换中的错误等。最终工作的结果可能是数据仓库的更新等。
工作由工作节点连接、工作实体和工作设置组成。
工作文件的扩展名是.kjb。
根据变量的作用域,变量被分为两类:环境变量和kettle变量。
环境变量可以通过edit menu下面的set environment variables对话框进行设置。使用环境变量的唯一的问题是,它不能被动态的使用。如果在同一个应用服务器中执行两个或多个使用同一环境变量的转换,将可能发生冲突。环境变量在所以使用jvm的应用中可见。
Kettle变量用于在一个小的动态范围内存储少量的信息。Kettle变量是kettle本地的,作用范围可以是一个工作或转换,在工作或转换中可以设置或修改。Set variable步骤用来设置与此变量有关的工作从此设置其作用域,如:父工作、祖父工作或根工作。
-rep : Repository name 任务包所在存储名
-user : Repository username 执行人
-pass : Repository password 执行人密码
-job : The name of the job to launch 任务包名称
-dir : The directory (don''t forget the leading / or \)
-file : The filename (Job XML) to launch
-level : The logging level (Basic, Detailed, Debug, Rowlevel, Error, Nothing) 指定日志级别
-log : The logging file to write to 指定日志文件
-listdir : List the directories in the repository 列出指定存储中的目录结构。
-listjobs : List the jobs in the specified directory 列出指定目录下的所有任务
-listrep : List the defined repositories 列出所有的存储
-norep : Don''t log into the repository 不写日志
kitchen.bat 后面可以是-也可以是/然后再加options
Options:
/rep : Repository name
/user : Repository username
/pass : Repository password
/job : The name of the job to launch
/dir : The directory (dont forget the leading /)
/file : The filename (Job XML) to launch
/level : The logging level (Basic, Detailed, Debug, Rowlevel, Error, Nothing)
/logfile : The logging file to write to
/listdir : List the directories in the repository
/listjobs : List the jobs in the specified directory
/listrep : List the available repositories
/norep : Do not log into the repository
/version : show the version, revision and build date
/param : Set a named parameter
/listparam : List information concerning the defined parameters in the specified job.
/export : Exports all linked resources of the specified job. The argument is the name of a ZIPfile.
而options 后面可以是=也可以是:也可以是空格
kitchen.bat /file d:\ 或者 -file=D:\ 或者/file:D:\等等都可以。
kitchen.bat /norep -file=D:/kettledata/mysal2orcle.kjb >> kitchen_%date:~0,10%.log
上面的含义是,使用kitchen.bat 命令来执行job文件,job文件的存放路径是D:/kettledata/mysal2orcle.kjb,并且将执行的结果输出到 kitchen_%date:~0,10%.log文件中。
XUL Framework是一个试图为不同UI技术提供统一样式的项目。它的目标是使得多种UI技术(如:Swing、SWT、GWT)能够提交出一个统一的用户接口而不必每次重写描述层。XUL的常见案例有:普通对话框、可定制的菜单和工具栏、新的工具应用。
XUL是英文“XML User Interface Language”的首字母缩写。它是为了支持Mozilla系列的应用程序(如Mozilla Firefox和Mozilla Thunderbird)而开发的使用者界面标示语言。顾名思义,它是一种应用XML来描述使用者界面的标示语言。
(1) 加载XUL文件
(2) 添加Event Handlers
(3) 为SWT提供菜单栏
(4) 为菜单栏添加菜单项
PS:这次仅仅介绍了Kettle的基本概念和术语,这部分是由我同学来完成的。Kettle应该还算比较小众的开源软件资料不多,希望这次的分析能够对大家有一定的帮助。大部分的分析都是自己理解的,所以有所偏差或错误请大家指正。接下来准备分析,Kettle的插件体系结构、转换机制、job运行机制。
插件是一种遵循统一的预定义接口规范编写出来的程序,应用程序在运行时通过接口规范对插件进行调用,以扩展应用程序的功能。在英文中插件通常称为plug-in、plugin或者plug in。插件最典型的例子是Eclipse开发平台,Microsoft的ActiveX控件和COM(Component Object Model,部件对象模型)实际上ActiveX控件不过是一个更高继承层次的COM而已。此外还有Photoshop的滤镜(Filter)也是一种比较常见的插件,还有就是Mozilla Firefox,Foobar等等也遵循着插件机制。
插件最吸引人的地方当然就是其所实现“运行时(Run-time)”功能扩展。这意味着软件开发者可以通过公布插件的预定义接口规范,从而允许第三方的软件开发者通过开发插件对软件的功能进行扩展,而无需对整个程序代码进行重新编译。运行时是相对于编译时(Assembly-time)而言的。一般来说,软件开发者对软件功能更新时,是在源代码级别进行更新,然后对整个程序进行重新编译,进而发布应用程序的新版本,这就是编译时的软件更新。
插件的本质在于不修改程序主体(或者程序运行平台)的情况下对软件功能进行扩展与加强,当插件的接口公开后,任何公司或个人都可以制作自己的插件来解决一些操作上的不便或增加新的功能,也就是实现真正意义上的“即插即用”软件开发。“平台+插件软件结构”是将一个待开发的目标软件分为两部分,一部分为程序的主体或主框架,可定义为平台,另一部分为功能扩展或补充模块,可定义为插件。
在进行软件开发之前,是否采用“平台+插件软件结构”进行软件开发,还要依据具体的软件需求情况进行确定,但一般来讲,使用“平台+插件软件结构”进行软件设计会给所开发软件增加新的生命力。当确定“平台+插件的软件结构”之后,就要分析哪些部分功能由主体完成(即平台的基本功能),哪些部分功能由插件完成(即需要扩展的插件功能)。平台所完成的功能应为一个软件系统的核心和基础,这些基本功能即可为用户使用,也可为插件使用,就是又可以把平台基本功能分为两个部分,内核功能和插件处理功能。平台的内核功能是整个软件的重要功能,一个软件的大部分功能因由内核功能完成。平台的插件处理功能用于扩展平台和管理插件,为插件操纵平台和与插件通信提供标准平台扩展接口。插件所完成的功能是对平台功能的扩展与补充,一般插件完成系列化功能。
为了实现平台+插件结构的软件设计需要定义两个标准接口,一个为由平台所实现的平台扩展接口,一个为插件所实现的插件接口。这里需要说明的是:平台扩展接口完全由平台实现,插件只是调用和使用,插件接口完全由插件实现,平台也只是调用和使用。平台扩展接口实现插件向平台方向的单向通信,插件通过平台扩展接口可获取主框架的各种资源和数据,可包括各种系统句柄,程序内部数据以及内存分配等。插件接口为平台向插件方向的单向通信,平台通过插件接口调用插件所实现的功能,读取插件处理数据等。
平台插件处理功能包括插件注册、管理和调用,以及平台扩展接口的功能实现。插件注册为按照某种机制首先在系统中搜索已安装插件,之后将搜索到的插件注册到平台上,并在平台上生成相应的调用机制,这包括菜单选项、工具栏、内部调用等。插件管理完成插件与平台的协调,为各插件在平台上生成管理信息以及进行插件的状态跟踪。插件调用为调用各插件所实现的功能。平台插件处理功能实现的另一部分功能为平台扩展接口的具体实现。
开发支持插件功能的应用程序必须解决一个问题:如何在主程序与插件间正确地互相通信。为了在主程序与插件之间能正确地互相通信,应该先制定一套通信标准,这套通信标准就是接口,主程序与插件只能通过制订好的接口进行通信。软件开发中,接口只是定义功能并规定调用功能的形式,而不包含功能的实现。接口实质上是软件模块的调用规范。在后续章节中我们将会介绍kettle开发的插件中,常用的几种通讯方式。
就开发支持插件功能的应用程序而言,一般来说由主程序的开发者来制订接口,如果希望其他的开发人员能开发相关的插件,只要公开相关接口即可。接口功能一般由插件方实现。因为插件的实现也要调用主程序的功能,所以接口功能也可能由主程序来实现。也就是说,主程序与插件的信息流可能是双向的。
接口的调用规范与功能实现互相分离有一个很大的优点:尽管不同的插件开发者对同一个接口的具体实现不同,但是在主程序中对这些插件的调用方式是一样的。如果有主程序实现的接口,在不同的插件中也可以用相同的使用方式调用主程序的功能。这极大的提高了应用程序的灵活性。
主程序中,插件管理部分用于管理插件的安装和删除,并将所有安装插件的信息保存到适合的地方,例如保存到注册表或配置文件中。主程序启动时,根据插件的配置信息加载插件模块,然后获得插件的输出函数或输出类的指针并加以保存,如果需要的话,可以向主程序增加界面接口元素,如菜单、工具条按钮等。在主程序中当点击与插件相关联的接口元素时,就会触发插件调用函数,在插件调用函数中使用主函数中所保存的插件信息调用插件中实现的功能。在调用插件输出函数时也可以把主程序中实现的接口传递给插件方。
图 2‑1 Kettle插件架构
Kettle分为kettle平台、各类插件。其中kettle平台是整个系统的基础,包括UI、插件管理、元数据管理和数据集成引擎。UI显示Spoon这个核心组件的界面,通过xul实现菜单栏、工具栏的定制化,显示插件界面接口元素。元数据管理引擎管理ktr、kjb或者元数据库,插件通过该引擎获取基本信息。插件管理引擎主要负责插件的注册。数据集成引擎负责调用插件,并返回相应信息。
Kettle是众多“可供插入的地方”(扩展点)和“可以插入的东西”(扩展)共同组成的集合体。在我们的生活中,电源接线板就是一种“扩展点”,很多“扩展”(也就是电线插头)可以插在它上面。
在Kettle中不管是以后的扩展还是系统集成的功能,本质上来讲都是插件,管理方式和运行机制是一致的。系统集成的功能点也均实现了对应的扩展接口,只是在插接的说明上略有不同。
Kettle的扩展点包括step插件、job entry插件、Database插件、Partioner插件、debugging插件,这里我们重点介绍step、job entry、database插件。暴露的扩展点如下表所示:
表 1 Step扩展接口
Java接口 |
基类 |
主要功能 |
StepMetaInterface |
BaseStepMeta |
存储step设置信息 验证step设置信息 序列化step设置信息 提供获取step类的方法 |
StepDialogInterface |
BaseStepDialog |
step属性信息配置窗口 |
StepInterface |
BaseStep |
处理rows |
StepDataInterface |
BaseStepData |
为数据处理提高数据存储 |
表 2 job entry扩展接口
Java接口 |
基类 |
主要功能 |
JobEntryInterface |
JobEntryBase |
存储job entry设置信息 序列化job entry设置信息 提供获取job entry类的方法 执行job entry任务 |
JobEntryDialogInterface |
JobEntryDialog |
job entry属性信息配置窗口 |
表 3 Database 扩展接口
Java接口 |
基类 |
主要功能 |
DatabaseInterface |
BaseDatabaseMeta |
访问各类数据库 |
Kettle中的插件包含两部分,一是系统本身就已经实现的功能点,在源码目录src中说明,如kettle-steps.xml;二是系统之外开发的插件,在plugins目录对应插件目录下的plugins.xml说明,plugins/steps/S3CsvInput/plugins.xml。
系统集成插件定义(step为例)
表 4 系统自带插件定义
内容 |
位置 |
插件说明信息 |
src/kettle-steps.xml,所有插件集中说明 |
插件源码 |
src与src-ui下,org.pentaho.di.steps.插件名 |
插件图片 |
插件说明xml中说明 |
插件界面文字说明 |
org.pentaho.di.steps.插件名.messages |
插件说明信息中包括描述信息、类名(包括package,反射用)、父级目录(Spoon左侧栏目录)、提示信息和图片信息。Kettle使用国家化方式编程,所以软件中的所有文字描述均由messages_**.properties提供。
图 2‑2 系统集成插件说明xml结构
扩展插件定义
所以新开发的扩展插件,均放在同一的目录下进行管理,插件管理模块会自动去该目录下进行搜索查找。插件目录结构如下所示:
图 2‑3 扩展插件目录结构
表 5 扩展插件定义
内容 |
位置 |
插件说明信息 |
plugins/插件类型/插件名称/plugin.xml |
插件源码 |
*.jar |
插件图片 |
plugins/插件类型/插件名称/ |
插件依赖包 |
plugins/插件类型/插件名称/ |
扩展插件与系统集成插件的说明内容相似,扩展插件增加ID属性和依赖属性,同时他的目录结构、描述信息和提示信息均能进行国际化配置。
图 2‑4 扩展插件说明xml结构
Spoon在启动的时候会对所有插件进行注册,并保存在PluginRegistry类里面。平台通过查找PluginRegistry注册表获取插件信息。Kettle安装插件需要进行重启,卸载插件也只需简单的删除plugins目录结构下对应的文件即可。
图 2‑5 插件注册时序图
图 2‑6 plugin注册相关的UML类图
PluginRegistry首选注册本系统的插件类型处理类,源码中注册了7中类型,我们这里仅介绍3中,并以StepPluginType为例。注册类型处理类后,PluginRegistry按照不同的类型进行插件搜索(模板模式),基类BasePluginType提供了本地搜索、jar搜索、xml信息搜索3种钩子。根据搜索结果,按照不同的插件类型存储在PluginRegistry中。
PluginRegistry提供了插件查找功能,准确的来说是插件信息的查找功能。以steps在左侧功能栏里面的显示为例,进行插件查找的说明。提供了getPlugins获取指定插件类型列表、getPlugin获取指定成名插件、getCateories获取目录结构、getClass获取指定插件类等方法。
图 2‑7 Spoon中step列表
左侧显示由Spoon.refreshCoreObjects()函数实现,如果选择时trans相关的内容,将显示所有的step插件。流程图如下所示:
图 2‑8 spoon界面step插件显示流程
Kettle中调用插件时,平台通过元素管理引擎获取对应的插件信息,通过反射生成插件对象,调用对应的函数。Kettle以外观模式的方式调用插件,我们以双击某个插件图表,弹出对应配置界面为例进行说明,具体的转换时调用将在后面进一步说明。
Spoon界面交互相关的处理器都封装到SpoonDelegates中,根据不同的事件类型调用对应的事件处理函数。UML类图如下所示。
图 2‑9 事件代理类
SpoonStepsDelegate提供了与UI交互相关的处理事件,如复制、删除、粘贴、编辑等。双击某个step时会调用编辑功能,编辑功能是对插件StepDialogInterface的封装。时序图如下:
图 2‑10 双击编辑step时序图
双击是TransGraph对象注册的时间,双击是根据页面上的坐标信息获取双击的stepmeta对象(来自于*.ktr)。然后,将这个对象传给事件代理类处理,根据stepmeta对象,获取对应的插件类名,通过反射生成StepDialogInterface的实例并调用open()方法。
Kettle插件之间天生就具有通信共享数据的特点,kettle中最主要通信方式是通过插件时间共同关联一个数据类对象的方式进行通信;使用单例模式实现插件间信息共享。
第一种方式还设计多线程同步的问题,在后面的章节中将会进行重点介绍。
Kettle并不能做到热插拔,每次添加或者删除插件的时候都需要重启。安装或删除插件,只需要在plugins文件夹下添加或删除对应的文件即可。
元数据主要包括转换元数据(.ktr)和Job元数据(.kjb),元数据也可以存储在数据库中,这里我们主要介绍文件存储形式的。
元数据管理类包括TransMeta,该类定义了一个转换(对应一个.ktr文件),提供了保存和加载该文件的方法;JobMeta类,同样对应于一个工作(.kjb文件),提供保存和加载方法。StepMeta类保存的是Step的一些公共信息的类,每个类的具体的元数据将保存在显示了StepMetaInterface的类里面。
两个类中主要保存的信息如下:
代码 1 TransMeta类主要属性
1 private Liststeps; 2 3 private List hops; 4 5 private String name; 6 7 private Result previousResult;//上一个jobentry的执行结果。 8 9 private List ;//resultRows;这次trans执行后的数据结果。 10 11 private List resultFiles;
steps字段对应于.ktr中的
代码 2 JobMeta类主要属性
1 protected String name; 2 3 protected String filename; 4 5 public Listjobentries;//保存jobentry列表 6 7 public List jobhops;//保存jobentries之间的链接关系。 8 9 public List databases;
图 3‑1 TransGraph类与显示
选中转换标签后,红框内的编辑区对象对应org.pentaho.di.ui.spoon.trans包中的TransGraph类。
图 3‑2 JobGraph与显示
选中Job标签后,红框内的编辑区对象对应org.pentaho.di.ui.spoon.job包中的JobGraph类。
Trans类负责转换执行相关的所有任务,包括转换加载、相关插件的实例化、初始化、运行、监视转换执行,并把内容放置到TransInfo类中。
Step初始化线程包装类,使用多线程,调用所有StepInterface实现类的Init函数。
把插件的主要实现类全部存储在这个类中,方便集中调用。
1 public class StepMetaDataCombi 2 3 { 4 5 public StepMeta stepMeta; 6 7 public String stepname; 8 9 public int copy; 10 11 public StepInterface step; 12 13 public StepMetaInterface meta; 14 15 public StepDataInterface data; 16 17 }
步骤处理线程包装类,这个类能够处理异常并将其记录到日志中。同时,也能够在异常发生或者执行结束后,记录相关内容、关闭相关资源。
Job的执行类,本身实现了Thread是一个单独的线程。Job entry可以是单独的线程,也可以是顺序执行,大多数情况都是顺序执行下一步以上一步的执行结果为基础。Job类也包括转换加载、相关插件的实例化、初始化、运行、监视Job执行。
每个转换步骤都是ETL数据流里面的一个任务。转换步骤包括输入、处理和输出。输入步骤从外部数据源获取数据,例如文件或者数据库;处理步骤处理数据流,字段计算,流处理等,例如整合或者过滤。输出步骤将数据写会到存储系统里面,例如文件或者数据库。
图 1 转换步骤示例
Kettle为扩展插件提供了4个扩展点,这4个扩展点也是每个步骤的组成。每个类都有其特定的目的及扮演的角色。以TableInput为例,下图说明了这4个类的继承体系。
图 2 StepInterface继承体系
实现StepInterface接口的类,在转换运行时,将是数据实际处理的位置。每个执行线程都表示一个实现StepInterface的实例。
BaseStep实现了StepInterface是各step具体实现类的基类。完成了公用的处理函数,如putRow(),但是对于更具体的processRow()在StepBase的子类中。StepBase的主要成员有
public ArrayList
StepBase的子类每次从inputRowSets中取出一行数据,向outputRowSets中写入一行数据。
图 3 StepDataInterface继承体系
实现StepDataInterface接口的类为数据类,当插件执行时,对于每个执行执行的线程都是唯一的。保存于step相关的数据信息,比如行的元数据信息。
图 4 StepMetaInterface继承体系
实现了StepMetaInterface接口的类为元数据类。它的职责是保存和序列化特定步骤的实例配置,例如保存步骤的名称、字段名称等,如何生成加载xml或者读写数据库。
图 5 StepDialogInterface继承体系
实现了StepDialogInterface接口的类为对话框类,该类实现了该步骤与用户交互的界面,它显示一对话框,通过对话框用户可以根据自己的要求进行步骤的设定。该对话框类与元数据类关系非常密切,对话框里面的配置数据均会保存在元数据类里面。
图 6 步骤之间通信机制
RowSet的实现类,负责步骤之间的相互通信,rowset对象即是前一个step的成员也是后一个step的成员,访问是线程安全的。
图 7 RowSet实现类内存快照
RowSet类中包含源step,目标step和由源向目标发送的一个rowMeta和一组data。其中data数据是以行为单位的队列(queArray)。一个RowSet作为此源step的outputrowsets的一部分。同时作为目标step的inputRowsets一部分。源Step每次向队列中写一行数据,目标step每次从队列中读取一行数据。
图 8 RowSet实现类
所有的data均擦除为object对象。步骤与步骤之间以行为单位进行处理,自然需要知道每行的结构,即行元数据。行元数据至少需要包括类型、名称,当然还可能包括字段长度、精度等常见内容。
行元数据不仅在执行的时候需要,而且在转换设置的时候同样需要。每个步骤的行元数据都会保存在.ktr文件或者数据库里面,所以可以根据步骤名称从TransMeta对象中获取行元数据。
行元数据的UML类图结构如下所示,主要有单元格元数据组成行元数据。在现有的版本中,支持的数据类型有String、Date、BigNumber、Boolean、SerializableType、Binary、Integer、Numberic。
图 9 行元数据UML类图
图 10 Trans执行时序图
在真正运行trans之前,还需要对运行模式进行一个设置。设置结果,会传给TransGraph.start(executionConfiguration)。配置界面如下所示:
图 11 执行转换模式设置
实例化Trans的基本流程如下,Trans类时最后真正执行转换的类。实例化之前需要配置启动项,保持.ktr文件同步,然后实例化Trans类。最后,开启后台程序,这样不会影响UI的操作,真正的转换在后台执行。
图 12 实例化Trans流程图
trans类的执行有execute()负责,主要包含两个步骤:转换执行前的准备工作和所有线程的开启。Trans每一个步骤都会对应一个独立的线程,线程之间公国RowSet进行通信交互。
代码 Trans执行代码
1 public void execute(String[] arguments) throws KettleException { 2 3 prepareExecution(arguments); 4 5 startThreads(); 6 7 }
该步骤,主要完成对通信类的初始化,对步骤的包装初始化。最后启动各个步骤初始化线程,即调用各个步骤的init()方法。准备结束之后,步骤之间的通信机制完成了,各个步骤的初始化工作也完成了。具体的流程如下所示:
图 13 准备执行流程图
Trans转换执行引擎类,通过startThreads()启动步骤线程。为所有步骤添加监听器,在开启监听进程对所有线程进行监听。具体的步骤如下所示
图 14 启动所有步骤线程
实现StepInterface的不同的step各个功能个不一样,但是它们之间也有一定的规律性。下图只列举了两个step,(TextInput)文本输入和Uniquerow(去重)。BaseStep封装了getRow()和putRow()方法,从上一个步骤获取数据和将数据输入到下一个步骤。
基类BaseStep采取了统一的处理方式,调用子类processRow以行为单位处理,核心代码如下。
while (stepInterface.processRow(meta, data)&& !stepInterface.isStopped());
processRow( )通用过程是:调用基类BaseStep 的getRow( )得到数据,对一行数据处理,处理之后调用基类putRow( )方法数据保存至outputRowSets(即next step的inputRowSets)
图 15 TextInput与Uniquerow
Trans中的ETL过程(每个step)以行为单位处理,其中行的元数据信息RowMeta和数据信息统一保存在RowSet对象中。
在RowSet中RowMeta的成员的调试结果如下。可见rowMeta储存了每列数据的名称和类型。第一列列名flag,数据是长度为1的String;第二列列名id…
RowSet的数据信息在queArray队列中,调试结果如下:可以看出第一个数据元素是一个Object包含了3列,数据内容为(N,1,a…)
一个job项代表ETL控制流中的一项逻辑任务。Job项将会顺序执行,每个job项会产生一个结果,能作为别的分支上job项的条件。
图 1 job项示例
图 2 Job entry类图结构
JobEntryInterface是Job Entry插件的主要实现接口。主要包含以下功能:
1 保存Job Entry设置
实现类使用私有变量保存设置的参数,通过get、set方法获取和设置。Dialog实现类会通过这些方法,保存或设置设置界面上的参数。同时,需要提供一个深度拷贝的方法,因为在一些保存参数且可能修改的地方会调用。
图 3 JobEntryTrans配置界面
2 序列化插件
插件要实现对本插件的序列化,实现两种方式xml与数据库。
图 4 转换插件xml序列化结果
3 输出信息提供
一个job entry支持三种类型的输出:true、flase和无条件。这三种情况不是所有的job entry插件都会同时支持的,例如dummy job entry仅支持true和false。所以,插件必须显现两类函数,来查看支持哪种结果。
public boolean evaluates()//是否支持true、false
public boolean isUnconditional()//是否支持无条件执行
4 执行任务
负责工作的执行。
public Result execute()//执行具体的逻辑,需要结果和开始到该项的距离
prev_result.setNrErrors()//设置执行过程中的异常数
prev_result.setResult()//设置结果,如果不知道true/false,结果不设置
最后返回prev_result。
负责构建和打开参数设置对话框。Spoon通过调用open函数打开该对话框,spoon是使用swt框架的,所以对话框也应使用swt来实现。
每一个jobEntryInterface的实现类在完成相应功能时,返回结果的类型。
主要成员变量:
1 private boolean result;执行是否出现异常 2 3 private int exitStatus; 执行结果状态 4 5 private Listrows;一个jobEntry完成处理后的数据(若存在) 6 7 private Map resultFiles;
图 5 Job开启时序图
Job的开启与Trans相类似,配置执行的参数,检查.kjb文件是否发生变化,实例化一个Job对象,开启该线程。
主要工作是从JobMeta的JobHopMeta找到job入口jobentry信息,根据开始条件调用真正执行jobentry的execute方法2,代码如下所示:
代码 4 Job.excute()关键代码
1 startpoint=jobMeta.findJobEntry(JobMeta.STRING_SPECIAL_START, 0, false);// 找到Job开始组件 2 JobEntrySpecial jes = (JobEntrySpecial) startpoint.getEntry(); 3 // JobEntrySpecial是启动job的job项目 4 Result res = null; 5 while ( (jes.isRepeat() || isFirst) && !isStopped()){ 6 //符合开始条件时,调用execute方法2 7 isFirst = false; 8 res = execute(0, null, startpoint, null, 9 Messages.getString("Job.Reason.Started")); 10 }
execute()方法包含,的参数有执行次数(START不算,从0开始,顺序执行)、接一个Entry执行结果、当前Entry的拷贝、前一个Entry拷贝和原因。
主要功能是根据参数startpoint,提取对应的jobentry,执行对应的jobentry操作,再根据JobMeta的hop信息依次得到下一个jobentry,递归调用。具体的执行步骤如下所示:
图 6 Job执行步骤
具体每个组件的执行体对应org.pentaho.di.job.entries包内每个entry的具体实现。
execute()方法2中调用jobEntry的execute()完成jobEntry的具体功能。
final Result result = cloneJei.execute(prevResult, nr, rep, this);
不同的Job项目(JobEntry)实现差别很大。
JobEntrySpecial
功能是开启一个job,只是简单地对传递来的preResult设置它的的result属性值为true,(Job项目据此判断前一结果执行完毕)。返回该对象即可。
JobEntryTableExit
功能是判断一个table是否存在数据库中。JobEntryTableExit Job项目有属性tablename和DatabaseMeta(对数据库的元数据信息描述)根据DatabaseMeta得到一个Dabase对象db,建立连接db.connect(); 调用db.checkTableExists(tablename)根据此返回值设置preResult的result属性为否为true。返回preResult对象。
JobEntryTrans
JobEntryJob和JobEntryTrans是嵌套job或trans的Job项目(JobEntry)。它们是比较复杂的job项目。
作用是执行一个trans。首先实例化一个TransMeta,之后实例化Trans。调用trans.start(),当执行完毕后调用函数trans.getResult(),并把结果加到preResult中,返回该对象即可。
补充说明
Result中也可以有处理数据,这些处理数据可以作为下一个Job项目(JobEntry)的输入。但是容量受内存容量限制。
PDI使用数据库插件来进行数据库的正确连接、执行SQL,同时也考虑现有数据的各种特殊功能和不同限制。
在PDI里面,已经集成了非常多的数据库插件,大部分的插件都会继承自BaseDatabaseMeta。下面所示的方法通常都需要被重写,基类里面并没有相关的实现。要实现的方法主要分成3大主题:连接信息、SQL方言和功能标记。
1 连接详情
当PDI建立数据库连接时将会调用这些函数,或者数据库设置对话框里显示与方言有关的内容时也会调用。
2 SQL Generation
构建有效的SQL数据库方言时会调用这些方法。
3 功能标记
查询使用的数据库是否支持该功能。