Kettle实现 HDFS文件解析同步到SQLServer数据库(ETL 包括:时间格式化、IP校验、字段拼接)

目录

一、需求及总体设计

1、设计完成后的作业

2、设计完成后的转换

3、处理流程概述

二、具体处理流程

1、作业

1、JS下载文件到本地 

2、转换(具体处理见下面)

3、JS重命名

4、Shell组件

5、删除多个文件

2、转换

2.1 文本文件输入

2.2字段选择

2.3 表输入

2.4 JS脚本校验IP

 2.5过滤记录

2.6 阻塞数据直到步骤完成

2.7 调用存储过程 


一、需求及总体设计

需求: 将 HDFS 中的文件解析完成后 将数据写入 SQLServer 数据库,然后将 处理后的 HDFS 文件标记后缀为完成解析,最后将标记后的文件移动到 bak 目录。

1、设计完成后的作业流程

Kettle实现 HDFS文件解析同步到SQLServer数据库(ETL 包括:时间格式化、IP校验、字段拼接)_第1张图片

2、设计完成后的转换

Kettle实现 HDFS文件解析同步到SQLServer数据库(ETL 包括:时间格式化、IP校验、字段拼接)_第2张图片

 Kettle实现 HDFS文件解析同步到SQLServer数据库(ETL 包括:时间格式化、IP校验、字段拼接)_第3张图片

此处说明一下:数据发送的 方式要选择 复制发送模式  因为 不合法的 IP 会过滤一部分 数据,发送给其他的 业务处理逻辑 就会减少一部分的数据,所以采用 复制发送模式,让每个处理拿到 HDFS解析后的全部数据。

3、处理流程概述

  1. 下载HDSF中的文件到本地
  2.  对文件进行解析,具体包括字段的日期格式化、IP校验、字段拼接    
  3. 调用存储过程将信息写入到SqlServer数据库
  4.  HDFS解析后的文件移动到bak 目录
  5. 删除下载到本地的文件

二、具体处理流程

1、作业

