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/bootstrap@5.1.3/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的名字所暗示的那样,我们的封装工具就像是在海底隧道上修建了一条高速公路,让数据在不同系统间的流转变得更加顺畅和高效。🌊🚀

相关推荐
ApacheSeaTunnel7 天前
保姆级 SeaTunnel 入门!再学不会小编当场表演倒立敲代码
大数据·数据集成·seatunnel·技术分享·数据同步
SeaTunnel12 天前
Apache SeaTunnel MySQL CDC 支持按时间启动吗?
大数据·数据库·mysql·开源·apache·seatunnel
ApacheSeaTunnel13 天前
告别手敲 Schema!SeaTunnel 集成 Gravitino 元数据 RestApi 这个新动作有点酷
大数据·ai·seatunnel·技术分享·数据同步·gravitino
SeaTunnel1 个月前
Apache SeaTunnel 2025 案例精选重磅发布!
大数据·开源·apache·seatunnel·案例
码农丁丁2 个月前
apache seatunnel 2.3.12 Changelog
apache·seatunnel
SeaTunnel2 个月前
Apache SeaTunnel 如何将 CDC 数据流转换为 Append-Only 模式?
大数据·开源·apache·开发者·seatunnel·转换插件
SeaTunnel3 个月前
结项报告完整版:Apache SeaTunnel 支持 Flink 引擎 Schema Evolution 功能
java·大数据·flink·开源·seatunnel
ApacheSeaTunnel3 个月前
结项报告完整版 | Apache SeaTunnel支持metalake开发
大数据·开源·数据集成·seatunnel·数据同步
SeaTunnel3 个月前
Apache SeaTunnel 支持 Metalake 开发了!避免任务配置敏感信息暴露
大数据·开源·apache·个人开发·数据集成·seatunnel·看开源之夏
ApacheSeaTunnel4 个月前
从小时级到分钟级:多点DMALL如何用Apache SeaTunnel把数据集成成本砍到1/3?
大数据·开源·数据集成·seatunnel·技术分享