拥抱开源——kettle几个常用的小场景案例

  以前用的商用talend开发,发布,TAC调度水到渠成,山回路转,来到了使用了开源的kettle新环境,瞬间有种从空调房搬到了电风扇集体宿舍的感觉,kettle这个不行,这个矬,那个慢,我就是自己写Java也不愿用kettle,能不能买个talend,一星期后,真香!废话结束,进入正题

Round 1: kettle导入数据慢

  90万数据导入mysql,竟然耗时2个半小时还没跑完,我还对着mysql发了顿牢骚(mysql怎么这么矬,虽然发现mysql背锅了,其实发现mysql大有来头,后面要研究下,难怪有专门的mysql专家,后续再说吧),其实是jdbc的问题,在输入和输出的数据库连接选项中配置以下四个参数,每个参数的解析可以参考(1)魂灵舞的kettle大数据量读写mysql性能优化,写的非常好,可以从原来的几个小时,瞬间变成2分钟完成,如图1-1。
图1-1 输入,输出的参数配置

图1-1 输入,输出的参数配置
rewriteBatchedStatements true
useServerPrepStmts false    (写的时候)
useServerPrepStmts true     (读的时候)
characterEncoding utf8
useCompression true

Round 2: 设置动态变量和获取动态变量

  静态变量没啥好说的,直接写在命名参数上即可,设置动态变量一般是在一个作业的第一个转换内,因为设置的变量只能在之后的转换或者步骤中使用,即在转换A中设置了变量a,那a只能在A之后的步骤中起作用,在A本身是不起作用的哟(特别注意),选一个输出控件,这里以表输出为例,如图2-1,输入可以是从数据取出来的数据赋值给变量,注意结果只能为一行多列,多列可以设置多个变量,结果多行了会报错,原因很简单,自己想想就知道了,多行的可以用
"复制记录到结果"来装(循环再讲);
在这里插入图片描述

图2-1 设置动态变量
将输出的值赋给变量,如图2-2,点击获取字段,会自动生成将输出结果转化为变量名的list,针对不要的字段可以自行调整;

在这里插入图片描述

图2-2 设置变量
  使用变量,一般有三种场景
  • 在该Job的控件里面使用,如图2-3,直接用${变量名}的形式引用;
  • 在下一个转换上使用,先打开转换,在转换里面的“转换设置”的“命名参数”,先把命名参数写出来,如图2-4,然后再作业层调用转换的时候,点到“命名参数”把变量值赋给命名参数,点击获取参数,把刚刚定义在转换里面的参数拿过来,然后再赋值变量,如图2-5,可以理解为命名参数为形参,变量值为实参
  • 在转换内部直接获取变量,如图2-6,利用获取变量控件,直接把变量值作为形参赋值给新定义的变量;
    变量在表输入的话,如果是字符串,需要用’’,并且要勾选替换SQL语句里面的变量

在这里插入图片描述

图2-3 变量在Job控件层使用

在这里插入图片描述

图2-4 转换里面设置命名参数

在这里插入图片描述

图2-5 作业里面把变量赋值给命名参数

在这里插入图片描述

图2-6 转换内部直接用获取变量接收

在这里插入图片描述

Round 3: 实现循环以及JavaScript操控变量

循环有两种,一种是实现for(int i=0;i<10;i++),一种是实现foreach(int x:array)的,一种是转换内部的循环

  • 实现类似for(int i=0;i<10;i++)
       先是设置变量i=1,size=10,当然也可以惨开Round 2里面的动态设置变量,然后选用“检验字段的值”控件,如图3-1,检验选用变量,变量名写i,然后是成功条件和值写${size}。注意,变量涉及到’值’都是${变量名},如果让填写变量’名’,就可以直接写名字!
    在这里插入图片描述
图3-1 判别循环条件
    如果检测判别成功,然后设置需要循环的操作,操作完设置i++,如图3-2,可以巧妙运用计算器控件,当然,也可以写JS脚本;再检测,如果为真,继续循环,否则,结束,总体JOB流程如图3-3;

在这里插入图片描述

图3-2 设置i++

在这里插入图片描述

图3-3 实现for()循环
  • 实现类似foreach(int x:array)遍历的循环
       首先,生成这个数组,以表输入为例,输出结果为“复制记录到结果字符串”,如图3-4;然后用judge_and_set_variable(作业JavaScript控件,代码如下)来初始化变量;然后判别循环变量i,具体可参看图3-1;
//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不可少,少了节点之间的连接跑不下去

在这里插入图片描述

图3-4 复制记录到结果字符串生成数组array
总体实现如图3-5

在这里插入图片描述

图3-5 kettle实现循环遍历
- 转换内部的循环 &emps;&emps;如图3-6,输入是表输入的结果,假设有30条记录,是一个合成的drop table if exists mytest_table;的30条记录,输出选用执行sql脚本控件,点击获取字段把上一步的结果字段获取过来,执行sql语句注意在写脚本的地方引用参数用?(英文版),第一个出现的?代表左下角序列号为1的参数,第二个?代表序列化为2的参数,以此类推,勾选执行每一行以及变量替换;这里的变量写法要和${变量名}区分开来哟。

在这里插入图片描述

Round 4: 利用Java代码随心所欲的操控转换

  Java代码是夹杂在输入,输出之间的,如把sql脚本文件的内容读取出来,赋值给一个变量,再把变量给到表输入,这样可以实现只存文件路径的做法;总体框架如图4-1;

在这里插入图片描述

图4-1 java代码随心所欲操控转换
  具体实现,如图4-1,打开Java代码,双击Common use下的Main,在Proceesor上会生成代码,跟传统的Java main有点不同,其他的也不需要改,只需要在putRow(data.outputRowMeta, r);加入自己的代码即可。

