以前用的商用talend开发,发布,TAC调度水到渠成,山回路转,来到了使用了开源的kettle新环境,瞬间有种从空调房搬到了电风扇集体宿舍的感觉,kettle这个不行,这个矬,那个慢,我就是自己写Java也不愿用kettle,能不能买个talend,一星期后,真香!废话结束,进入正题
90万数据导入mysql,竟然耗时2个半小时还没跑完,我还对着mysql发了顿牢骚(mysql怎么这么矬,虽然发现mysql背锅了,其实发现mysql大有来头,后面要研究下,难怪有专门的mysql专家,后续再说吧),其实是jdbc的问题,在输入和输出的数据库连接选项中配置以下四个参数,每个参数的解析可以参考(1)魂灵舞的kettle大数据量读写mysql性能优化,写的非常好,可以从原来的几个小时,瞬间变成2分钟完成,如图1-1。
rewriteBatchedStatements true
useServerPrepStmts false (写的时候)
useServerPrepStmts true (读的时候)
characterEncoding utf8
useCompression true
静态变量没啥好说的,直接写在命名参数上即可,设置动态变量一般是在一个作业的第一个转换内,因为设置的变量只能在之后的转换或者步骤中使用,即在转换A中设置了变量a,那a只能在A之后的步骤中起作用,在A本身是不起作用的哟(特别注意),选一个输出控件,这里以表输出为例,如图2-1,输入可以是从数据取出来的数据赋值给变量,注意结果只能为一行多列,多列可以设置多个变量,结果多行了会报错,原因很简单,自己想想就知道了,多行的可以用
"复制记录到结果"来装(循环再讲);
循环有两种,一种是实现for(int i=0;i<10;i++),一种是实现foreach(int x:array)的,一种是转换内部的循环
//judge_and_set_variable
var prevRow=previous_result.getRows(); //获取上一步骤的结果,其实就是获取array
if(prevRow == null && (prevRow.size()=0))
{
false;
}
else
{
parent_job.setVariable("tables",prevRow);
parent_job.setVariable("size",prevRow.size()); //获取上一步结果数组长度赋值给变量size
parent_job.setVariable("i",0);//初始化变量i=0
parent_job.setVariable("TABLENAME",prevRow.get(0).getString("source_table",""));//获取数组array[0]的source_table赋值给变量"TABLENAME
true; //返回true不可少,少了节点之间的连接跑不下去
}
判别后面接循环体需要的操作,然后设置循环变量自增,也用"JavaScript"实现,具体代码如下;
var prevRow=previous_result.getRows(); //获取array
var size =new Number(parent_job.getVariable("size")); //获取变量size
var i =new Number(parent_job.getVariable("i"))+1; //获取变量i=i+1
if(i<size)
{
parent_job.setVariable("TABLENAME",prevRow.get(i).getString("source_table",""));//遍历array[i]
}
parent_job.setVariable("i",i);//重置变量i
true;//返回true不可少,少了节点之间的连接跑不下去
Java代码是夹杂在输入,输出之间的,如把sql脚本文件的内容读取出来,赋值给一个变量,再把变量给到表输入,这样可以实现只存文件路径的做法;总体框架如图4-1;
注意:图4-1中,Input fields来源于输入的字段,Output fields是要输出给到输出端接收的,input和output的字段列数一样的时候,下方的字段不需要写任何东西,留空即可,如果Java代码操作后,有新的变量要输出,就要在下方写好字段名和类型,传给输出端。
import java.io.*;
String lineTxt = null,alltxt="";
public boolean processRow(StepMetaInterface smi, StepDataInterface sdi) throws KettleException {
if (first) {
first = false;
/* TODO: Your code here. (Using info fields)
FieldHelper infoField = get(Fields.Info, "info_field_name");
RowSet infoStream = findInfoRowSet("info_stream_tag");
Object[] infoRow = null;
int infoRowCount = 0;
// Read all rows from info step before calling getRow() method, which returns first row from any
// input rowset. As rowMeta for info and input steps varies getRow() can lead to errors.
while((infoRow = getRowFrom(infoStream)) != null){
// do something with info data
infoRowCount++;
}
*/
}
Object[] r = getRow();
if (r == null) {
setOutputDone();
return false;
}
r = createOutputRow(r, data.outputRowMeta.size());
// It is always safest to call createOutputRow() to ensure that your output row's Object[] is large
// enough to handle any new fields you are creating in this step.
/* TODO: Your code here. (See Sample)
// Get the value from an input field
String foobar = get(Fields.In, "a_fieldname").getString(r);
foobar += "bar";
// Set a value in a new output field
get(Fields.Out, "output_fieldname").setValue(r, foobar);
*/
// Send the row on to the next step.
try
{
String myfilepath = get(Fields.In, "MY_SOURCE_DML_SCRIPT").getString(r); //MY_SOURCE_DML_SCRIPT存的是文件路径
File file = new File(myfilepath);
if(file.isFile() && file.exists())
{
InputStreamReader isr = new InputStreamReader(new FileInputStream(file), "utf-8");
BufferedReader br = new BufferedReader(isr);
while ((lineTxt = br.readLine()) != null)
{
alltxt+=lineTxt+'\n'; //读取文件内容到alltxt
}
//br.close();
}
else
{
System.out.println("文件不存在!");
}
}
catch (Exception e)
{
System.out.println("文件读取错误!");
}
get(Fields.Out, "MY_SOURCE_DML_SCRIPT").setValue(r, alltxt); //把alltxt赋值回到MY_SOURCE_DML_SCRIPT
//如果有多个变量,可以写多行
//get(Fields.Out, "MY_SOURCE_DML_SCRIPT2").setValue(r, alltxt2); //把alltxt赋值回到MY_SOURCE_DML_SCRIPT
putRow(data.outputRowMeta, r);
return true;
}
Linux下shell调用:
mydate="20200304"
myversion ="1.0"
/tools/pdi-ce-8.2.0.0-342/data-integration/kitchen.sh -file:job.kjb -param:MYDATE=${mydate} -param:VERSION=${myversion} -level=basic
其中/tools/pdi-ce-8.2.0.0-342/data-integration/kitchen.sh
是你kettle文件的安装目录;-file:job.kjb
表示你要调用的文件Job,-param:MYDATE=${mydate} -param:VERSION=${myversion}
是你传给你的kettle Job的命名参数
,-level=basic
是日志级别;
-level 日志级别(运行界面,log显示框左上角三个小图标,最后一个扳手锤子为设置level)
Rowlevel: 所有在Kettle中的有效日志,包括在大量复杂步骤的信息;
Debugging: 产生大量的日志信息,主要用于调试,但是不是在行级别(row level);
Detailed:允许用户看到比基本日志级别更富比较性的信息,额外的信息实例包括SQL查询语句和一般的DDL都会产生。
Basic:默认的日志级别;仅仅打印这些能够反映在步骤或者任务条目上的信息。
Minimal:通知你仅仅关于一个任务或者转化的信息。
Errorlogging only: 如果那儿有一个错误,显示错误消息;否则,什么都不显示。
Nothingat all: 即使当有错误存在的时候,不要产生任何日志。
Windows 下的带参调用:
D:\Dev\Kettle\data-integration\Kitchen.bat -file:job.kjb "-param:MYDATE=20200304" "-param:VERSION=1.0" -level=basic
linux下参数可以没有双引号,windows要求参数必须有双引号。
如图6-1,发带附件的邮件之前,先把需要的数据生成文件,我这里里是生成excel文件,在转换get_13_inch_devices
先把库里面的数据取出来生成excel文件the_13_inch_devices_${MYVERSION}.xlsx
,${MYVERSION}
是转换get_version
设置的一个变量,其实就是当前日期,如20200423,然后需要先加个控件添加到结果文件列表
,该控件的配置信息如图6-2.
发送邮件
配置地址栏
,如图6-3,填写收件人
,抄送
,暗送
的邮件地址,如果有多个,每个人之间用空格隔开
,然后填写发件人
地址,一般如果你要自动化发送,都可以申请一个部门邮箱,以部门的名义自动化发,注意这个发件人
用户要和图6-4 kettle发送邮件配置服务器栏的用户验证
的用户保持一致,不然会报错,其实也很好理解,你不能冒名顶替以别人的身份来发邮件,不然岂不是细思极恐?除非你的邮箱服务器设置的极其差劲,可以允许你匿名发送邮件,即不需要图6-4的用户认证。
发送邮件
配置附件
栏,如图6-6,勾选下带附件
。
最终效果如图6-7。
以上是几个常用又比较繁琐的kettle操作小结,后续如果还有常用的,我会尽量更新进来,下一篇总结下关于Jinkens服务+GitLab实现自动化版本上线发布的操作,以调用kettle作业或转换CICD为例,文章链接:拥抱开源——Jinkens+Gitlab构建自动版本上线发布