欢迎光临
我们一直在努力

大数据调度集成:Sqoop与Airflow/Oozie协同实现高效数据同步

1. 大数据同步与调度集成概述

在当今大数据处理环境中,关系型数据库与Hadoop生态系统之间的数据交换是常见需求。Sqoop作为专门用于Hadoop与传统数据库间数据传输的工具,虽然功能强大,但在复杂的企业级数据管道中,单纯的Sqoop执行往往不足以满足企业对任务调度、依赖管理、错误处理和监控告警的高级需求。通过将Sqoop与专业的工作流调度系统Airflow或Oozie集成,可以构建更加健壮和高效的数据同步解决方案。

现代企业数据管道通常需要满足以下要求:

  • 定时增量同步:能够高效地定期同步增量数据,避免全量同步的资源浪费
  • 任务依赖管理:确保数据同步任务按照预定顺序执行,前序任务完成后才启动后续任务
  • 失败处理与告警:当同步任务失败时,能够及时通知相关人员并采取相应措施
  • 资源优化:合理分配计算资源,避免资源冲突和竞争

Airflow和Oozie作为业界成熟的开源调度系统,各有优势:Airflow具有更直观的UI和强大的Python扩展能力,而Oozie则与Hadoop生态系统集成更为紧密。接下来我们将分别探讨这两种系统与Sqoop的集成方案。

2. Sqoop与Airflow集成方案

Airflow是Apache旗下的开源工作流调度和管理平台,它通过有向无环图(DAG)来定义任务间的依赖关系。将Sqoop任务集成到Airflow中,可以实现更加灵活和强大的数据同步控制。

2.1 基本集成方式

Airflow提供了多种执行系统(Executor),其中LocalExecutor和CeleryExecutor较为常用。将Sqoop集成到Airflow中,主要使用BashOperator或SqoopOperator。

from airflow import DAG
from airflow.operators.bash_operator import BashOperator
from airflow.operators.sensors import TimeDeltaSensor
from datetime import datetime, timedelta
# 默认参数
default_args = {
'owner': 'data_team',
'depends_on_past': False,
'start_date': datetime(2023, 1, 1),
'email_on_failure': True,
'email_on_retry': False,
'retries': 1,
'retry_delay': timedelta(minutes=5),
}
# 创建DAG
DAG('incremental_sqoop_sync', default_args=default_args, schedule_interval='@daily')
# 定义SQOOP同步任务
sqoop_sync_task = BashOperator(
task_id='incremental_sqoop_sync_task',
bash_command='sqoop import –connect jdbc:mysql://mysql-server/db_name ' \\
'–username user –password pass ' \\
'–table orders –incremental lastmodified –check-column updated_at ' \\
'–last-value $(date -d "yesterday" +"%Y-%m-%d %H:%M:%S")'
)

2.2 定时增量同步实现

增量同步是数据同步中的关键需求。对于Sqoop而言,增量同步主要通过–incremental参数实现,配合–check-column和–last-value参数指定增量检查的列和基准值。

# 使用模板实现动态增量同步值
from airflow.operators.python_operator import PythonOperator
from airflow.operators.bash_operator import BashOperator
from jinja2 import Template
from datetime import datetime, timedelta
def get_last_sync_value(**context):
# 从数据库获取上一次同步的最大ID或时间戳
last_value = # 从数据库查询逻辑
return last_value
last_value_task = PythonOperator(
task_id='get_last_sync_value',
python_callable=get_last_sync_value
)
# 使用模板生成SQOOP命令
sqoop_template = Template("""
sqoop import –connect jdbc:mysql://mysql-server/db_name \\
–username user –password pass \\
–table orders –incremental lastmodified –check-column updated_at \\
–last-value {{ last_value }} –target-dir /data/orders
""")
sqoop_sync_task = BashOperator(
task_id='incremental_sqoop_sync',
bash_command=sqoop_template.render(last_value="{{ ti.xcom_pull(task_ids='get_last_sync_value') }}")
)
# 设置任务依赖
last_value_task >> sqoop_sync_task

2.3 任务依赖管理

Airflow通过DAG和任务之间的依赖关系轻松实现任务依赖管理。可以使用>>和<<操作符或set_upstream和set_downstream方法来定义任务间的依赖关系。

