SeaTunnel简易封装方案:让数据集成更简单

文章目录

SeaTunnel简易封装方案:让数据集成更简单

在了解了Apache SeaTunnel的基本原理和使用方法后,我们可以进一步封装它的能力,为用户提供更简单的配置体验。本文将详细介绍如何基于SeaTunnel构建一个易用的数据集成工具,让用户只需关注最核心的配置项。

设计思路

SeaTunnel虽然强大,但对于初学者来说,编写完整的配置文件仍有一定门槛。我们的封装方案基于以下思路:

  1. 模板化配置:预定义常见数据源的模板
  2. 参数简化:只暴露必要参数,其他使用合理默认值
  3. 双层接口:提供命令行和Web两种交互方式
  4. 可视化监控:集成任务执行状态和日志查看
  5. 智能推荐:根据数据特征推荐合适的配置

项目结构

复制代码
/Users/francis/TraeProject/seatunnel/
├── bin/                      # 原SeaTunnel脚本
├── config/                   # 原SeaTunnel配置
├── easy-seatunnel/           # 我们的封装项目
│   ├── bin/                  # 封装后的启动脚本
│   ├── templates/            # 配置模板
│   │   ├── sources/          # 数据源模板
│   │   ├── transforms/       # 转换模板
│   │   └── sinks/            # 目标模板
│   ├── web/                  # Web界面
│   ├── lib/                  # 核心库
│   └── conf/                 # 封装工具配置
└── README.md                 # 项目说明

核心实现

配置模板

首先,我们需要为常见的数据源、转换和目标创建模板文件。这些模板只包含必要的参数,其他参数使用合理的默认值。

让我们创建几个基础模板:

hocon:/Users/francis/TraeProject/seatunnel/easy-seatunnel/templates/sources/mysql.template 复制代码
source {
  Jdbc {
    url = "{{jdbc_url}}"
    driver = "com.mysql.cj.jdbc.Driver"
    user = "{{username}}"
    password = "{{password}}"
    query = "{{query}}"
    result_table_name = "{{table_name}}"
    
    # 高级参数(默认值)
    connection_check_timeout_sec = 30
    fetch_size = 1000
    partition_column = ""
    partition_num = 1
  }
}
hocon:/Users/francis/TraeProject/seatunnel/easy-seatunnel/templates/sinks/elasticsearch.template 复制代码
sink {
  Elasticsearch {
    hosts = ["{{es_host}}"]
    index = "{{index_name}}"
    source_table_name = "{{source_table}}"
    
    # 高级参数(默认值)
    username = ""
    password = ""
    max_retry_count = 3
    batch_size = 1000
  }
}

命令行工具

接下来,我们创建一个命令行工具,让用户通过简单的命令生成完整的配置文件并执行:

python:/Users/francis/TraeProject/seatunnel/easy-seatunnel/bin/easy-seatunnel.py 复制代码
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import os
import sys
import argparse
import json
import subprocess
from string import Template

# 项目根目录
ROOT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
SEATUNNEL_HOME = os.path.abspath(os.path.join(ROOT_DIR, ".."))
TEMPLATE_DIR = os.path.join(ROOT_DIR, "templates")
OUTPUT_DIR = os.path.join(ROOT_DIR, "output")

def load_template(template_type, template_name):
    """加载指定类型和名称的模板"""
    template_path = os.path.join(TEMPLATE_DIR, template_type, f"{template_name}.template")
    if not os.path.exists(template_path):
        print(f"错误: 模板 {template_path} 不存在")
        sys.exit(1)
    
    with open(template_path, 'r', encoding='utf-8') as f:
        return f.read()

def generate_config(args):
    """根据参数生成配置文件"""
    # 加载环境配置
    env_config = """
env {
  execution.parallelism = %d
  job.mode = "%s"
}
""" % (args.parallelism, args.mode)
    
    # 加载source模板
    source_template = load_template("sources", args.source)
    source_params = json.loads(args.source_params)
    source_config = Template(source_template).substitute(**source_params)
    
    # 加载transform模板(如果指定)
    transform_config = ""
    if args.transform:
        transform_template = load_template("transforms", args.transform)
        transform_params = json.loads(args.transform_params) if args.transform_params else {}
        transform_config = Template(transform_template).substitute(**transform_params)
    else:
        transform_config = """
transform {
  # 无转换操作
}
"""
    
    # 加载sink模板
    sink_template = load_template("sinks", args.sink)
    sink_params = json.loads(args.sink_params)
    sink_config = Template(sink_template).substitute(**sink_params)
    
    # 组合完整配置
    full_config = env_config + source_config + transform_config + sink_config
    
    # 保存配置文件
    os.makedirs(OUTPUT_DIR, exist_ok=True)
    config_path = os.path.join(OUTPUT_DIR, f"{args.job_name}.conf")
    with open(config_path, 'w', encoding='utf-8') as f:
        f.write(full_config)
    
    print(f"配置文件已生成: {config_path}")
    return config_path

