使用Shell脚本,动态生成高效Flink SQL!
使用 Shell 脚本来生成 Flink SQL 是一种灵活的方法,可以根据需要动态生成 SQL 语句。以下是一个示例,说明如何使用 Shell 脚本生成 Flink SQL 脚本。
作用
-
动态生成SQL:
- 根据输入参数(如Kafka主题、数据源和目的地等),动态生成适合当前环境的Flink SQL脚本。
- 简化传统手写SQL的复杂性,将SQL的生成过程自动化。
-
参数化控制:
- 通过Shell脚本接受命令行参数,可以灵活控制生成的SQL脚本内容。
- 支持不同的配置组合,如不同的Kafka主题或数据库表。
-
自动化操作:
- 可以集成到自动化部署管道中,如CI/CD流程中,通过脚本自动生成SQL并部署到Flink集群。
-
提高开发效率:
- 通过脚本生成SQL大幅减少手工编写SQL的时间和出错概率。
- 便于维护和更新,只需修改脚本即可适配新的需求。
好处
-
灵活性和可重用性:
- Shell脚本可以根据需要重复使用和调整,适应不同的环境和配置要求。
- 将复杂的SQL构建逻辑封装在脚本中,使其更易于管理和重用。
-
简化配置管理:
- 将配置参数化,通过命令行或配置文件传递,避免在多个SQL文件中重复修改配置。
- 提供统一的入口,对配置进行集中管理。
-
提高安全性:
- 避免在源码中硬编码敏感信息(如数据库密码),可以通过安全的方式传入脚本参数。
- 减少人为操作失误的风险,确保配置的一致性和正确性。
-
可扩展性:
- 可以根据业务需求扩展脚本功能,如新增数据源类型、目标存储等。
- 支持复杂的生成逻辑,通过条件判断和循环生成多段SQL。
-
便于调试和测试:
- 通过脚本快速生成不同配置下的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 消费者组 IDMYSQL_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
修改和优化包括:
- 变量引用的正确处理:使用
$(...)而不是反引号`...`。 - 避免文件覆盖:在使用重定向操作符时注意避免无意间覆盖文件。
- 重用列信息:在生成列定义时,将结果输出到临时文件
col.tmp中,并在生成表创建脚本时多次使用。 - 去除末尾多余的逗号:在
col.tmp和最终输出文件中去除多余的逗号。 - 注释和结构优化:添加了注释并优化了代码结构,使其更易于理解和维护。
可以根据实际需要进一步调整脚本。特别是需要注意集群配置、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,不仅能显著提高开发和运维效率,还能提升系统的灵活性和可维护性。同时,通过参数化和自动化的方式,增强了配置管理的安全性和一致性。这一方法非常适合需要频繁调整配置和快速响应业务需求的场景。