注意:图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;
}


在这里插入图片描述

图4-1 Java代码实现

Round 5: linux或者windows脚本带参调用kettle

   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要求参数必须有双引号

Round 6: kettle发送附件邮件

  如图6-1,发带附件的邮件之前,先把需要的数据生成文件,我这里里是生成excel文件,在转换get_13_inch_devices先把库里面的数据取出来生成excel文件the_13_inch_devices_${MYVERSION}.xlsx,${MYVERSION}是转换get_version设置的一个变量,其实就是当前日期,如20200423,然后需要先加个控件添加到结果文件列表,该控件的配置信息如图6-2.
在这里插入图片描述

图6-1 发送附件邮件设置
   给`添加到结果文件列表`起个名字`add mail attachfile`,把你生成的附件文件路径加进来,然后开始配置图6-1上的 `发送邮件`控件,如图6-3到图6-6。

在这里插入图片描述

图6-2 添加到结果文件列表配置

  发送邮件配置地址栏,如图6-3,填写收件人抄送暗送的邮件地址,如果有多个,每个人之间用空格隔开,然后填写发件人地址,一般如果你要自动化发送,都可以申请一个部门邮箱,以部门的名义自动化发,注意这个发件人用户要和图6-4 kettle发送邮件配置服务器栏的用户验证的用户保持一致,不然会报错,其实也很好理解,你不能冒名顶替以别人的身份来发邮件,不然岂不是细思极恐?除非你的邮箱服务器设置的极其差劲,可以允许你匿名发送邮件,即不需要图6-4的用户认证。

在这里插入图片描述

图6-3 kettle发送邮件配置地址栏
   `发送邮件`配置`服务器`栏,如图6-4,上面填写好你的邮箱服务器,公司一般都有,不知道的向运维部问,没有的话让他们搭建,下面填写你的图6-3`发件人`人调用邮箱服务器的用户认证,就是你选用的`发件人`这个邮箱的邮箱地址和密码。

在这里插入图片描述

图6-4 kettle发送邮件配置服务器栏
   `发送邮件`配置`邮件消息`栏,如图6-5,可以勾选下`使用HTML邮件格式`这样你就可以在注释(对应你发出来的邮件的正文)使用HTML的标签,如图6-5用到的<br>,其他配置参考图6-5即可。

在这里插入图片描述

图6-5 kettle发送邮件配置邮件消息栏

  发送邮件配置附件栏,如图6-6,勾选下带附件

在这里插入图片描述

图6-6 kettle发送邮件配置附件栏

  最终效果如图6-7。

在这里插入图片描述

图6-7 kettle发送邮件最终效果

  以上是几个常用又比较繁琐的kettle操作小结,后续如果还有常用的,我会尽量更新进来,下一篇总结下关于Jinkens服务+GitLab实现自动化版本上线发布的操作,以调用kettle作业或转换CICD为例,文章链接:拥抱开源——Jinkens+Gitlab构建自动版本上线发布

Round 7: kettle数据库连接信息配置文件及密码加密

  kettle安装好以后,在\data-integration\.kettle\kettle.properties文件内,是可以配置kettle专用的变量参数的,那么kettle用到的连接信息就可以参数化的写到这里,如图7-1;

在这里插入图片描述

图7-1 在kettle.properties新增数据库连接信息参数

  新建一个Job(作业),利用Job(作业)里面的设置变量控件,如图7-2,将属性文件名的路径填上(注意:这里说明啥?也就是说要只要是个.properties且按照上面图7-1的配置都行,这个文件可以是你新建的在任意地方的,只要我高兴,我也可以一个项目建一个,放在同一个目录下${Internal.Entry.Current.Directory}/kettle.properties),一般是全公司的人共用一套就行,服务器迁移的时候同意改起来方便;
  变量有效范围最好选在根作业有效,避免变量作用域过小导致传输错误,下面的变量名和值不用填写,剩下的就是结合Round 2: 设置动态变量和获取动态变量来使用这些变量了,对应的这些变量分别是${DW_IP},${DW_DB},${DW_PORT},${DW_USER},DW_PASSWORD;

在这里插入图片描述

图7-2 引用配置文件的变量

  方便是方便了,但是也面临了新的问题,那就是密码如此大胆的明文存储,真的Ok吗?怕是安全部门知道了分分钟请你喝下午茶吧?kettle也考虑到这一点,于是她可以采用加密的形式来存储密码,首先进入目录data-integration
在这里插入图片描述

图7-3 kettle自带的加密工具

  利用加密工具输入如下图7-4指令加密;得到相应的加密密码,然后将这串加密的密码Encrypted 2be98afc86aa7f2e4cb48fc23fe83b8df(一个都不能少哟,包括前面的关键字Encrypted也要)代替到图7-1的DW_PASSWORD即可;
在这里插入图片描述

图7-4 利用加密工具加密

  这样只要利用kettle的Job,其实只要DBA统一管理生产的密码就行了,这就是绝对的安全吗,其实远没有绝对的安全吧,有人接触到了这个kettle,一样可以利用kettle包来改写语句删不跑路不是?针对这一点,个人觉得首先是可以利用权限把控来控制相应全选的把控;第二点,kettle本身的包文件里面的内容其实是xml文件,可以利用对文件内容md5处理来检测文件是否有更改,有,则需要审核才能上线;也可以git来版本管控,每次merge都需要git的maintain或者写一段脚本负责核对修改的部分,是否有危险语句的关键字DROP等 ,并检测其合理性(博主的做法),当然人要是真的想搞破坏,肯定是永远存在漏洞,每个人本身还是要有职业素养的;

  • 2
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

╭⌒若隐_RowYet——大数据

谢谢小哥哥,小姐姐的巨款

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值