def run_job(config_path, engine):
    """运行SeaTunnel作业"""
    cmd = [
        os.path.join(SEATUNNEL_HOME, "bin", "seatunnel.sh"),
        "--config", config_path,
        "-e", engine
    ]
    
    print(f"执行命令: {' '.join(cmd)}")
    subprocess.run(cmd)

def main():
    parser = argparse.ArgumentParser(description="SeaTunnel简易封装工具")
    parser.add_argument("--job-name", required=True, help="作业名称")
    parser.add_argument("--source", required=True, help="数据源类型")
    parser.add_argument("--source-params", required=True, help="数据源参数(JSON格式)")
    parser.add_argument("--transform", help="转换类型")
    parser.add_argument("--transform-params", help="转换参数(JSON格式)")
    parser.add_argument("--sink", required=True, help="目标类型")
    parser.add_argument("--sink-params", required=True, help="目标参数(JSON格式)")
    parser.add_argument("--mode", default="BATCH", choices=["BATCH", "STREAMING"], help="作业模式")
    parser.add_argument("--parallelism", type=int, default=1, help="并行度")
    parser.add_argument("--engine", default="flink", choices=["flink", "spark"], help="执行引擎")
    parser.add_argument("--run", action="store_true", help="生成配置后立即运行")
    
    args = parser.parse_args()
    
    # 生成配置文件
    config_path = generate_config(args)
    
    # 如果指定了--run,则运行作业
    if args.run:
        run_job(config_path, args.engine)

if __name__ == "__main__":
    main()

创建启动脚本:

bash:/Users/francis/TraeProject/seatunnel/easy-seatunnel/bin/easy-seatunnel.sh 复制代码
#!/bin/bash

# 获取脚本所在目录
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
PYTHON_SCRIPT="${SCRIPT_DIR}/easy-seatunnel.py"

# 检查Python3是否安装
if ! command -v python3 &> /dev/null; then
    echo "错误: 未找到Python3,请先安装Python3"
    exit 1
fi

# 执行Python脚本,传递所有参数
python3 "${PYTHON_SCRIPT}" "$@"

别忘了添加执行权限:

bash 复制代码
chmod +x /Users/francis/TraeProject/seatunnel/easy-seatunnel/bin/easy-seatunnel.sh
chmod +x /Users/francis/TraeProject/seatunnel/easy-seatunnel/bin/easy-seatunnel.py

Web界面

为了进一步简化用户体验,我们可以创建一个简单的Web界面。这里使用Flask框架实现:

python:/Users/francis/TraeProject/seatunnel/easy-seatunnel/web/app.py 复制代码
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import os
import sys
import json
import subprocess
from flask import Flask, render_template, request, jsonify

# 添加项目根目录到Python路径
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
from bin.easy_seatunnel import load_template, generate_config, run_job

app = Flask(__name__)

@app.route('/')
def index():
    """首页,显示作业创建表单"""
    # 获取可用的模板列表
    templates_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), "templates")
    
    sources = [f.split('.')[0] for f in os.listdir(os.path.join(templates_dir, "sources")) 
               if f.endswith('.template')]
    
    transforms = [f.split('.')[0] for f in os.listdir(os.path.join(templates_dir, "transforms")) 
                 if f.endswith('.template')]
    
    sinks = [f.split('.')[0] for f in os.listdir(os.path.join(templates_dir, "sinks")) 
            if f.endswith('.template')]
    
    return render_template('index.html', sources=sources, transforms=transforms, sinks=sinks)

@app.route('/get_template_params', methods=['GET'])
def get_template_params():
    """获取指定模板的参数列表"""
    template_type = request.args.get('type')
    template_name = request.args.get('name')
    
    if not template_type or not template_name:
        return jsonify({"error": "缺少参数"}), 400
    
    try:
        template_content = load_template(template_type, template_name)
        # 解析模板中的参数 (格式为 {{param_name}})
        import re
        params = re.findall(r'{{(.*?)}}', template_content)
        return jsonify({"params": params})
    except Exception as e:
        return jsonify({"error": str(e)}), 500