from airflow.operators.python_operator import PythonOperator
from airflow.operators.bash_operator import BashOperator
from airflow.operators.dummy_operator import DummyOperator
# 定义起始和结束任务
start = DummyOperator(task_id='start')
end = DummyOperator(task_id='end')
# 数据预处理任务
data_prep_task = BashOperator(
task_id='data_prep',
bash_command='hadoop fs -rm -r /data/orders/processed'
)
# 数据同步任务
sqoop_sync_task = BashOperator(
task_id='sqoop_sync',
bash_command='sqoop import …'
)
# 数据后处理任务
data_post_process = BashOperator(
task_id='data_post_process',
bash_command='hadoop jar process.jar /data/orders/processed /data/processed'
)
# 设置任务依赖关系
start >> data_prep_task >> sqoop_sync_task >> data_post_process >> end

2.4 失败处理与告警

Airflow提供了强大的失败处理和告警机制,可以通过配置和自定义操作来实现。

from airflow.utils.email import send_email
from airflow.operators.python_operator import PythonOperator
from airflow.operators.python import BranchPythonOperator
from airflow.utils.trigger_rule import TriggerRule
def send_failure_email(context):
subject = 'Sqoop同步任务失败'
html_content = f"""
<h3>任务信息</h3>
<p>任务ID: {context['task_instance'].task_id}</p>
<p>执行时间: {context['execution_date']}</p>
<p>重试次数: {context['task_instance'].try_number}</p>
<h3>错误信息</h3>
<p>{context['exception']}</p>
"""
send_email(
to=['data_team@example.com'],
subject=subject,
html_content=html_content,
cc=['manager@example.com']
)
return 'alert'
# 失败处理任务
failure_handler = PythonOperator(
task_id='failure_handler',
python_callable=send_failure_email,
trigger_rule=TriggerRule.ONE_FAILED,
dag=dag
)
# 设置失败依赖关系
sqoop_sync_task.on_failure_callback = failure_handler

3. Sqoop与Oozie集成方案

Oozie是另一个流行的Hadoop工作流调度系统,它基于有限状态机模型,通过XML定义工作流。Oozie与Hadoop生态系统紧密集成,适合在纯Hadoop环境中部署。

3.1 工作流设计

Oozie工作流由一系列动作(action)和控制流节点组成。对于Sqoop集成,通常使用Sqoop动作(action)。

<workflow-app name="incremental_sqoop_sync" xmlns="uri:oozie:workflow:0.5">
<start to="sqoop_node"/>

<action name="sqoop_node">
<sqoop xmlns="uri:oozie:sqoop-action:0.2">
<job-xml>sqoop-site.xml</job-xml>
<configuration>
<property>
<name>mapred.job.queue.name</name>
<value>data-queue</value>
</property>
</configuration>
<command>import \\
–connect jdbc:mysql://mysql-server/db_name \\
–username user \\
–password pass \\
–table orders \\
–target-dir /data/orders \\
–incremental lastmodified \\
–check-column updated_at \\
–last-value ${LAST_VALUE}</command>
</sqoop>
<ok to="end"/>
<error to="fail"/>
</action>

<kill name="fail">
<message>Sqoop同步任务失败,错误信息[${wf:errorMessage(wf:lastErrorNode())}]</message>
</kill>

<end name="end"/>
</workflow-app>

3.2 定时增量同步实现

在Oozie中实现增量同步,通常使用–last-value参数,并通过Oozie变量传递增量值。

<workflow-app name="incremental_sqoop_sync" xmlns="uri:oozie:workflow:0.5">
<start to="prepare_last_value"/>

<action name="prepare_last_value">
<shell xmlns="uri:oozie:shell-action:0.1">
<job-xml>core-site.xml</job-xml>
<exec>get_last_value.sh</exec>
<file>get_last_value.sh#get_last_value.sh</file>
<argument>${ coordination:currentTime() }</argument>
<env-var>HADOOP_USER_NAME=${user.name}</env-var>
<capture-output/>
</shell>
<ok to="sqoop_node"/>
<error to="fail"/>
</action>

<action name="sqoop_node">
<sqoop xmlns="uri:oozie:sqoop-action:0.2">
<job-xml>sqoop-site.xml</job-xml>
<command>import \\
–connect jdbc:mysql://mysql-server/db_name \\
–username user \\
–password pass \\
–table orders \\
–target-dir /data/orders \\
–incremental lastmodified \\
–check-column updated_at \\
–last-value ${data_output}</command>
</sqoop>
<ok to="end"/>
<error to="fail"/>
</action>

<kill name="fail">
<message>Sqoop同步任务失败,错误信息[${wf:errorMessage(wf:lastErrorNode())}]</message>
</kill>

<end name="end"/>
</workflow-app>

其中get_last_value.sh脚本用于获取上一次同步的基准值:

#!/bin/bash
# 获取上一次同步的最大时间戳
LAST_SYNC=$(mysql -u user -ppass -h mysql-server -e "SELECT MAX(updated_at) FROM orders" -s -N)
echo "data_output=$LAST_SYNC"

