实战:使用Shell脚本,动态生成高效Flink SQL!

354 阅读4分钟

使用Shell脚本,动态生成高效Flink SQL!

使用 Shell 脚本来生成 Flink SQL 是一种灵活的方法,可以根据需要动态生成 SQL 语句。以下是一个示例,说明如何使用 Shell 脚本生成 Flink SQL 脚本。

作用

  1. 动态生成SQL

    • 根据输入参数(如Kafka主题、数据源和目的地等),动态生成适合当前环境的Flink SQL脚本。
    • 简化传统手写SQL的复杂性,将SQL的生成过程自动化。
  2. 参数化控制

    • 通过Shell脚本接受命令行参数,可以灵活控制生成的SQL脚本内容。
    • 支持不同的配置组合,如不同的Kafka主题或数据库表。
  3. 自动化操作

    • 可以集成到自动化部署管道中,如CI/CD流程中,通过脚本自动生成SQL并部署到Flink集群。
  4. 提高开发效率

    • 通过脚本生成SQL大幅减少手工编写SQL的时间和出错概率。
    • 便于维护和更新,只需修改脚本即可适配新的需求。

好处

  1. 灵活性和可重用性

    • Shell脚本可以根据需要重复使用和调整,适应不同的环境和配置要求。
    • 将复杂的SQL构建逻辑封装在脚本中,使其更易于管理和重用。
  2. 简化配置管理

    • 将配置参数化,通过命令行或配置文件传递,避免在多个SQL文件中重复修改配置。
    • 提供统一的入口,对配置进行集中管理。
  3. 提高安全性

    • 避免在源码中硬编码敏感信息(如数据库密码),可以通过安全的方式传入脚本参数。
    • 减少人为操作失误的风险,确保配置的一致性和正确性。
  4. 可扩展性

    • 可以根据业务需求扩展脚本功能,如新增数据源类型、目标存储等。
    • 支持复杂的生成逻辑,通过条件判断和循环生成多段SQL。
  5. 便于调试和测试

    • 通过脚本快速生成不同配置下的SQL,有助于在开发过程中进行调试和测试。
    • 可以轻松模拟生产环境中不同参数组合下的SQL运行情况。

示例

假设你需要生成一个 Flink SQL 脚本,用于从一个 Kafka 来源读取数据并写入到一个 MySQL 数据库。可以具体化你的需求和参数到 Shell 脚本中。

Shell 脚本内容

将以下内容保存为 generate_flink_sql.sh

bash
#!/bin/bash

# Kafka 配置参数
KAFKA_TOPIC="${1:-default_topic}"
KAFKA_BOOTSTRAP_SERVERS="${2:-localhost:9092}"
KAFKA_GROUP_ID="${3:-default_group}"

# MySQL 配置参数
MYSQL_HOST="${4:-localhost}"
MYSQL_PORT="${5:-3306}"
MYSQL_DATABASE="${6:-default_db}"
MYSQL_TABLE="${7:-default_table}"
MYSQL_USER="${8:-user}"
MYSQL_PASSWORD="${9:-password}"

# 输出的 SQL 文件路径
OUTPUT_SQL_FILE=${10:-flink_job.sql}

# 生成的 Flink SQL 脚本内容
FLINK_SQL=$(cat << EOF
CREATE TABLE kafka_source (
  id STRING,
  name STRING,
  age INT
) WITH (
  'connector' = 'kafka',
  'topic' = '${KAFKA_TOPIC}',
  'properties.bootstrap.servers' = '${KAFKA_BOOTSTRAP_SERVERS}',
  'properties.group.id' = '${KAFKA_GROUP_ID}',
  'format' = 'json', 
  'scan.startup.mode' = 'earliest-offset'
);

CREATE TABLE mysql_sink (
  id STRING,
  name STRING,
  age INT
) WITH (
  'connector' = 'jdbc',
  'url' = 'jdbc:mysql://${MYSQL_HOST}:${MYSQL_PORT}/${MYSQL_DATABASE}',
  'table-name' = '${MYSQL_TABLE}',
  'username' = '${MYSQL_USER}',
  'password' = '${MYSQL_PASSWORD}'
);

INSERT INTO mysql_sink
SELECT
  id,
  name,
  age
FROM kafka_source;
EOF
)

# 将生成的 Flink SQL 脚本写入指定文件
echo "${FLINK_SQL}" > "${OUTPUT_SQL_FILE}"

echo "Flink SQL 脚本生成完成,保存在 ${OUTPUT_SQL_FILE}"

运行脚本

你可以通过命令行传递参数来运行这个脚本。如果没有传递参数,将使用默认值。

sh
chmod +x generate_flink_sql.sh

# 运行脚本并传递参数
./generate_flink_sql.sh my_topic localhost:9092 my_group localhost 3306 mydb mytable myuser mypassword my_flink_job.sql