@app.route('/create_job', methods=['POST'])
def create_job():
    """创建并可选地运行作业"""
    try:
        data = request.json
        
        # 构造参数对象
        class Args:
            pass
        
        args = Args()
        args.job_name = data.get('job_name')
        args.source = data.get('source')
        args.source_params = json.dumps(data.get('source_params', {}))
        args.transform = data.get('transform')
        args.transform_params = json.dumps(data.get('transform_params', {})) if data.get('transform') else None
        args.sink = data.get('sink')
        args.sink_params = json.dumps(data.get('sink_params', {}))
        args.mode = data.get('mode', 'BATCH')
        args.parallelism = int(data.get('parallelism', 1))
        
        # 生成配置文件
        config_path = generate_config(args)
        
        # 如果需要运行作业
        if data.get('run', False):
            # 异步运行作业
            import threading
            engine = data.get('engine', 'flink')
            threading.Thread(target=run_job, args=(config_path, engine)).start()
            return jsonify({"status": "success", "message": "作业已提交运行", "config_path": config_path})
        else:
            return jsonify({"status": "success", "message": "配置文件已生成", "config_path": config_path})
    
    except Exception as e:
        return jsonify({"status": "error", "message": str(e)}), 500

@app.route('/jobs')
def list_jobs():
    """列出所有作业"""
    output_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), "output")
    jobs = [f.split('.')[0] for f in os.listdir(output_dir) if f.endswith('.conf')]
    return jsonify({"jobs": jobs})

if __name__ == '__main__':
    app.run(debug=True, host='0.0.0.0', port=5000)

创建一个简单的HTML模板:

html:/Users/francis/TraeProject/seatunnel/easy-seatunnel/web/templates/index.html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>SeaTunnel简易配置工具</title>
    <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
    <style>
        body {
            padding-top: 20px;
            padding-bottom: 20px;
        }
        .header {
            padding-bottom: 20px;
            border-bottom: 1px solid #e5e5e5;
            margin-bottom: 30px;
        }
        .form-section {
            margin-bottom: 30px;
            padding: 20px;
            border: 1px solid #eee;
            border-radius: 5px;
        }
        .param-group {
            margin-top: 15px;
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <h2>🌊 SeaTunnel简易配置工具</h2>
            <p class="lead">只需几步,轻松创建数据集成任务</p>
        </div>

        <div id="jobForm">
            <div class="form-section">
                <h4>基本配置</h4>
                <div class="row">
                    <div class="col-md-6">
                        <div class="mb-3">
                            <label for="jobName" class="form-label">作业名称</label>
                            <input type="text" class="form-control" id="jobName" required>
                        </div>
                    </div>
                    <div class="col-md-3">
                        <div class="mb-3">
                            <label for="jobMode" class="form-label">作业模式</label>
                            <select class="form-select" id="jobMode">
                                <option value="BATCH">批处理</option>
                                <option value="STREAMING">流处理</option>
                            </select>
                        </div>
                    </div>
                    <div class="col-md-3">
                        <div class="mb-3">
                            <label for="parallelism" class="form-label">并行度</label>
                            <input type="number" class="form-control" id="parallelism" value="1" min="1">
                        </div>
                    </div>
                </div>
            </div>

            <div class="form-section">
                <h4>数据源配置</h4>
                <div class="row">
                    <div class="col-md-4">
                        <div class="mb-3">
                            <label for="sourceType" class="form-label">数据源类型</label>
                            <select class="form-select" id="sourceType" onchange="loadTemplateParams('source', this.value)">
                                <option value="">请选择...</option>
                                {% for source in sources %}
                                <option value="{{ source }}">{{ source }}</option>
                                {% endfor %}
                            </select>
                        </div>
                    </div>
                </div>
                <div id="sourceParams" class="param-group">
                    <!-- 动态加载的参数表单 -->
                </div>
            </div>

            <div class="form-section">
                <h4>数据转换配置 <small class="text-muted">(可选)</small></h4>
                <div class="row">
                    <div class="col-md-4">
                        <div class="mb-3">
                            <label for="transformType" class="form-label">转换类型</label>
                            <select class="form-select" id="transformType" onchange="loadTemplateParams('transform', this.value)">
                                <option value="">无转换</option>
                                {% for transform in transforms %}
                                <option value="{{ transform }}">{{ transform }}</option>
                                {% endfor %}
                            </select>
                        </div>
                    </div>
                </div>
                <div id="transformParams" class="param-group">
                    <!-- 动态加载的参数表单 -->
                </div>
            </div>

            <div class="form-section">
                <h4>数据目标配置</h4>
                <div class="row">
                    <div class="col-md-4">
                        <div class="mb-3">
                            <label for="sinkType" class="form-label">目标类型</label>
                            <select class="form-select" id="sinkType" onchange="loadTemplateParams('sink', this.value)">
                                <option value="">请选择...</option>
                                {% for sink in sinks %}
                                <option value="{{ sink }}">{{ sink }}</option>
                                {% endfor %}
                            </select>
                        </div>
                    </div>
                </div>
                <div id="sinkParams" class="param-group">
                    <!-- 动态加载的参数表单 -->
                </div>
            </div>

            <div class="form-section">
                <h4>执行选项</h4>
                <div class="row">
                    <div class="col-md-4">
                        <div class="mb-3">
                            <label for="engine" class="form-label">执行引擎</label>
                            <select class="form-select" id="engine">
                                <option value="flink">Flink</option>
                                <option value="spark">Spark</option>
                            </select>
                        </div>
                    </div>
                    <div class="col-md-8">
                        <div class="form-check mt-4">
                            <input class="form-check-input" type="checkbox" id="runImmediately">
                            <label class="form-check-label" for="runImmediately">
                                生成配置后立即运行
                            </label>
                        </div>
                    </div>
                </div>
            </div>

            <div class="d-grid gap-2 d-md-flex justify-content-md-end mb-5">
                <button class="btn btn-primary" onclick="createJob()">创建作业</button>
            </div>
        </div>

        <div id="jobResult" style="display: none;">
            <div class="alert alert-success" role="alert">
                <h4 class="alert-heading">作业创建成功!</h4>
                <p id="resultMessage"></p>
                <hr>
                <p class="mb-0">
                    <button class="btn btn-sm btn-outline-secondary" onclick="showJobForm()">创建新作业</button>
                    <button class="btn btn-sm btn-outline-primary" id="viewConfigBtn">查看配置</button>
                </p>
            </div>
            <div class="card mt-3">
                <div class="card-header">配置文件内容</div>
                <div class="card-body">
                    <pre id="configContent" class="bg-light p-3" style="max-height: 400px; overflow-y: auto;"></pre>
                </div>
            </div>
        </div>
    </div>

    <script>
        // 加载模板参数
        function loadTemplateParams(type, name) {
            if (!name) {
                document.getElementById(type + 'Params').innerHTML = '';
                return;
            }
            
            fetch(`/get_template_params?type=${type}s&name=${name}`)
                .then(response => response.json())
                .then(data => {
                    if (data.error) {
                        alert('错误: ' + data.error);
                        return;
                    }
                    
                    let html = '<div class="row">';
                    for (let param of data.params) {
                        html += `
                            <div class="col-md-6">
                                <div class="mb-3">
                                    <label for="${type}_${param}" class="form-label">${param}</label>
                                    <input type="text" class="form-control" id="${type}_${param}" 
                                           data-param="${param}" required>
                                </div>
                            </div>
                        `;
                    }
                    html += '</div>';
                    
                    document.getElementById(type + 'Params').innerHTML = html;
                })
                .catch(error => {
                    console.error('Error:', error);
                    alert('获取参数失败: ' + error);
                });
        }
        
        // 创建作业
        function createJob() {
            // 收集基本信息
            const jobName = document.getElementById('jobName').value;
            const jobMode = document.getElementById('jobMode').value;
            const parallelism = document.getElementById('parallelism').value;
            const engine = document.getElementById('engine').value;
            const runImmediately = document.getElementById('runImmediately').checked;
            
            // 收集数据源参数
            const sourceType = document.getElementById('sourceType').value;
            const sourceParams = collectParams('source');
            
            // 收集转换参数
            const transformType = document.getElementById('transformType').value;
            const transformParams = transformType ? collectParams('transform') : {};
            
            // 收集目标参数
            const sinkType = document.getElementById('sinkType').value;
            const sinkParams = collectParams('sink');
            
            // 验证必填项
            if (!jobName || !sourceType || !sinkType) {
                alert('请填写所有必填项');
                return;
            }
            
            // 构造请求数据
            const data = {
                job_name: jobName,
                source: sourceType,
                source_params: sourceParams,
                transform: transformType || null,
                transform_params: transformParams,
                sink: sinkType,
                sink_params: sinkParams,
                mode: jobMode,
                parallelism: parseInt(parallelism),
                engine: engine,
                run: runImmediately
            };
            
            // 发送请求
            fetch('/create_job', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify(data)
            })
            .then(response => response.json())
            .then(data => {
                if (data.status === 'success') {
                    document.getElementById('resultMessage').textContent = data.message;
                    document.getElementById('jobForm').style.display = 'none';
                    document.getElementById('jobResult').style.display = 'block';
                    
                    // 设置查看配置按钮
                    document.getElementById('viewConfigBtn').onclick = function() {
                        fetch(data.config_path)
                            .then(response => response.text())
                            .then(content => {
                                document.getElementById('configContent').textContent = content;
                            })
                            .catch(error => {
                                console.error('Error:', error);
                                alert('获取配置文件失败: ' + error);
                            });
                    };
                } else {
                    alert('错误: ' + data.message);
                }
            })
            .catch(error => {
                console.error('Error:', error);
                alert('创建作业失败: ' + error);
            });
        }
        
        // 收集参数
        function collectParams(type) {
            const params = {};
            const container = document.getElementById(type + 'Params');
            const inputs = container.querySelectorAll('input[data-param]');
            
            for (let input of inputs) {
                params[input.dataset.param] = input.value;
            }
            
            return params;
        }
        
        // 显示作业表单
        function showJobForm() {
            document.getElementById('jobForm').style.display = 'block';
            document.getElementById('jobResult').style.display = 'none';
        }
    </script>