3.3 依赖管理与条件执行

Oozie通过控制流节点实现条件执行和依赖管理。例如,可以使用decision节点根据Sq同步结果决定后续流程。

<workflow-app name="conditional_sqoop_flow" xmlns="uri:oozie:workflow:0.5">
<start to="sqoop_node"/>

<action name="sqoop_node">
<sqoop xmlns="uri:oozie:sqoop-action:0.2">
<job-xml>sqoop-site.xml</job-xml>
<configuration>
<property>
<name>mapred.job.queue.name</name>
<value>data-queue</value>
</property>
</configuration>
<command>import …</command>
</sqoop>
<ok to="decision_node"/>
<error to="fail"/>
</action>

<decision name="decision_node">
<switch>
<case to="post_process">${fs:exists(dir_path)}</case>
<default to="error"/>
</switch>
</decision>

<action name="post_process">
<shell xmlns="uri:oozie:shell-action:0.1">
<job-xml>core-site.xml</job-xml>
<exec>process_data.sh</exec>
<file>process_data.sh#process_data.sh</file>
<argument>${ data_output }</argument>
</shell>
<ok to="end"/>
<error to="fail"/>
</action>

<kill name="fail">
<message>工作流执行失败,错误信息[${wf:errorMessage(wf:lastErrorNode())}]</message>
</kill>

<kill name="error">
<message>数据同步未产生有效数据,请检查数据源</message>
</kill>

<end name="end"/>
</workflow-app>

3.4 失败处理与告警

Oozie提供了内置的失败处理机制,同时可以通过集成邮件和监控系统实现告警。

<workflow-app name="sqoop_sync_with_alert" xmlns="uri:oozie:workflow:0.5">
<start to="sqoop_node"/>

<action name="sqoop_node">
<sqoop xmlns="uri:oozie:sqoop-action:0.2">
<job-xml>sqoop-site.xml</job-xml>
<configuration>
<property>
<name>mapred.job.queue.name</name>
<value>data-queue</value>
</property>
</configuration>
<command>import …</command>
</sqoop>
<ok to="end"/>
<error to="fail_alert"/>
</action>

<action name="fail_alert">
<email xmlns="uri:oozie:email-action:0.1">
<to>data_team@example.com</to>
<cc>manager@example.com</cc>
<subject>Sqoop同步任务失败</subject>
<body>
Sqoop同步任务失败,错误信息:${wf:errorMessage(wf:lastErrorNode())}。请检查数据源和网络连接。
执行时间:${wf:conf("oozie.wf.application.path")}
开始时间:${wf:conf("user.name")}
</body>
</email>
<ok to="end"/>
<error to="fail"/>
</action>

<kill name="fail">
<message>工作流执行失败,错误信息[${wf:errorMessage(wf:lastErrorNode())}]</message>
</kill>

<end name="end"/>
</workflow-app>

4. Airflow与Oozie集成对比

| 特性 | Airflow集成 | Oozie集成 |

|——|————|———–|

| 学习曲线 | 中等(Python基础) | 较高(XML和Hadoop知识) |

| 增量同步实现 | Python脚本动态传递增量值 | 需要外部脚本获取增量值 |

| 任务依赖管理 | 直观的Python代码定义 | XML中的控制流节点 |

| UI界面 | 现代化、功能丰富 | 相对简单 |

| 扩展性 | 高,可通过Python库扩展 | 中,主要依赖Hadoop生态系统 |

| 错误处理 | Python回调函数 | XML控制节点和邮件动作 |

| 调试能力 | 强,支持任务日志查看 | 一般,依赖Hadoop日志 |

| 适用场景 | 复杂ETL流程、混合环境 | 纯Hadoop环境、Hadoop深度集成 |

5. 最佳实践与最小示例