参数说明

  • KAFKA_TOPIC:Kafka 主题名称
  • KAFKA_BOOTSTRAP_SERVERS:Kafka 服务器地址
  • KAFKA_GROUP_ID:Kafka 消费者组 ID
  • MYSQL_HOST:MySQL 服务器地址
  • MYSQL_PORT:MySQL 端口
  • MYSQL_DATABASE:MySQL 数据库名称
  • MYSQL_TABLE:MySQL 表名
  • MYSQL_USER:MySQL 用户
  • MYSQL_PASSWORD:MySQL 密码
  • OUTPUT_SQL_FILE:生成的 Flink SQL 文件名

简化版的调用示例

如果你只需要一个简化版的调用,例如只传入 Kafka 主题和 MySQL 表名:

sh
./generate_flink_sql.sh my_topic localhost:9092 my_group localhost 3306 mydb mytable myuser mypassword my_flink_job.sql

这将使用默认的其他参数生成相应的 SQL 文件。

使用 Shell 脚本来生成 Flink SQL 文件是非常灵活和强大的,可以根据需要进一步自定义。如果有更多需求或复杂的 SQL 生成逻辑,也可以通过增加脚本的复杂度来实现。

真实完整shell脚本

这个脚本的目的是从多个输入文件中读取Kafka主题和Hive表的信息,然后生成一系列的Flink SQL脚本来创建Kafka和Hudi表,并将数据从Kafka插入到Hudi表中。脚本中使用了Bash的循环、文件I/O操作和字符串操作。

i=0
for topic in `cat topics.in`;
do
i=$((i+1))
hudiTab=`sed -n '$i p' hudi_table.sql`
echo "SET execution.checkpointing.interval=60000;
SET jobmanager.memory.process.size=2048m;
SET taskmanager.memory.process.size=8192m;