</body>
</html>

现在,让我们添加一个启动Web界面的脚本:

bash:/Users/francis/TraeProject/seatunnel/easy-seatunnel/bin/start-web.sh 复制代码
#!/bin/bash

# 获取脚本所在目录
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
PROJECT_ROOT=$(cd "${SCRIPT_DIR}/.." && pwd)
WEB_APP="${PROJECT_ROOT}/web/app.py"

# 检查依赖
if ! command -v python3 &> /dev/null; then
    echo "错误: 未找到Python3,请先安装Python3"
    exit 1
fi

# 检查Flask是否安装
if ! python3 -c "import flask" &> /dev/null; then
    echo "正在安装Flask..."
    pip3 install flask
fi

# 启动Web应用
echo "启动SeaTunnel简易配置工具Web界面..."
echo "请访问 http://localhost:5000"
python3 "${WEB_APP}"

别忘了添加执行权限:

bash 复制代码
chmod +x /Users/francis/TraeProject/seatunnel/easy-seatunnel/bin/start-web.sh

使用示例

下面通过一个完整的示例,展示如何使用我们的封装工具:

命令行方式

假设我们要从MySQL读取数据并写入到Elasticsearch:

bash 复制代码
# 进入项目目录
cd /Users/francis/TraeProject/seatunnel

# 使用命令行工具创建配置
./easy-seatunnel/bin/easy-seatunnel.sh \
  --job-name mysql-to-es \
  --source mysql \
  --source-params '{"jdbc_url":"jdbc:mysql://localhost:3306/test","username":"root","password":"password","query":"SELECT * FROM users","table_name":"users"}' \
  --sink elasticsearch \
  --sink-params '{"es_host":"localhost:9200","index_name":"users_index","source_table":"users"}' \
  --mode BATCH \
  --parallelism 2 \
  --engine flink \
  --run

Web界面方式

  1. 启动Web界面:
bash 复制代码
cd /Users/francis/TraeProject/seatunnel
./easy-seatunnel/bin/start-web.sh
  1. 打开浏览器访问 http://localhost:5000

  2. 在Web界面上填写相关配置:

    • 作业名称:mysql-to-es
    • 作业模式:批处理
    • 并行度:2
    • 数据源类型:mysql
    • 数据源参数:填写MySQL连接信息
    • 目标类型:elasticsearch
    • 目标参数:填写ES连接信息
    • 执行引擎:Flink
    • 勾选"生成配置后立即运行"
  3. 点击"创建作业"按钮

部署指南

