【Ubuntu datasophon1.2.1 二开之九:验证离线数据入湖】

Ubuntu datasophon1.2.1 二开之九:验证离线数据入湖

  • 背景
  • 环境准备
  • 遇到坑及填平方法
    • [1.现象: 经典的 NoClassDefFoundError,例如 org/apache/spark/kafka010/KafkaConfigUpdater 和 org/apache/spark/sql/connector/write/Write。](#1.现象: 经典的 NoClassDefFoundError,例如 org/apache/spark/kafka010/KafkaConfigUpdater 和 org/apache/spark/sql/connector/write/Write。)
    • [2. Spark与Paimon版本不兼容](#2. Spark与Paimon版本不兼容)
    • [3. HDFS权限问题](#3. HDFS权限问题)
    • [4. 元数据存储方式选择](#4. 元数据存储方式选择)
    • [5. 环境与组件升级](#5. 环境与组件升级)
    • [6.Spark 找不到 Kafka 数据源](#6.Spark 找不到 Kafka 数据源)
  • [下载匹配版本的 Kafka 依赖](#下载匹配版本的 Kafka 依赖)
  • [在 DS Spark 任务的"选项参数"中添加 --jars](#在 DS Spark 任务的"选项参数"中添加 --jars)
    • [7.Paimon 表创建失败(Derby 权限问题)](#7.Paimon 表创建失败(Derby 权限问题))
    • [8.Kafka Topic 不存在](#8.Kafka Topic 不存在)
  • [查看所有 topic](#查看所有 topic)
  • [使用正确的 topic 名称](#使用正确的 topic 名称)
    • [9.Spark 版本不匹配导致类找不到](#9.Spark 版本不匹配导致类找不到)
    • [10.DolphinScheduler 参数换行问题](#10.DolphinScheduler 参数换行问题)
    • [11.Shell 脚本换行符问题](#11.Shell 脚本换行符问题)
  • 转换换行符
  • [或直接在 DS 的脚本框中编写,不上传文件](#或直接在 DS 的脚本框中编写,不上传文件)
    • [12.Shell 脚本中 Spark 命令缺少配置](#12.Shell 脚本中 Spark 命令缺少配置)
    • [13.CSV 导入 ClickHouse 格式错误](#13.CSV 导入 ClickHouse 格式错误)
  • [将 CSV 转换为 TSV(逗号 → 制表符)](#将 CSV 转换为 TSV(逗号 → 制表符))
    • [14.Shell 脚本 while 循环只执行一次](#14.Shell 脚本 while 循环只执行一次)
  • [不推荐:while 循环逐行插入](#不推荐:while 循环逐行插入)
  • 推荐:管道批量导入
    • [15. Paimon 表写入失败(表不存在)](#15. Paimon 表写入失败(表不存在))
  • 最后

背景

前面一篇已经验证在线数据入湖了,最后一步就是验证离线数据入湖。虽然已经做好坑坑洼洼的准备。但是困难比预想要多得多。花时间要多得多。为了最后一哆嗦,坚持坚持坚持,终于胜利!

环境准备

1. 在datasophon安装好dolphinscheduler 3.1.8

配置租户

hdfs就是linux用户

创建环境

告诉ds spark3位置,hdfs位置

修改配置文件

修改bin\env\dolphinscheduler_env.sh

总结一下:

1.修改数据库类型为mysql,指定数据库url,用户名及密码

2.修改zk地址

3.修改hadoop位置及它配置目录

4.jdk位置

5.hive及flink位置

修改 work-server\bin\start.sh

就修改一行:

原来:

javascript 复制代码
$JAVA_HOME/bin/java $JAVA_OPTS \
  -cp "$DOLPHINSCHEDULER_HOME/conf":"$DOLPHINSCHEDULER_HOME/libs/*" \
  org.apache.dolphinscheduler.server.worker.WorkerServer

改成:

javascript 复制代码
$JAVA_HOME/bin/java $JAVA_OPTS \
 -cp "$DOLPHINSCHEDULER_HOME/conf":"$DOLPHINSCHEDULER_HOME/libs/*":${HADOOP_CONF_DIR}:${HADOOP_HOME}/share/hadoop/common/*:${HADOOP_HOME}/share/hadoop/hdfs/* \
  org.apache.dolphinscheduler.server.worker.WorkerServer

启动时传入hdfs配置目录及home目录

2. 升级spark3版本

datasophon 自带spark3.1.3 ,不支持paimon,换句话paimon,支持spark 从3.2版本开始。必须升级,否则报类找不到,这个问题耽搁我好长时间,paimon从0.7-0.9都试,还试了kafka-connect方式。

遇到坑及填平方法

1.现象: 经典的 NoClassDefFoundError,例如 org/apache/spark/kafka010/KafkaConfigUpdater 和 org/apache/spark/sql/connector/write/Write。

填平方案:

依赖分析: 通过错误栈和GitHub issue确认,KafkaConfigUpdater 类属于 Spark 而非 Paimon,且需要额外的 spark-token-provider-kafka 等JAR包。

版本匹配: 最终放弃在Spark 3.1.3上挣扎,将Spark升级到与Paimon 0.9.0兼容的3.2.4版本,从根本上解决了 Write 类找不到的API不兼容问题。

依赖管理: 明确需要通过 --jars 参数或DataSophon资源中心,将 paimon-spark-3.2-0.9.0.jar 等所有依赖JAR包完整地提供给Spark任务。

2. Spark与Paimon版本不兼容

现象: 使用Paimon 0.8.2或0.9.0时,均报告 Write 类找不到。

填平方案:

放弃低版本组合: 确认Paimon从某个版本开始强依赖Spark 3.2的DataSource V2 API,与你的Spark 3.1.3环境不兼容。

升级Spark: 决定将Spark从 3.1.3 升级到 3.2.4。这一步是解决问题的关键转折点,虽然需要升级组件,但一劳永逸地解决了兼容性问题。

3. HDFS权限问题

现象: 建表或写入时报 Permission denied,用户 root123 无法在 /user/paimon/warehouse 或 /user/hive/warehouse 下创建目录。

填平方案:

使用 sudo -u hdfs: 切换到HDFS超级用户执行权限修复命令。

递归授权: 执行 hdfs dfs -chown -R root123:supergroup /user/paimon 和 hdfs dfs -chmod -R 755 /user/paimon,将目录所有者改为当前用户并赋予写权限。

调整Hive仓库权限: 类似地,对 /user/hive/warehouse 目录也进行了权限调整,确保了元数据操作顺畅。

4. 元数据存储方式选择

现象: 使用Paimon默认的文件系统Catalog时,元数据直接存在HDFS上,导致并发问题和权限困扰。

填平方案:

切换为Hive Metastore: 最终决定使用 Hive Metastore 作为Paimon的Catalog。配置如下:

text

--conf spark.sql.catalog.paimon.metastore=hive

--conf spark.sql.catalog.paimon.uri=thrift://ddp1:9083

明确数据存储路径: 同时指定 spark.sql.catalog.paimon.warehouse,实现元数据(在MySQL/Hive)与数据文件(在HDFS)的分离,使整个架构更清晰、更稳定。

5. 环境与组件升级

现象: 旧版Spark 3.1.3成为兼容性瓶颈,且DataSophon管理着多个组件,升级有顾虑。

填平方案:

确认兼容性: 检查并确认Hadoop 3.3.3、Hive 3.1.3、Kafka 2.4.1等核心组件与Spark 3.2.4和JDK 11兼容。

分步升级: 选择保持JDK 8不变,仅升级Spark到3.2.4,将影响面降到最低,成功绕过JDK升级的风险。

6.Spark 找不到 Kafka 数据源

错误信息:

text

Failed to find data source: kafka

原因: Spark 默认不包含 Kafka 集成包。

解决方案:

bash

下载匹配版本的 Kafka 依赖

spark-sql-kafka-0-10_2.12-3.2.0.jar

kafka-clients-2.8.0.jar

commons-pool2-2.11.1.jar

在 DS Spark 任务的"选项参数"中添加 --jars

--jars /path/to/spark-sql-kafka-0-10_2.12-3.2.0.jar,/path/to/kafka-clients-2.8.0.jar,/path/to/commons-pool2-2.11.1.jar

关键点:

版本必须与 Spark 版本匹配

commons-pool2 是 Kafka 消费者的传递依赖,容易遗漏

7.Paimon 表创建失败(Derby 权限问题)

错误信息:

text

ERROR XBM0H: Directory /.../metastore_db cannot be created.

java.io.FileNotFoundException: derby.log (Permission denied)

原因: Spark 默认使用嵌入式 Derby 作为 Hive Metastore,没有配置 Hive Metastore 时会尝试在临时目录创建 Derby 数据库。

解决方案: 配置使用外部的 Hive Metastore

bash

--conf spark.sql.catalog.paimon.metastore=hive

--conf spark.sql.catalog.paimon.uri=thrift://hive-metastore-host:9083

--conf spark.sql.extensions=org.apache.paimon.spark.extensions.PaimonSparkSessionExtensions

8.Kafka Topic 不存在

错误信息:

text

UNKNOWN_TOPIC_OR_PARTITION

原因: 脚本中配置的 topic 名称与实际不符。

解决方案:

bash

查看所有 topic

kafka-topics.sh --bootstrap-server kafka-host:9092 --list

使用正确的 topic 名称

kafka_topic = "user_log_topic" # 而不是 "user_log"

9.Spark 版本不匹配导致类找不到

错误信息:

text

java.lang.NoClassDefFoundError: org/apache/spark/kafka010/KafkaConfigUpdater

java.lang.ClassNotFoundException: org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig

原因: Spark 版本与 Kafka 依赖版本不匹配。

解决方案:

Spark 3.2.0 → 使用 spark-sql-kafka-0-10_2.12-3.2.0.jar 和 kafka-clients-2.8.0.jar

不能混用 3.2.4 的包

10.DolphinScheduler 参数换行问题

错误信息:

text

--conf: command not found

原因: DS 的"选项参数"中换行导致命令被分割。

解决方案: 所有参数写在一行,不要换行

properties

--jars /path/to/jar1.jar,/path/to/jar2.jar --conf key1=value1 --conf key2=value2

11.Shell 脚本换行符问题

错误信息:

text

$'\r': command not found

原因: 在 Windows 上编辑的脚本上传后带有 \r\n 换行符。

解决方案:

bash

转换换行符

sed -i 's/\r$//' script.sh

或直接在 DS 的脚本框中编写,不上传文件

12.Shell 脚本中 Spark 命令缺少配置

错误信息:

text

ERROR XBM0H: Directory /.../metastore_db cannot be created.

原因: Shell 脚本中的 spark-sql 命令没有配置 Hive Metastore。

解决方案: 在 Shell 脚本中的每个 spark-sql 命令都要加上完整配置:

bash

${SPARK_HOME}/bin/spark-sql

--conf spark.sql.catalog.paimon=org.apache.paimon.spark.SparkCatalog

--conf spark.sql.catalog.paimon.metastore=hive

--conf spark.sql.catalog.paimon.uri=thrift://ddp1:9083

--conf spark.sql.catalog.paimon.warehouse=hdfs://.../warehouse

--conf spark.sql.extensions=org.apache.paimon.spark.extensions.PaimonSparkSessionExtensions

13.CSV 导入 ClickHouse 格式错误

错误信息:

text

Code: 117. DB::Exception: Expected end of line: ... (INCORRECT_DATA)

原因:

CSV 中的 JSON 字段包含逗号和引号,干扰了 CSV 解析

时间格式 2026-03-29T10:00:00.000Z 不是 ClickHouse 默认格式

解决方案: 使用 TSV 格式替代 CSV

bash

将 CSV 转换为 TSV(逗号 → 制表符)

cat data.csv | sed 's/,/\t/g' | clickhouse client --query "INSERT INTO table FORMAT TSV"

14.Shell 脚本 while 循环只执行一次

错误信息: 循环内的命令导致循环提前退出。

原因: clickhouse-client 命令失败或 echo 干扰了循环变量。

解决方案: 放弃 while 循环,使用管道直接导入

bash

不推荐:while 循环逐行插入

while read line; do

clickhouse-client --query "INSERT ..."

done < file.csv

推荐:管道批量导入

cat file.csv | clickhouse-client --query "INSERT INTO table FORMAT TSV"

15. Paimon 表写入失败(表不存在)

错误信息:

text

Schema file not found in location paimon.default.ods_user_log. Please create table first.

解决方案: 先创建表,再写入数据

sql

CREATE TABLE IF NOT EXISTS paimon.default.ods_user_log (...) USING paimon;

或在 Python 脚本中自动创建:

python

if not spark.catalog.tableExists("paimon.default.ods_user_log"):

spark.sql("CREATE TABLE ...")

最后

成果截图:

各个节点对应脚本

1.kafka_to_paimon

javascript 复制代码
from pyspark.sql import SparkSession
from pyspark.sql.functions import col, lit, from_json, to_timestamp
from pyspark.sql.types import StructType, StructField, StringType, TimestampType
import sys
import os

bizdate = sys.argv[1] if len(sys.argv) > 1 else os.environ.get("bizdate", "2026-03-29")

spark = SparkSession.builder \
    .appName(f"KafkaToPaimon_Batch_{bizdate}") \
    .config("spark.sql.catalog.paimon", "org.apache.paimon.spark.SparkCatalog") \
    .config("spark.sql.catalog.paimon.warehouse", "hdfs://nameservice1/user/paimon/warehouse") \
    .config("spark.sql.catalog.paimon.metastore", "hive") \
    .config("spark.sql.catalog.paimon.uri", "thrift://ddp1:9083") \
    .config("spark.sql.extensions", "org.apache.paimon.spark.extensions.PaimonSparkSessionExtensions") \
    .getOrCreate()

print(f"开始处理业务日期: {bizdate}")

# 定义 schema
json_schema = StructType([
    StructField("user_id", StringType(), True),
    StructField("event_time", StringType(), True),
    StructField("event_type", StringType(), True),
    StructField("data", StringType(), True)
])

# Kafka 配置
kafka_bootstrap_servers = "ddp4:9092,ddp3:9092"
kafka_topic = "user_log_topic"

# 检查表是否存在,如果不存在则创建
print("检查表是否存在...")
try:
    # 尝试查询表
    spark.sql("SELECT 1 FROM paimon.default.ods_user_log LIMIT 1")
    print("表已存在")
except Exception:
    print("表不存在,正在创建...")
    create_sql = """
    CREATE TABLE paimon.default.ods_user_log (
        user_id STRING,
        event_time TIMESTAMP,
        event_type STRING,
        data STRING,
        dt STRING
    ) USING paimon
    PARTITIONED BY (dt)
    TBLPROPERTIES (
        'bucket' = '4',
        'file.format' = 'parquet'
    )
    """
    spark.sql(create_sql)
    print("表创建成功")

# 从 Kafka 读取数据
df = spark.read \
    .format("kafka") \
    .option("kafka.bootstrap.servers", kafka_bootstrap_servers) \
    .option("subscribe", kafka_topic) \
    .option("startingOffsets", "earliest") \
    .option("endingOffsets", "latest") \
    .load() \
    .selectExpr("CAST(value AS STRING) as json_str")

# 解析 JSON
parsed_df = df.select(
    from_json(col("json_str"), json_schema).alias("data")
).select(
    col("data.user_id"),
    to_timestamp(col("data.event_time")).alias("event_time"),
    col("data.event_type"),
    col("data.data"),
    lit(bizdate).alias("dt")
).filter(col("user_id").isNotNull())

count = parsed_df.count()
print(f"从 Kafka 读取到 {count} 条有效数据")

if count > 0:
    print("\n数据样例(前5条):")
    parsed_df.show(5, truncate=False)
    
    # 写入数据
    parsed_df.write \
        .format("paimon") \
        .mode("append") \
        .insertInto("paimon.default.ods_user_log")
    
    print(f"✅ 写入完成,共 {count} 条记录")
else:
    print("没有数据需要写入")

spark.stop()

2.paimon写入ck(实际是写入hdfs,忘记改名称)

javascript 复制代码
from pyspark.sql import SparkSession

bizdate = "2026-03-29"

spark = SparkSession.builder \
    .appName(f"PaimonToClickHouse_{bizdate}") \
    .config("spark.sql.catalog.paimon", "org.apache.paimon.spark.SparkCatalog") \
    .config("spark.sql.catalog.paimon.metastore", "hive") \
    .config("spark.sql.catalog.paimon.uri", "thrift://ddp1:9083") \
    .config("spark.sql.catalog.paimon.warehouse", "hdfs://nameservice1/user/paimon/warehouse") \
    .config("spark.sql.extensions", "org.apache.paimon.spark.extensions.PaimonSparkSessionExtensions") \
    .getOrCreate()

print("=" * 50)
print("Paimon → ClickHouse 导入")
print(f"业务日期: {bizdate}")
print("=" * 50)

# 读取数据
df = spark.sql(f"""
    SELECT user_id, event_time, event_type, data 
    FROM paimon.default.ods_user_log 
    WHERE dt = '{bizdate}'
""")

count = df.count()
print(f"[1/2] Paimon 表中数据量: {count}")

if count > 0:
    print("[2/2] 写入 ClickHouse...")
    
    # 保存为 CSV 到 HDFS
    output_path = f"/tmp/paimon_export_{bizdate}"
    df.coalesce(1).write.mode("overwrite").option("header", "false").csv(output_path)
    
    print(f"✅ 数据已导出到 HDFS: {output_path}")
    print(f"   请使用 clickhouse-client 导入数据")
else:
    print("⚠️ 没有数据需要导入")

spark.stop()

3.hdfs导入ck

javascript 复制代码
#!/bin/bash

# 环境变量
CH_CLIENT=/opt/datasophon/clickhouse/bin/clickhouse
HOST=ddp3
PORT=9000
USER=default
BIZDATE="2026-03-29"

echo "=========================================="
echo "Paimon → ClickHouse 数据导入"
echo "业务日期: ${BIZDATE}"
echo "=========================================="

# 1. 下载 CSV 文件
echo "[1/5] 下载 CSV 文件..."
hdfs dfs -get -f /tmp/paimon_export_${BIZDATE}/part-*.csv /tmp/paimon_data_${BIZDATE}.csv 2>/dev/null

if [ ! -f /tmp/paimon_data_${BIZDATE}.csv ]; then
    echo "❌ CSV 文件不存在"
    exit 1
fi

TOTAL_LINES=$(wc -l < /tmp/paimon_data_${BIZDATE}.csv)
echo "CSV 文件行数: ${TOTAL_LINES}"

# 2. 创建 ClickHouse 表(如果不存在)
echo "[2/5] 创建 ClickHouse 表(如果不存在)..."

${CH_CLIENT} client --host ${HOST} --port ${PORT} --user ${USER} --query "
CREATE TABLE IF NOT EXISTS default.user_log (
    user_id String,
    event_time String,
    event_type String,
    data String
) ENGINE = MergeTree()
ORDER BY user_id
" 2>/dev/null

# 3. 删除当前日期的旧数据(避免重复)
echo "[3/5] 删除当前日期的旧数据..."
${CH_CLIENT} client --host ${HOST} --port ${PORT} --user ${USER} --query "
ALTER TABLE default.user_log DELETE WHERE event_time LIKE '${BIZDATE}%'
" 2>/dev/null

# 4. 导入数据(使用 TSV 格式)
echo "[4/5] 导入数据到 ClickHouse..."

# 将 CSV 转换为 TSV(用制表符分隔)后导入
cat /tmp/paimon_data_${BIZDATE}.csv | sed 's/,/\t/g' | ${CH_CLIENT} client --host ${HOST} --port ${PORT} --user ${USER} --query "INSERT INTO default.user_log FORMAT TSV"

# 5. 验证导入结果
echo "[5/5] 验证导入结果..."
COUNT=$(${CH_CLIENT} client --host ${HOST} --port ${PORT} --user ${USER} --query "SELECT COUNT(*) FROM default.user_log WHERE event_time LIKE '${BIZDATE}%'" 2>/dev/null)
TOTAL=$(${CH_CLIENT} client --host ${HOST} --port ${PORT} --user ${USER} --query "SELECT COUNT(*) FROM default.user_log" 2>/dev/null)

echo "=========================================="
echo "✅ 导入完成!"
echo "   本次导入行数: ${TOTAL_LINES}"
echo "   当前日期数据量: ${COUNT}"
echo "   ClickHouse 总数据量: ${TOTAL}"
echo "=========================================="

# 显示数据样例
if [ "${TOTAL_LINES}" -gt 0 ]; then
    echo ""
    echo "新增数据样例:"
    ${CH_CLIENT} client --host ${HOST} --port ${PORT} --user ${USER} --query "SELECT * FROM default.user_log WHERE event_time LIKE '${BIZDATE}%' LIMIT 3" --format PrettyCompact
fi

# 清理临时文件
rm -f /tmp/paimon_data_${BIZDATE}.csv

echo ""
echo "=========================================="
echo "导入任务完成"
echo "=========================================="

如需沟通:lita2lz

相关推荐
rainy雨2 小时前
精益班组建设通过标准化作业解决现场管理混乱难题,推动精益班组建设落地
大数据·运维·数据挖掘·数据分析·精益工程
Ashley_Amanda2 小时前
UiPath完全指南:从入门到精通
大数据·人工智能
小荟荟2 小时前
全国数据资产新闻和报纸摘要联播 2026年3月22日 第25期
大数据·人工智能·数据挖掘
似水এ᭄往昔2 小时前
【Linux】--命令行参数和环境变量
linux·运维·服务器
linux修理工2 小时前
在Debian上安装桌面环境并启用远程登录
linux·运维·服务器
雷焰财经2 小时前
首都在线MaaS平台:打造企业级AI中枢,驱动游戏产业智变
大数据·人工智能·游戏
不愿透露姓名的大鹏2 小时前
Oracle Undo空间爆满急救指南(含在线切换+更优方案+避坑指南)
linux·运维·数据库·oracle
艾莉丝努力练剑2 小时前
【Linux线程】Linux系统多线程(三):Linux线程 VS 进程,线程控制
java·linux·运维·服务器·c++·学习·ubuntu
浮芷.2 小时前
Flutter 框架跨平台鸿蒙开发 - 智能家电故障诊断应用
运维·服务器·科技·flutter·华为·harmonyos·鸿蒙