CREATE TABLE  $topic (
\`table\` varchar,
        op_type varchar,
        op_ts varchar,
        current_ts varchar,
        pos varchar,
        \`before\` ROW (" > ${topic}.out

#       echo "" >>${topic}.out
        echo "" > col.tmp
        for col in `cat cols/${topic}.sql`;
        do
        echo "\`$col\` varchar," >>col.tmp
        done
sed -i '$ s/.$//' col.tmp
`cat col.tmp >> ${topic}.out`

echo "),
        \`after\` ROW (" >> ${topic}.out
#       for col in `cat cols/${topic}.sql`;
#       do
#       echo "\`$col\` varchar," >> ${topic}.out
#       done
`cat col.tmp >> ${topic}.out`
echo ")
) WITH (
  'connector' = 'kafka',
  'topic' = '$topic',
  'properties.bootstrap.servers' = '10.4.81.4:9092',
  'properties.group.id' = 'sql_01',
  'scan.startup.mode' = 'earliest-offset',
  'format' = 'json',
  'json.fail-on-missing-field' = 'false',
 'json.ignore-parse-errors' = 'true'
);">>  ${topic}.out
echo "CREATE TABLE $hudiTab (
op_type varchar,
        op_ts varchar,
        current_ts varchar,
        pos varchar," >>  ${topic}.out
#       for col in `cat cols/${topic}.sql`;
#       do
#       echo "\`$col\` varchar," >> ${topic}.out
#       done
`cat col.tmp >> ${topic}.out`
echo ") WITH (
'connector' = 'hudi',
'path' = '/ns1/hudi/dws/db_dws_crm/$hudiTab',
'hoodie.table.name' = '$hudiTab',
'table.type' = 'MERGE_ON_READ',
'hoodie.datasource.write.recordkey.field' = 'current_ts',
'write.precombine.field' = 'op_ts',
'index.type' = 'BUCKET',
'write.operation' = 'upsert',
'write.tasks' = '2',
'hive_sync.enabled' = 'true',
'hive_sync.mode' = 'hms',
'hive_sync.matastore.uris' = 'thrift://ddp-js6dn-c027:9083,thrift://ddp-js6dn-c028:9083',
'hive_sync.conf.dir' = '/opt/TDP/hive/conf','hive_sync.db' = 'default',
'hive_sync.table' = '$hudiTab',
'hive_sync.kerberos.krb5.conf' = '/data/dataos/hadoop-cluster/krb5.conf',
'hive_sync.kerberos.principal' = 'zt_test2@GROUPB.HADOOP.CN',
'hive_sync.kerberos.keytab.file' = '/data/dataos/hadoop-cluster/zt_test2.keytab',
'hoodie.bucket.index.num.buckets' = '2',
'compaction.tasks' = '2',
'compaction.max_memory' = '2048'
);"  >> ${topic}.out
echo "INSERT INTO $hudiTab
SELECT a.op_ts as op_ts,
a.op_type as op_type,
        a.current_ts as current_ts,
        a.pos as pos," >> ${topic}.out

        for col in `cat cols/${topic}.sql`;
        do
        echo "case when a.op_type = 'D' then a.before.$col else a.after.$col end as $col ," >> ${topic}.out
        done
#echo "" >> ${topic}.out
echo "from ${topic} a where a.\`table\`='${topic}_hudi' and a.op_type is not null and a.before is not null and a.after is not null;"  >> ${topic}.out
done

修改和优化包括:

  1. 变量引用的正确处理:使用$(...)而不是反引号`...`
  2. 避免文件覆盖:在使用重定向操作符时注意避免无意间覆盖文件。
  3. 重用列信息:在生成列定义时,将结果输出到临时文件col.tmp中,并在生成表创建脚本时多次使用。
  4. 去除末尾多余的逗号:在col.tmp和最终输出文件中去除多余的逗号。
  5. 注释和结构优化:添加了注释并优化了代码结构,使其更易于理解和维护。

可以根据实际需要进一步调整脚本。特别是需要注意集群配置、Flink和Hudi的版本兼容性以及Kafka和Hudi表的具体设置。

修改后的shell

i=0
for topic in $(cat topics.in); do

    i=$((i+1))
    hudiTab=$(sed -n "${i}p" hudi_table.sql)

    echo "SET execution.checkpointing.interval=60000;
SET jobmanager.memory.process.size=2048m;
SET taskmanager.memory.process.size=8192m;

CREATE TABLE $topic (
\`table\` varchar,
op_type varchar,
op_ts varchar,
current_ts varchar,
pos varchar,
\`before\` ROW (" > ${topic}.out

    > col.tmp

    for col in $(cat cols/${topic}.sql); do
        echo "\`$col\` varchar," >> col.tmp
    done

    # Remove the last comma from col.tmp
    sed -i '$ s/,$//' col.tmp

    cat col.tmp >> ${topic}.out

    echo "),
\`after\` ROW (" >> ${topic}.out

    cat col.tmp >> ${topic}.out

    echo ")
) WITH (
  'connector' = 'kafka',
  'topic' = '$topic',
  'properties.bootstrap.servers' = '10.4.81.4:9092',
  'properties.group.id' = 'sql_01',
  'scan.startup.mode' = 'earliest-offset',
  'format' = 'json',
  'json.fail-on-missing-field' = 'false',
 'json.ignore-parse-errors' = 'true'
);" >> ${topic}.out

    echo "CREATE TABLE $hudiTab (
op_type varchar,
op_ts varchar,
current_ts varchar,
pos varchar," >> ${topic}.out

    cat col.tmp >> ${topic}.out

    echo ") WITH (
'connector' = 'hudi',
'path' = '/ns1/hudi/dws/db_dws_crm/$hudiTab',
'hoodie.table.name' = '$hudiTab',
'table.type' = 'MERGE_ON_READ',
'hoodie.datasource.write.recordkey.field' = 'current_ts',
'write.precombine.field' = 'op_ts',
'index.type' = 'BUCKET',
'write.operation' = 'upsert',
'write.tasks' = '2',
'hive_sync.enabled' = 'true',
'hive_sync.mode' = 'hms',
'hive_sync.matastore.uris' = 'thrift://ddp-js6dn-c027:9083,thrift://ddp-js6dn-c028:9083',
'hive_sync.conf.dir' = '/opt/TDP/hive/conf',
'hive_sync.db' = 'default',
'hive_sync.table' = '$hudiTab',
'hive_sync.kerberos.krb5.conf' = '/data/dataos/hadoop-cluster/krb5.conf',
'hive_sync.kerberos.principal' = 'zt_test2@GROUPB.HADOOP.CN',
'hive_sync.kerberos.keytab.file' = '/data/dataos/hadoop-cluster/zt_test2.keytab',
'hoodie.bucket.index.num.buckets' = '2',
'compaction.tasks' = '2',
'compaction.max_memory' = '2048'
);" >> ${topic}.out

    echo "INSERT INTO $hudiTab
SELECT a.op_ts as op_ts,
a.op_type as op_type,
a.current_ts as current_ts,
a.pos as pos," >> ${topic}.out

    for col in $(cat cols/${topic}.sql); do
        echo "case when a.op_type = 'D' then a.before.$col else a.after.$col end as $col," >> ${topic}.out
    done

    # Remove the last comma and add the final SQL part
    sed -i '$ s/,$//' ${topic}.out

    echo "from ${topic} a where a.\`table\`='${topic}_hudi' and a.op_type is not null and a.before is not null and a.after is not null;" >> ${topic}.out

done

# Clean up temporary file
rm col.tmp

总结

使用Shell脚本动态生成高效Flink SQL,不仅能显著提高开发和运维效率,还能提升系统的灵活性和可维护性。同时,通过参数化和自动化的方式,增强了配置管理的安全性和一致性。这一方法非常适合需要频繁调整配置和快速响应业务需求的场景。