前置条件

  1. JDK 1.8+
  2. Python 3.6+
  3. Flink 1.12.x (或其他支持的引擎)
  4. SeaTunnel最新版本

安装步骤

  1. 下载并安装SeaTunnel:
bash 复制代码
# 创建工作目录
mkdir -p /Users/francis/TraeProject/seatunnel
cd /Users/francis/TraeProject/seatunnel

# 下载SeaTunnel
wget https://dlcdn.apache.org/seatunnel/2.3.3/apache-seatunnel-2.3.3-bin.tar.gz

# 解压
tar -zxvf apache-seatunnel-2.3.3-bin.tar.gz
mv apache-seatunnel-2.3.3/* .
rm -rf apache-seatunnel-2.3.3 apache-seatunnel-2.3.3-bin.tar.gz
  1. 创建封装工具目录结构:
bash 复制代码
# 创建目录结构
mkdir -p /Users/francis/TraeProject/seatunnel/easy-seatunnel/bin
mkdir -p /Users/francis/TraeProject/seatunnel/easy-seatunnel/templates/{sources,transforms,sinks}
mkdir -p /Users/francis/TraeProject/seatunnel/easy-seatunnel/output
mkdir -p /Users/francis/TraeProject/seatunnel/easy-seatunnel/web/templates
  1. 复制前面提供的所有脚本和模板文件到相应目录

  2. 安装依赖:

bash 复制代码
# 安装Python依赖
pip3 install flask
  1. 添加执行权限:
bash 复制代码
chmod +x /Users/francis/TraeProject/seatunnel/easy-seatunnel/bin/*.sh
chmod +x /Users/francis/TraeProject/seatunnel/easy-seatunnel/bin/*.py

配置Flink环境

确保Flink环境已正确配置:

bash 复制代码
# 下载Flink(如果尚未安装)
wget https://archive.apache.org/dist/flink/flink-1.14.6/flink-1.14.6-bin-scala_2.11.tgz
tar -zxvf flink-1.14.6-bin-scala_2.11.tgz
mv flink-1.14.6 /Users/francis/TraeProject/flink

# 配置环境变量
echo 'export FLINK_HOME=/Users/francis/TraeProject/flink' >> ~/.bash_profile
echo 'export PATH=$PATH:$FLINK_HOME/bin' >> ~/.bash_profile
source ~/.bash_profile

验证安装

运行一个简单的测试作业来验证安装:

bash 复制代码
cd /Users/francis/TraeProject/seatunnel
./easy-seatunnel/bin/easy-seatunnel.sh \
  --job-name test-job \
  --source fake \
  --source-params '{"row_num":"10","table_name":"test_table"}' \
  --sink console \
  --sink-params '{"source_table":"test_table"}' \
  --run

使用示例

场景一:MySQL到Elasticsearch

这是一个典型的数据同步场景,将MySQL数据库中的数据同步到Elasticsearch索引中。

命令行方式
bash 复制代码
./easy-seatunnel/bin/easy-seatunnel.sh \
  --job-name mysql-to-es \
  --source mysql \
  --source-params '{"jdbc_url":"jdbc:mysql://localhost:3306/test","username":"root","password":"password","query":"SELECT * FROM users","table_name":"users"}' \
  --sink elasticsearch \
  --sink-params '{"es_host":"localhost:9200","index_name":"users_index","source_table":"users"}' \
  --mode BATCH \
  --parallelism 2 \
  --engine flink \
  --run
Web界面方式
  1. 启动Web界面:./easy-seatunnel/bin/start-web.sh
  2. 访问 http://localhost:5000
  3. 填写表单:
    • 作业名称:mysql-to-es
    • 数据源:mysql
    • 数据源参数:填写MySQL连接信息
    • 目标:elasticsearch
    • 目标参数:填写ES连接信息
  4. 点击"创建作业"

场景二:Kafka到HDFS

实时将Kafka消息写入HDFS,适合日志收集场景。

命令行方式
bash 复制代码
./easy-seatunnel/bin/easy-seatunnel.sh \
  --job-name kafka-to-hdfs \
  --source kafka \
  --source-params '{"bootstrap_servers":"localhost:9092","topic":"logs","group_id":"seatunnel","table_name":"logs"}' \
  --transform field_select \
  --transform-params '{"fields":"timestamp,level,message","source_table":"logs","result_table":"logs_filtered"}' \
  --sink hdfs \
  --sink-params '{"fs_default_fs":"hdfs://localhost:9000","path":"/data/logs","source_table":"logs_filtered"}' \
  --mode STREAMING \
  --parallelism 4 \
  --engine flink \
  --run

扩展功能

添加新的数据源模板

要添加新的数据源模板,只需在templates/sources目录下创建新的模板文件:

bash 复制代码
# 创建新的数据源模板
cat > /Users/francis/TraeProject/seatunnel/easy-seatunnel/templates/sources/mongodb.template << 'EOF'
source {
  MongoDB {
    uri = "{{mongo_uri}}"
    database = "{{database}}"
    collection = "{{collection}}"
    result_table_name = "{{table_name}}"
    
    # 高级参数(默认值)
    fetch_size = 1000
    schema = {}
  }
}
EOF

添加新的转换模板

同样,可以添加新的数据转换模板:

bash 复制代码
# 创建新的转换模板
cat > /Users/francis/TraeProject/seatunnel/easy-seatunnel/templates/transforms/sql.template << 'EOF'
transform {
  Sql {
    source_table_name = "{{source_table}}"
    result_table_name = "{{result_table}}"
    query = "{{sql_query}}"
  }
}
EOF

添加新的目标模板

添加新的数据目标模板:

bash 复制代码
# 创建新的目标模板
cat > /Users/francis/TraeProject/seatunnel/easy-seatunnel/templates/sinks/clickhouse.template << 'EOF'
sink {
  Clickhouse {
    host = "{{host}}"
    database = "{{database}}"
    table = "{{table}}"
    username = "{{username}}"
    password = "{{password}}"
    source_table_name = "{{source_table}}"
    
    # 高级参数(默认值)
    batch_size = 1000
    retry = 1
  }
}
EOF

总结与展望

本文详细介绍了如何基于Apache SeaTunnel构建一个简易的数据集成工具,通过模板化配置和友好的用户界面,大大降低了使用门槛。用户只需关注核心业务参数,无需深入了解SeaTunnel的复杂配置细节。

主要特点

  1. 简化配置:将复杂的SeaTunnel配置简化为少量必要参数
  2. 双重接口:同时提供命令行和Web界面,满足不同用户需求
  3. 模板化设计:预定义常用数据源模板,易于扩展
  4. 一键执行:配置完成后可直接运行,无需额外步骤

未来展望

  1. 任务监控:添加任务执行状态监控和日志查看功能
  2. 定时调度:集成定时调度功能,支持周期性任务执行
  3. 数据预览:添加数据源和结果预览功能
  4. 智能推荐:根据数据特征自动推荐合适的配置参数
  5. 多任务管理:支持多任务并行管理和依赖关系设置

通过这种封装方式,我们可以充分利用SeaTunnel强大的数据集成能力,同时大幅降低使用门槛,让更多用户能够轻松实现数据集成需求。无论是数据工程师还是业务分析人员,都能通过这个简易工具快速构建自己的数据管道。

正如SeaTunnel的名字所暗示的那样,我们的封装工具就像是在海底隧道上修建了一条高速公路,让数据在不同系统间的流转变得更加顺畅和高效。🌊🚀

相关推荐
大尾巴5 天前
记第一次跟踪seatunnel的任务运行过程四——getJobConfigParser().parse()的动作
seatunnel
大尾巴8 天前
记第一次跟踪seatunnel的任务运行过程三——解析配置的具体方法getLogicalDag
seatunnel
俊哥大数据3 个月前
【最新】17个一站式数据集成平台案例PPT下载(Apache SeaTunnel )
seatunnel
Str_Null4 个月前
Seatunnel解决ftp读取json文件无法读取数组以及格式化之后的json无法解析的问题
java·json·seatunnel
Str_Null4 个月前
Seatunnel运行时报错Caused by: java.lang.NoClassDefFoundError: com/mysql/cj/MysqlType
java·seatunnel
Str_Null4 个月前
Seatunnel解决Excel中无法将数字类型转换成字符串类型以及源码打包
java·excel·seatunnel·源码修改
大尾巴5 个月前
记第一次跟踪seatunnel的任务运行过程二——ClientJobExecutionEnvironment的execture方法
seatunnel
向阳12185 个月前
Apache SeaTunnel 介绍
apache·cdc·seatunnel
KawYang7 个月前
Seatunnel Web 无数据源问题
seatunnel·seatunnel web