5.1 性能优化建议

  • 批处理优化:将小型同步任务合并为批次,减少调度开销
  • 资源分配:根据数据量调整mapreduce任务资源
  • 并行同步:使用Airflow的并行执行能力或Oozie的fork节点实现多表并行同步
  • 增量值存储:使用高效的存储方式记录增量值,如关系型数据库或HBase
  • 5.2 安全考虑

  • 凭证管理:避免在配置文件中硬编码密码,使用Airflow Variables或Hadoop Key Management
  • 访问控制:限制调度系统的管理员权限,实施最小权限原则
  • 数据加密:对敏感数据传输和存储进行加密
  • 5.3 完整最小示例

    以下是使用Airflow实现的一个完整最小示例,包含增量同步、依赖管理和失败告警:

    from airflow import DAG
    from airflow.operators.bash_operator import BashOperator
    from airflow.operators.python_operator import PythonOperator
    from airflow.utils.email import send_email
    from jinja2 import Template
    from datetime import datetime, timedelta
    import mysql.connector
    def get_last_sync_value():
    """从数据库获取上一次同步的最大ID"""
    try:
    conn = mysql.connector.connect(
    host="mysql-server",
    user="user",
    password="pass",
    database="db_name"
    )
    cursor = conn.cursor()
    cursor.execute("SELECT MAX(id) FROM orders")
    result = cursor.fetchone()
    return str(result[0]) if result[0] else "0"
    except Exception as e:
    raise Exception(f"获取增量值失败: {str(e)}")
    finally:
    if conn.is_connected():
    cursor.close()
    conn.close()
    def send_failure_email(context):
    """发送失败邮件通知"""
    subject = "Sqoop同步任务失败"
    html_content = f"""
    <h3>任务信息</h3>
    <p>任务ID: {context['task_instance'].task_id}</p>
    <p>执行时间: {context['execution_date']}</p>
    <p>重试次数: {context['task_instance'].try_number}</p>
    <h3>错误信息</h3>
    <p>{context['exception']}</p>
    """
    send_email(
    to=["data_team@example.com"],
    subject=subject,
    html_content=html_content,
    cc=["manager@example.com"]
    )
    return 'alert'
    # 默认参数
    default_args = {
    'owner': 'data_team',
    'depends_on_past': False,
    'start_date': datetime(2023, 1, 1),
    'email_on_failure': True,
    'email_on_retry': False,
    'retries': 2,
    'retry_delay': timedelta(minutes=10),
    }
    # 创建DAG
    DAG('incremental_sqoop_sync_dag', default_args=default_args, schedule_interval='@daily', catchup=False)
    # 定义任务
    get_last_value_task = PythonOperator(
    task_id='get_last_sync_value',
    python_callable=get_last_sync_value
    )
    # SQOOP命令模板
    sqoop_template = Template("""
    sqoop import –connect jdbc:mysql://mysql-server/db_name \\
    –username user –password pass \\
    –table orders –incremental append –check-column id \\
    –last-value {{ last_value }} –target-dir /data/orders \\
    –as-textfile –num-mappers 4
    """)
    sqoop_sync_task = BashOperator(
    task_id='incremental_sqoop_sync',
    bash_command=sqoop_template.render(last_value="{{ ti.xcom_pull(task_ids='get_last_sync_value') }}")
    )
    # 数据后处理任务
    data_post_process = BashOperator(
    task_id='data_post_process',
    bash_command='hadoop jar /path/to/process.jar /data/orders /data/processed'
    )
    # 设置任务依赖
    get_last_value_task >> sqoop_sync_task >> data_post_process
    # 设置失败回调
    sqoop_sync_task.on_failure_callback = send_failure_email

    6. 注意事项

  • 增量列选择:确保增量检查列具有唯一性和有序性,通常使用自增ID或时间戳
  • 错误处理:正确处理同步过程中的错误,避免脏数据产生
  • 资源管理:根据数据量调整mapreduce任务资源,避免资源冲突
  • 测试验证:在生产环境部署前,充分测试同步逻辑和错误处理机制
  • 监控维护:建立完善的监控机制,及时发现和处理异常情况
  • 版本兼容:确保Sqoop版本与Hadoop版本兼容,避免版本不匹配问题
  • 增量值持久化:可靠地存储和管理增量值,确保同步的连续性和一致性
  • #publish-mermaid-1788750490055-0{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#publish-mermaid-1788750490055-0 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#publish-mermaid-1788750490055-0 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#publish-mermaid-1788750490055-0 .error-icon{fill:#552222;}#publish-mermaid-1788750490055-0 .error-text{fill:#552222;stroke:#552222;}#publish-mermaid-1788750490055-0 .edge-thickness-normal{stroke-width:1px;}#publish-mermaid-1788750490055-0 .edge-thickness-thick{stroke-width:3.5px;}#publish-mermaid-1788750490055-0 .edge-pattern-solid{stroke-dasharray:0;}#publish-mermaid-1788750490055-0 .edge-thickness-invisible{stroke-width:0;fill:none;}#publish-mermaid-1788750490055-0 .edge-pattern-dashed{stroke-dasharray:3;}#publish-mermaid-1788750490055-0 .edge-pattern-dotted{stroke-dasharray:2;}#publish-mermaid-1788750490055-0 .marker{fill:#333333;stroke:#333333;}#publish-mermaid-1788750490055-0 .marker.cross{stroke:#333333;}#publish-mermaid-1788750490055-0 svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#publish-mermaid-1788750490055-0 p{margin:0;}#publish-mermaid-1788750490055-0 .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#publish-mermaid-1788750490055-0 .cluster-label text{fill:#333;}#publish-mermaid-1788750490055-0 .cluster-label span{color:#333;}#publish-mermaid-1788750490055-0 .cluster-label span p{background-color:transparent;}#publish-mermaid-1788750490055-0 .label text,#publish-mermaid-1788750490055-0 span{fill:#333;color:#333;}#publish-mermaid-1788750490055-0 .node rect,#publish-mermaid-1788750490055-0 .node circle,#publish-mermaid-1788750490055-0 .node ellipse,#publish-mermaid-1788750490055-0 .node polygon,#publish-mermaid-1788750490055-0 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#publish-mermaid-1788750490055-0 .rough-node .label text,#publish-mermaid-1788750490055-0 .node .label text,#publish-mermaid-1788750490055-0 .image-shape .label,#publish-mermaid-1788750490055-0 .icon-shape .label{text-anchor:middle;}#publish-mermaid-1788750490055-0 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#publish-mermaid-1788750490055-0 .rough-node .label,#publish-mermaid-1788750490055-0 .node .label,#publish-mermaid-1788750490055-0 .image-shape .label,#publish-mermaid-1788750490055-0 .icon-shape .label{text-align:center;}#publish-mermaid-1788750490055-0 .node.clickable{cursor:pointer;}#publish-mermaid-1788750490055-0 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#publish-mermaid-1788750490055-0 .arrowheadPath{fill:#333333;}#publish-mermaid-1788750490055-0 .edgePath .path{stroke:#333333;stroke-width:1px;}#publish-mermaid-1788750490055-0 .flowchart-link{stroke:#333333;fill:none;}#publish-mermaid-1788750490055-0 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#publish-mermaid-1788750490055-0 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#publish-mermaid-1788750490055-0 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#publish-mermaid-1788750490055-0 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#publish-mermaid-1788750490055-0 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#publish-mermaid-1788750490055-0 .cluster text{fill:#333;}#publish-mermaid-1788750490055-0 .cluster span{color:#333;}#publish-mermaid-1788750490055-0 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#publish-mermaid-1788750490055-0 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#publish-mermaid-1788750490055-0 rect.text{fill:none;stroke-width:0;}#publish-mermaid-1788750490055-0 .icon-shape,#publish-mermaid-1788750490055-0 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#publish-mermaid-1788750490055-0 .icon-shape p,#publish-mermaid-1788750490055-0 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#publish-mermaid-1788750490055-0 .icon-shape .label rect,#publish-mermaid-1788750490055-0 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#publish-mermaid-1788750490055-0 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#publish-mermaid-1788750490055-0 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#publish-mermaid-1788750490055-0 .node .neo-node{stroke:#9370DB;}#publish-mermaid-1788750490055-0 [data-look=\”neo\”].node rect,#publish-mermaid-1788750490055-0 [data-look=\”neo\”].cluster rect,#publish-mermaid-1788750490055-0 [data-look=\”neo\”].node polygon{stroke:#9370DB;filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#publish-mermaid-1788750490055-0 [data-look=\”neo\”].swimlane.cluster rect{filter:none;}#publish-mermaid-1788750490055-0 [data-look=\”neo\”].node path{stroke:#9370DB;stroke-width:1px;}#publish-mermaid-1788750490055-0 [data-look=\”neo\”].node .outer-path{filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#publish-mermaid-1788750490055-0 [data-look=\”neo\”].node .neo-line path{stroke:#9370DB;filter:none;}#publish-mermaid-1788750490055-0 [data-look=\”neo\”].node circle{stroke:#9370DB;filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#publish-mermaid-1788750490055-0 [data-look=\”neo\”].node circle .state-start{fill:#000000;}#publish-mermaid-1788750490055-0 [data-look=\”neo\”].icon-shape .icon{fill:#9370DB;filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#publish-mermaid-1788750490055-0 [data-look=\”neo\”].icon-shape .icon-neo path{stroke:#9370DB;filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#publish-mermaid-1788750490055-0 :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}AirflowOozie是否

    开始定时同步

    获取上次同步增量值

    选择调度系统

    定义DAG和任务依赖

    定义XML工作流

    执行Sqoop同步任务

    同步成功?

    执行数据后处理

    发送失败告警

    更新增量值

    记录错误日志

    赞(0)
    未经允许不得转载:171主机测评 » 大数据调度集成:Sqoop与Airflow/Oozie协同实现高效数据同步
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址