1、JS下载文件到本地 

  JS 下载文件到本地,使用了Java自定义封装好的代码实现,将 编写好的代码编译打包放在kettle的 lib包中即可。刚开始我们可以将HDFS中的文件下载到 windows 环境进行测试,待本地测试没有问题后,再将 targetFolder 换成 linux 中的文件路径,进行linux中的测试。(代码附录到最后

Kettle实现 HDFS文件解析同步到SQLServer数据库(ETL 包括:时间格式化、IP校验、字段拼接)_第4张图片

2、转换(具体处理见下面)

3、JS重命名

此处从下载到本地的HDFS文件匹配, 就是从找到 本次解析后的文件然后 将 HDFS中的文件找到 加上 ".completed" 后缀,表示此文件已经处理过,之后下次正则匹配就不会将这些文件匹配下载到本地了。此处的 文件的重命名 和前面的 HDFS文件下载到本地都封装到了 HDFSProcess类中,之后会附上代码,大家可以参考。(代码附录到最后

Kettle实现 HDFS文件解析同步到SQLServer数据库(ETL 包括:时间格式化、IP校验、字段拼接)_第5张图片

4、Shell组件

说明:想用Kettle  Shell 组件 做到  HDFS中 文件的移动 ,脚本的命令也很简单 ,mv将 加了 .completed 解析完成后的文件移动到 bak 目录,这块想用 kettle的 Shell 组件实现,另外一个思路是直接在 linux 做一个 定时的文件移动也是可以的。此处 Kettle 的Shell 组件我也是不知道怎么用,我是在linux机器上运行的,刚开始我用的 ".sh" 文件发现不行。我就改为 .bat 文件,想着应该是 kettle不识别 .sh 文件。之后又试了6次路径的配置成功了,之后不知道会不会出现问题,

(1)【Shell】

 shell组件是用来执行Windows下的批处理脚本,即bat文件(参考博客https://www.csdn.net/gather_25/MtTaUg4sMzMxOTQtYmxvZwO0O0OO0O0O.html)。但是测试 运行后的日志我发现 kettle 知道正在运行的 platform 是 linux,也是说 .bat 在 linux 平台也是可以运行的,但是linux 平台下 脚本还是 以.sh 结尾。

Kettle实现 HDFS文件解析同步到SQLServer数据库(ETL 包括:时间格式化、IP校验、字段拼接)_第6张图片

(2)第二步:编辑组件

  •  勾选【插入脚本】表示在该组件中写脚本,右侧的【脚本】选项卡即是写脚本的位置,若不勾选【插入脚本】,则是通过执行指定脚本文件的方式。不建议勾选【插入脚本】选项,因为勾选【插入脚本】后,Kettle运行Shell的时候会先将脚本内容写到一个临时批处理文件中再去执行,很容易出现中文乱码情况。
  • 【脚本文件名】配置项,用于指定批处理脚本文件位置,勾选【插入脚本】后,该项不可配置。
  • 【工作路径】配置项,用于指定脚本工作路径,通俗的说,就是在哪个目录下运行这个脚本。
  •   最下面的【字段】列表,用于配置脚本入参,可以使用参数变量。

 

我在 linux 运行环境的配置。就配置一个当前脚本的运行路径,就生效了。

Kettle实现 HDFS文件解析同步到SQLServer数据库(ETL 包括:时间格式化、IP校验、字段拼接)_第7张图片

 

此处我的另外一个实现思路是在作业启动的脚本中开启 一个 linux的定时器 ,定时的 将 解析后的 HDFS 的 .completed 文件移动到 bak 目录,也在进一步改进中。

再看一下我整个的ETL的文件有哪些:(chmod +x .bat   之后我改为 .sh 结尾)

  • 文件移动脚本权限的修改
  • window下编写的脚本,可能存在编码问题,dos2unix xxx.sh  修改编码

Kettle实现 HDFS文件解析同步到SQLServer数据库(ETL 包括:时间格式化、IP校验、字段拼接)_第8张图片

Kettle实现 HDFS文件解析同步到SQLServer数据库(ETL 包括:时间格式化、IP校验、字段拼接)_第9张图片

5、删除多个文件

将下载到本地的 多个  HDFS文件 删除掉。当然了也可以增加一个 本地文件的备份,看需要。

Kettle实现 HDFS文件解析同步到SQLServer数据库(ETL 包括:时间格式化、IP校验、字段拼接)_第10张图片

2、转换

2.1 文本文件输入

文本文件这里主要配置三块文件、内容、字段。

1、文件 

Kettle实现 HDFS文件解析同步到SQLServer数据库(ETL 包括:时间格式化、IP校验、字段拼接)_第11张图片

2、 内容

Kettle实现 HDFS文件解析同步到SQLServer数据库(ETL 包括:时间格式化、IP校验、字段拼接)_第12张图片

3、字段

Kettle实现 HDFS文件解析同步到SQLServer数据库(ETL 包括:时间格式化、IP校验、字段拼接)_第13张图片

2.2字段选择

这块由于后面使用“?” 传递流中的字段,使用选择字段调整了 字段的 顺序。

Kettle实现 HDFS文件解析同步到SQLServer数据库(ETL 包括:时间格式化、IP校验、字段拼接)_第14张图片

2.3 表输入

因为业务需要查询 hostname 然后根据查询到的 主机做更新 IP、更新机器配置,所以此处用到了表输入,并且使用了“?” 接收上一步传过来的字段值,目前不知道“?”是否会带来 ETL的风险,进一步的验证测试中。 “?” 号代表上一步传入的条件,第一个“?” 代表上一步第一个字段名称,第二个“?” 代表上一步第二个字段名称,依次类推。

Kettle实现 HDFS文件解析同步到SQLServer数据库(ETL 包括:时间格式化、IP校验、字段拼接)_第15张图片

Kettle实现 HDFS文件解析同步到SQLServer数据库(ETL 包括:时间格式化、IP校验、字段拼接)_第16张图片

2.4 JS脚本校验IP

 此步骤使用 JS 校验IP, 如果 IP 合法 我们才调用存储过程 更新IP。

var reg = /^((\d)|([1-9]\d)|(1\d{2})|((2[0-4]\d)|(25[0-5])))(\.((\d)|([1-9]\d)|(1\d{2})|((2[0-4]\d)|(25[0-5])))){3}$/;
if (reg.test(IP) == true){
	var flag = 1
}else {
	var flag = 2
}

Kettle实现 HDFS文件解析同步到SQLServer数据库(ETL 包括:时间格式化、IP校验、字段拼接)_第17张图片

2.5过滤记录

此组件在处理 正则,判断时很重要,尤其在处理一个字段的不同业务分离,刚开始不会使用 此组件,百度学习后解决了SQLServer 自身SQL不支持正则判断的问题。

Kettle实现 HDFS文件解析同步到SQLServer数据库(ETL 包括:时间格式化、IP校验、字段拼接)_第18张图片

2.6 阻塞数据直到步骤完成

由于需要从表里查出hostname 对应的 key ,然后 解析数据流 里面的字段,不希望流里的数据 没查完表就流向后边,所以使用了阻塞数据直到步骤完成。

 Kettle实现 HDFS文件解析同步到SQLServer数据库(ETL 包括:时间格式化、IP校验、字段拼接)_第19张图片

2.7 调用存储过程 

调用过程中的返回值名称不要填写,我调用的存储过程是带有输入参数,不带输出参数

Kettle实现 HDFS文件解析同步到SQLServer数据库(ETL 包括:时间格式化、IP校验、字段拼接)_第20张图片

 运行的结果

Kettle实现 HDFS文件解析同步到SQLServer数据库(ETL 包括:时间格式化、IP校验、字段拼接)_第21张图片

两个文件夹的移动到 bak 目录也是成功的。 

Kettle实现 HDFS文件解析同步到SQLServer数据库(ETL 包括:时间格式化、IP校验、字段拼接)_第22张图片

三、代码附录 

说明:代码是利用发编译工具 解析的 class 文件,然后自己手巧上去的,正确与否本人没有测试,具体的实现确实是如此,最后要在 resource 中添加 HDFS的 配置文件 core-site.xml 、HDFS-site.xml等配置文件。

1、HDFSProcess 类

package com.kangna.hdfs;

import com.google.common.collect.Sets;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;

/**
 * @AUTHOR kangll
 * @DATE 2020/6/20 10:16
 * @DESC: HDFS 工具类
 */
public class HDFSProcess {

    private static final Logger logger = LoggerFactory.getLogger(HDFSProcess.class);

    private String regex;
    private String srcHDFSFolder;
    private String destinationFolder;
    private FileSystem fileSystem;

    public HDFSProcess() {
    }

    public HDFSProcess(String regex, String srcHDFSFolder, String destinationFolder) {
        this.regex = regex.toLowerCase();
        this.srcHDFSFolder = srcHDFSFolder;
        this.destinationFolder = destinationFolder;
    }

    /**
     * 下载文件到本地
     */
    public void downloadFile() throws Exception {
        // 提供配置参数对象
        Configuration conf = new Configuration();
        // 获取文件的状态
        List paths = HDFSUtil.listStatus(conf, this.srcHDFSFolder, this.regex);
        logger.info("HDFS matched " + paths.size() + " files");
        for (Path path : paths) {
            String fileName = path.getName();
            fileSystem = FileSystem.get(conf);
            //  比如:  /hadoop/kangna/ods
            Path srcPath = new Path(this.srcHDFSFolder + File.separator + fileName);
            // 比如:G:\\API\\
            String localPath = this.destinationFolder + File.separator + fileName;

            FSDataInputStream dataInputStream = fileSystem.open(srcPath);

            BufferedReader reader = new BufferedReader(new InputStreamReader(dataInputStream));
            BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(localPath)));
            String line = null;
            while ((line = reader.readLine()) != null) {
                writer.write(line + "\n");
            }
            reader.close();
            writer.close();
        }
        logger.info("download file successful.");
    }

    /**
     * 文件重命名
     *
     * @param localPath 本地文件路径
     * @param HDFSPath  HDFS文件路径
     * @param suffix    后缀
     */
    public void rename(String localPath, String HDFSPath, String suffix) throws  Exception{
        Configuration conf = new Configuration();
        // 获取本地文件名的集合
        Set fileNameList = getFileLocalName(localPath);
        logger.info("match local file size : " + fileNameList.size());
        System.out.println("matche local file size : " + fileNameList.size());
        for (String fileName : fileNameList) {
            String src = HDFSPath + File.separator + fileName;
            // 比如: 将 kangna.txt   重命名 为  kangna.txt.completed
            String target = HDFSPath + File.separator + fileName + suffix;
            // 重命名 实现 调用 renameTo
            boolean flag = HDFSUtil.renameTo(conf, src, target);
            if (flag) {
                System.out.println("renamed file " + src + " to " + target + "successful.");
            } else {
                System.out.println("renamed file " + src + " to " + target + "failed.");
            }
            System.out.println(fileName);
        }
    }

    public Set getFileLocalName(String localPath) {
        System.out.println("listing path is : " + localPath);
        // 将正则编译为此类的实例
        Pattern pattern = Pattern.compile(this.regex);

        Set list = Sets.newHashSet();
        File baseFile = new File(localPath);
        if ((baseFile.isFile()) || (!baseFile.exists())) {
            return list;
        }
        // 返回文件的抽象路径名数组
        File[] files = baseFile.listFiles();
        System.out.println("this path has files : " + files.length);
        for (File file : files) {
            if (file.isDirectory()) {
                list.addAll(getFileLocalName(file.getAbsolutePath()));
            } else {
                list.add(file.getName());
            }
        }
        return list;
    }

    public static void main(String[] args) throws Exception {
        System.out.println(new HDFSProcess());
    }
}

2、HDFSUtil  类

package com.kangna.hdfs;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.LocatedFileStatus;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.fs.RemoteIterator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;

/**
 * @AUTHOR kangll
 * @DATE 2020/6/20 10:31
 * @DESC:
 */
public class HDFSUtil {

    private static final Logger logger = LoggerFactory.getLogger(HDFSUtil.class);

    // 文件执行中的标记
    private static final String FILE_EXECUTING_MARK = ".executing";
    // 文件执行后的标记
    private static final String FILE_COMPLETED_MARK = ".completed";

    private static FileSystem fileSystem;

    /**
     * 获取文件执行的状态
     *
     * @param conf
     * @param dirPath
     * @return
     */
    public static List listStatus(Configuration conf, String dirPath) throws Exception {
        return listStatus(conf, dirPath, "");
    }

    /**
     * 获取文件执行的状态
     *
     * @param conf    配置参数
     * @param dirPath 文件路径
     * @param regex   要匹配的正则
     * @return
     */
    public static List listStatus(Configuration conf, String dirPath, String regex) throws IOException {
        List files = new ArrayList<>();

        try {
            // 返回配置过的文件系统对象
            fileSystem = FileSystem.get(conf);
            // 列出指定路径中文件的状态和块位置
            RemoteIterator fileStatus = fileSystem.listFiles(new Path(dirPath), true);

            Pattern pattern = Pattern.compile(regex);
            while (fileStatus.hasNext()) {
                LocatedFileStatus file = fileStatus.next();
                Path path = file.getPath();
                String fileName = path.getName().toLowerCase();
                if (regex.equals("")) {
                    files.add(path);
                    logger.info("match file : " + fileName);
                } else if (pattern.matcher(fileName).matches()) {
                    files.add(path);
                    logger.info("match file : " + fileName);
                }
            }
        } finally {
            if (fileSystem != null) {
                fileSystem.close();
            }
        }
        return files;
    }

    /**
     * 创建文件夹
     *
     * @param conf
     * @param dir
     * @return
     */
    public static boolean mkdir(Configuration conf, String dir) throws IOException {
        boolean flag = false;
        try {
            fileSystem.get(conf);
            if (fileSystem.exists(new Path(dir))) {
                flag = true;
            } else {
                fileSystem.mkdirs(new Path(dir));
            }
        } finally {
            if (fileSystem != null) {
                fileSystem.close();
            }
        }
        return flag;
    }

    /**
     * @param conf
     * @param src    要重命名的 src 路径
     * @param target 重新命名的 target 路径
     * @return
     * @throws Exception
     */
    public static boolean renameTo(Configuration conf, String src, String target) throws Exception {
        boolean flag = false;
        try {
            fileSystem = FileSystem.get(conf);
            if (fileSystem.exists(new Path(target))) {
                fileSystem.delete(new Path(target), true);
                logger.info("target file : " + target + " exist, deleted");
            }
            // 将路径名 src 重命名为 路径target
            flag = fileSystem.rename(new Path(src), new Path(target));
            if (flag) {
                logger.info("renamed file " + src + " to " + target + " success!");
            } else {
                logger.info("renamed file " + src + " to " + target + " failed!");
            }
        } finally {
            if (fileSystem != null) {
                fileSystem.close();
            }
        }
        return flag;
    }
}

 

 

你可能感兴趣的:(ETL,工具,#,Kettle)