资讯动态

从Glue Data Catalog到MySQL:AWS Glue ETL数据同步实战

发布时间:2026/9/7 19:53:12 来源:尧图企业网站定制
有段时间我一直在处理一类需求公司的数据湖已经搭起来了S3上躺着每天的订单、用户、库存明细Glue Crawler每天按时跑Data Catalog里几十张表的元数据清清楚楚。看上去一切都很完美直到业务方说了一句——这些表能不能导到我们的MySQL库里后台系统查询要用。这个需求在数据平台上太常见了。Glue Data Catalog本质上只是一个元数据仓库它告诉你有哪些表、字段是什么、数据存在S3的哪个位置但业务系统没法直接连S3查数据也不该用Athena顶着生产库的压力去查。于是从Glue Data Catalog把数据搬到MySQL就成了一个绕不开的ETL任务。我最终用的是纯Glue方案用Glue ETL作业读取Data Catalog中注册的表再通过JDBC写入MySQL全程托管、无服务器不需要自己维护Spark集群。这篇东西我会把这条链路的原理、脚本、调度、增量设计和踩坑过程都摊开来讲内容包括完整可复现的Glue作业脚本、MySQL建表DDL以及在中国区AWS环境下的实测经验。老手可以重点看后面的调优和排错部分新手建议从头到尾过一遍应该能省下不少试错时间。1. 先说清楚Data Catalog里的数据为什么非要搬到MySQL1.1 Data Catalog的本质它只是表目录不是数据库很多人第一次接触Glue Data Catalog容易有个误解觉得它就像MySQL一样里面存着数据。不是的。Data Catalog存的只是元数据——表名、字段名、字段类型、分区信息、数据在S3上的路径。你可以把它理解成图书馆的检索卡片卡片上写得再详细书本身还是放在书架上要看内容必须去书架取。这一点决定了从Data Catalog搬数据这个需求的本质我们真正的读取目标是Data Catalog指向的S3文件Data Catalog在这里扮演的是Schema提供者和访问入口的角色。Glue作业通过from_catalog接口拿到表结构然后Spark按这个结构去解析对应S3路径上的文件。理解这个本质很重要因为后面很多问题——比如为什么一张表同步过去字段类型变了、为什么数据量和S3上文件大小对不上——根子都在这层元数据与实际文件的对应关系上。我这次要同步的表是通过Glue Crawler自动识别生成的。Crawler在识别时对S3文件里的每一个字段做了类型推断比如把数值识别成了bigint或double时间字段识别成timestamp。这些推断出来的类型和MySQL里最终需要的类型大概率不是一一对应的所以元数据到物理表结构的转换是这条链路里第一个要认真处理的点。1.2 什么业务场景会催生这种湖到库的同步需求我自己梳理了一下真正会触发这个需求的场景其实就那么几类业务后台查询。运营后台、客服系统、管理报表它们需要的是单条查询快、有索引、事务可控的关系型数据库。让它们直接查S3不现实Athena的查询延迟和并发也不适合作为在线系统的数据源。下游系统对接。比如ERP、CRM是自建的内部约定好了通过MySQL做数据交换。数据湖里的清洗结果、分析明细最终要通过MySQL接口喂给它们。数据结构变更后的数据回补。Data Catalog中的表结构因为源文件格式变化发生了演进下游MySQL需要跟着调整并重新同步。数据备份与归档需求。虽然S3本身有版本控制但有些场景要求数据库里有物理数据这种情况下也需要做一份MySQL副本。这次的订单明细表就属于第一种加第二种的混合业务后台要看近三个月的订单状态同时有个CRM系统要按订单号关联客户信息。两个下游都只认MySQL于是Glue版的ETL迁移方案就被推到了台面上。2. 链路拆解数据从Data Catalog流到MySQL到底经历了什么2.1 Glue ETL读取Data Catalog的方式create_dynamic_frame.from_catalog以Spark为基础Glue ETL作业读取Data Catalog的标准姿势是这样dynamic_frame glueContext.create_dynamic_frame.from_catalog( databasedata_lake_db, table_nameods_order_detail, transformation_ctxdatasource0 )这一行代码背后做的事情比表面看起来多得多。Glue会先去Data Catalog拿到表的Schema和分区信息然后根据SerDe配置和Location去S3上找到对应路径用Spark的Data Source解析S3里的文件JSON、Parquet、CSV都可以最终生成一个DynamicFrame。如果你用Spark原生的spark.read去读S3也不是不行但那样你就丢了Glue的很多优势比如自动使用Data Catalog的Schema、自动处理分区裁剪、以及后面要重点讲的Job Bookmark增量机制。所以只要是从Data Catalog出发的任务我建议都走glueContext的API而不是绕回原生Spark。为什么选择DynamicFrame而不是直接转成DataFrameDynamicFrame比DataFrame更贴合数据湖场景支持解析JSON嵌套结构、自动处理脏数据还带有Schema演化能力。如果S3里是Parquet文件带着嵌套结构用DynamicFrame能少写很多解析代码。等到要写MySQL之前再转成DataFrame用Spark SQL构建目标表的字段投影。2.2 从DynamicFrame到MySQLJDBC写入的两种写法数据读进来之后写MySQL有两条路。第一种用Glue封装好的write_dynamic_frame.from_jdbc_confglueContext.write_dynamic_frame.from_jdbc_conf( framedynamic_frame, catalog_connectionmysql-jdbc-conn, connection_options{ database: business_db, dbtable: ods_order_detail, batchsize: 1000 }, transformation_ctxdatasink0 )第二种Spark原生的jdbc writerdynamic_frame.toDF().write \ .mode(append) \ .format(jdbc) \ .option(url, jdbc:mysql://your-mysql-host:3306/business_db) \ .option(dbtable, ods_order_detail) \ .option(user, glue_user) \ .option(password, ******) \ .option(driver, com.mysql.cj.jdbc.Driver) \ .option(batchsize, 1000) \ .save()两种写法底层都是走JDBC核心区别在于用Glue封装的方法连接信息可以复用你在Glue控制台里配置好的Connection密码不暴露在代码里安全性和可维护性都好很多。我用的是第一种这里也推荐你这么做——尤其是多人协作的团队不要把数据库密码直接写进脚本。需要补充的是from_jdbc_conf里的catalog_connection参数指向的是你在Glue控制台创建的Connection这个Connection的底层是一个网络配置加认证信息。它可以是一个VPC子网和Security Group的组合配合存储在Secrets Manager里的用户名密码也可以在Connection里直接填JDBC URL。数据安全方面我强烈建议用Secrets Manager而不是明文存密码这样脚本即便被导出也不会泄露凭据。2.3 谁在调度、谁在监控职责划分一条完整的数据管道不只是读加写这么简单。Glue ETL作业跑起来之后还需要触发机制和失败告警。我这次用的是EventBridge定时触发每天凌晨2点跑一次增量。Glue支持在控制台配Trigger也可以直接在EventBridge里创建Cron表达式指向Glue Job。失败告警走的是Glue Job的Job Run状态变化事件发到SNS再转邮件或钉钉Webhook。监控上有个容易被忽略的点Glue本身的Console只显示作业状态和CloudWatch Metrics如果你是靠肉眼盯状态迟早会漏掉问题。我习惯把每次Job Run的关键日志读取行数、写入行数、耗时通过CloudWatch Logs订阅过滤出来汇总到一个日志组再定期检查。CloudWatch里那几个指标也要盯一下glue.driver.aggregate.bytesRead、glue.driver.aggregate.recordsRead、glue.driver.aggregate.elapsedTime这些能帮你快速判断任务是不是真的读到了预期数据量。如果某天recordsRead突然掉了几个数量级大概率是源文件路径或者分区出了问题这种问题比作业直接报错更隐蔽。3. 实操全流程一个能跑通的Glue作业是怎么写出来的3.1 前置准备IAM权限、网络连接、Driver依赖这一步最容易出问题我把清单和理由都列一下。IAM角色权限。Glue作业运行时会扮演一个Service Role。这个角色至少需要glue的Job运行相关权限实际操作中可以最小化为glue:StartJobRun、glue:GetJob、glue:GetJobRun等。S3读写权限读取源数据所在的Bucket同时读写Glue作业的临时目录通常是aws-glue-temporary-xxx这样的默认桶。SecretsManager读取权限如果你按前面说的方式用SecretsManager存MySQL密码。CloudWatch Logs写入权限Glue Driver、Core、Error三类日志要写到CloudWatch权限不全会看到作业莫名其妙失败。网络连接。Glue作业如果跑在默认的公共网络下是访问不到你VPC里MySQL的。必须先在Glue控制台创建ConnectionVPC选MySQL所在的VPC子网和Security Group都要选对。Security Group规则上需要放行到MySQL 3306的入站流量来源可以限定为Glue作业所在子网的CIDR。如果MySQL在RDS里注意RDS的SG也要允许来自Glue所选SG的流量。二者之间的信任关系是双向的只开一边都会连不上。Driver依赖。Glue的Spark运行时内置的JDBC驱动并不包含MySQL Connector/J这是高频踩坑点。解决方式是在Job创建时指定一个存放驱动JAR包的S3路径Glue会把它加载进classpath。我建议用mysql-connector-java-8.0.x.jar注意8.0版本类名是com.mysql.cj.jdbc.Driver老版本的类名是com.mysql.jdbc.Driver两者不要混用。具体配置是在Job参数里加--extra-jars s3://etl-assets/jars/mysql-connector-java-8.0.29.jar如果是中国区环境这个S3桶要在同一个区域跨区拉JAR会有延迟甚至失败。3.2 核心脚本完整可复现的代码下面这个脚本是我在生产环境跑过的完整版本做了脱敏处理。整体逻辑是读取Data Catalog表过滤分区投影字段类型转换最后写入MySQL。import sys from awsglue.transforms import * from awsglue.utils import getResolvedOptions from pyspark.context import SparkContext from awsglue.context import GlueContext from awsglue.job import Job from pyspark.sql.functions import col, when, lit args getResolvedOptions(sys.argv, [JOB_NAME, TARGET_DATE, DATABASE, TABLE_NAME]) sc SparkContext() glueContext GlueContext(sc) spark glueContext.spark_session job Job(glueContext) job.init(args[JOB_NAME], args) target_date args[TARGET_DATE] # 1. 从Data Catalog读取只取目标分区 dynamic_frame glueContext.create_dynamic_frame.from_catalog( databaseargs[DATABASE], table_nameargs[TABLE_NAME], push_down_predicatefdt {target_date}, transformation_ctxdatasource0 ) # 2. DynamicFrame转DataFrame做字段投影、空值和类型清洗 df dynamic_frame.toDF() \ .select( col(order_id).cast(string), col(customer_id).cast(string), col(order_status).cast(string), when(col(pay_amount).isNull(), lit(0)).otherwise(col(pay_amount)).cast(decimal(18,2)).alias(pay_amount), col(order_time).cast(timestamp), col(dt) ) # 3. 写出到MySQL df.write \ .mode(append) \ .format(jdbc) \ .option(url, jdbc:mysql://your-rds-host:3306/business_db?useSSLfalsecharacterEncodingutf8serverTimezoneAsia/ShanghairewriteBatchedStatementstrue) .option(dbtable, ods_order_detail) \ .option(user, glue_user) \ .option(password, ******) \ .option(driver, com.mysql.cj.jdbc.Driver) \ .option(batchsize, 2000) \ .save() job.commit()这里有几个点我要单独解释。为什么用push_down_predicate而不是事后filter因为这个谓词会被下推到元数据层Glue会先定位到Data Catalog里dt等于目标值对应的分区只加载该分区的文件。如果整张表有几个TB而目标日期只有几GB这个差异就是几十倍的执行效率差距。写代码的时候务必要把分区过滤做在读取阶段而不是读完全部数据再filter。为什么在Spark侧先cast因为源S3数据里pay_amount可能是string类型直接写MySQL会遇到类型不兼容。在Spark侧统一做好类型映射MySQL收到什么就是什么避免后期在数据库里做隐式转换或者写入报错。另外pay_amount为空的情况也要处理S3上的Parquet文件里NULL值很正常MySQL那边如果字段是NOT NULL就会报错这里用when函数兜底给个0是常见的处理方式。为什么连接串里要加characterEncodingutf8MySQL默认连接字符集在部分版本上是latin1中文数据写进去就变成乱码。如果不显式声明装好的订单备注全是????后面运维同事查数据的时候会怀疑人生的。serverTimezoneAsia/Shanghai是给Connector/J 8.x用的不指定的话驱动会用JVM的默认时区Glue这边默认UTC和北京时间差8小时订单时间全部对不上。rewriteBatchedStatementstrue的作用是让驱动把多条insert合并成一条批量insert语句写入性能提升非常明显我在同样的数据量下对比过加上这个参数后写入时间能缩短一半以上。3.3 从ODS层视角看MySQL侧的DDL设计话题稍微展开一点。在很多公司这种从湖里同步出来的明细表就是典型的ODS操作数据存储层——数据原样落地基本不做业务逻辑加工只做最小限度的清洗。因此MySQL这边不是简单建张表就行对着一张每天要写入几百万行的增量表我一般这么设计CREATE TABLE ods_order_detail ( order_id varchar(64) NOT NULL, customer_id varchar(64) DEFAULT NULL, order_status varchar(32) DEFAULT NULL, pay_amount decimal(18,2) DEFAULT NULL, order_time datetime DEFAULT NULL, dt date NOT NULL, etl_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (order_id, dt), KEY idx_customer_id (customer_id), KEY idx_order_time (order_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;几个设计意图主键用order_id加dt组合既能保证幂等又方便按天删除重跑。dt字段在源数据里可能是string比如2024-05-06MySQL里建date类型写入时会自动转换前提是Spark侧已经cast成date或者字符串格式统一。etl_time这个字段是给数据排查用的记录每行数据是哪个批次写入的后面数据对不上账时能溯源。字符集用utf8mb4而不是utf8如果业务里有emoji或者生僻字utf8在MySQL里其实是utf8mb3存不了4字节字符插入直接报错。关于幂等写入还有一个更讲究的做法如果你们的业务允许每天整表替换可以先在脚本里执行一条delete删掉目标日期的数据再append写入。我在生产环境就是这么处理的每个批次写入前用单独的JDBC连接先执行delete from ods_order_detail where dt 目标日期然后再由Glue写入。这样即使上游数据修正后重跑同一天的批次也不会出现重复数据。想用Glue的from_jdbc_conf时可以通过preaction参数在写入前执行同样的删除逻辑。4. 增量同步与任务编排让管道每天自动跑、不多不重4.1 分区裁剪与跑批日期的传递增量同步的第一步是确定今天要同步哪一天的数据。这个日期怎么传给Glue作业我的做法是在Job参数里定义一个TARGET_DATE由上游调度把日期作为参数传进来。如果定时任务每天凌晨两点跑调度日期减一天就是目标日期如果是补数场景调度会传具体的日期。这样一套作业既能跑日调度也能跑补数不需要为了补数单独写脚本。这里有个容易踩的坑Glue的Job参数在Python脚本里是通过--keyvalue的方式传入的在Console的Job details里配置时参数名要写成--TARGET_DATE然后getResolvedOptions时用不带横杠的TARGET_DATE。如果这两边对不上脚本会直接报错而且报错信息经常很不直观。我在早期就栽过一回明明脚本写对了就是起不来后来发现是参数名前后的横杠数不对。4.2 Job Bookmark到底能不能用于这个场景Glue的Job Bookmark功能专门用来做增量读取它会记住上一次任务读到S3的位置下次从断点继续。但这里有个重要的边界Job Bookmark只对S3源有效对JDBC源比如从RDS读基本无效。我们要做的是从S3读、写MySQL这个场景Bookmark是可以用的前提是源表有确定的增量语义。但我在实际项目中对增量的态度一直是优先相信业务分区字段而不是依赖Bookmark。原因有二。第一Bookmark的增量判断基于文件的最后修改时间或事件时间如果上游文件被覆盖修改比如某天数据修正后重新上传覆盖Bookmark可能识别不到变化导致漏数据。第二Bookmark的启停由Job参数控制一旦你改了作业的source路径或表结构Bookmark的行为可能变得不可控排查起来很麻烦。所以我的方案是源表按日期分区调度明确传目标日期用push_down_predicate精确裁剪。这相当于用元数据层面的分区代替了运行时的状态记忆简单、可控、可解释。当然如果你们的源数据根本没做分区那Job Bookmark确实是一个值得考虑的兜底方案但并发和重试时的行为要提前测试好。4.3 补数、重跑与告警数据管道最怕的是补数。某天凌晨任务挂了早上业务问昨天的数怎么没到这时候要做的不是手工跑一次而是让系统具备重跑指定日期的能力。我在EventBridge之外单独留了一条补数通道平时以JSON文件或者一个简单的内部后台接口把TARGET_DATE传给同一个Glue作业。这个作业的脚本不需要任何修改因为日期本来就是参数化的。唯一的注意点是MySQL侧的幂等处理这一步做不好补数就会补出重复数据——就是前面说的写前先删目标日期。告警方面Glue的失败事件结构里包含了jobName和jobRunIdSNS主题收到之后转给Webhook。我习惯再额外加一层数据量级校验作业结束前对比这次实际写入MySQL的行数和源分区文件的行数偏差超过1%就发一条WARNING级别告警。这个逻辑在脚本里用count()对比即可虽然会多花几秒但能挡住一批作业成功但数据不对的事故。国内很多团队用的是Airflow或者XXL-JOB这类调度平台其实也可以直接调AWS的API去触发Glue作业把Glue当成一个远程执行引擎来用。Glue这边不用关心谁调的它只要保证作业幂等、日期参数化谁来触发都行。5. 踩坑记录驱动、类型、超时、并发这些事5.1 JDBC驱动版本和连接URL的坑Glue运行时自带的Spark版本如果较新比如Glue 3.0以上对应Spark 3.1JDBC连接必须用8.0版本的Connector/J而且连接URL里的时区参数需要显式指定。我们遇到过连接能建立但时间偏差8小时的问题就是因为没有加serverTimezoneAsia/Shanghai。在中国区跑任务时区问题比海外区更明显因为默认UTC和我们业务时区差8小时订单时间全部对不上。连接串的最终形态刚才已经给出这里再复制一遍方便直接抄jdbc:mysql://your-rds-host:3306/business_db?useSSLfalsecharacterEncodingutf8serverTimezoneAsia/ShanghairewriteBatchedStatementstrue关于SSL如果你没有强制要求加密useSSLfalse能省掉一次TLS握手对单批次几千上万条insert来说整体耗时能少不少。如果公司的安全策略要求必须加密那就把证书配置好再开别图省事直接关。5.2 类型映射与数据内容问题这是最容易让新手懵的地方。我在同步过程中遇到过几类典型问题源字段是decimal(20,6)目标MySQL字段建成了decimal(18,2)Spark写入时直接抛异常因为精度放不下。建表之前一定要先检查数据范围而不是拍脑袋定精度。源字段包含非法日期比如2024-02-30Spark cast成timestamp会变成null。如果下游表字段不允许null写入会失败。解决办法是在ETL脚本里对这种字段做统一清洗比如所有日期字段先to_date非法值给一个默认值或者单独打标。string字段超长。S3里的description字段可能长达几万字符MySQL的varchar(255)根本装不下。要么在Spark侧对字段做截断要么在MySQL侧改用TEXT类型。这个属于表和源结构天生不匹配必须提前在DDL和脚本里达成一致。NULL值策略。源文件里空字符串和NULL经常混用MySQL里如果字段设置了NOT NULL空字符串可以写进去NULL会报错。建议在Spark侧统一用when(col(xxx) , lit(null))做转换保证语义一致。我的建议是第一次全量同步前先在MySQL里建一个临时表结构完全照搬目标表然后让Glue往临时表写一批数据试试。跑通之后再改成正式表。这个试写步骤能帮你把所有类型、长度、NULL的问题提前暴露掉而不是等全量任务跑了一半才报错。5.3 写入性能与并发配置Glue作业默认分配的DPU数量对Spark执行引擎来说并不等于同数量的并发写入MySQL连接。JDBC写MySQL的性能瓶颈往往不在Spark侧而在MySQL实例的并发能力和磁盘IO。我实测的经验值单条insert的batchsize设置在1000到2000之间Spark的partition数量和MySQL的连接并发数要匹配一般4到8个partition同时写就行不要几十个并发去压一个小规格RDS。如果数据量特别大可以先写到S3临时目录再通过LOAD DATA导入但那样架构就复杂了。日增量在几百万行的场景直接JDBC写入完全扛得住。另外Glue作业的worker数量要结合数据量和分区数来定。数据读进来之后如果只给2个DPUReduce阶段的并发会受限如果给太多又会浪费钱。我通常先按源分区文件数量除以4估算需要的并行度再跑一次小数据量验证执行时间逐步调整。Glue的计费是按秒算的作业跑得越快越省钱但调参别拍脑袋拿数据说话。5.4 一个印象深刻的排错过程说一个真实的排查过程很有代表性。某天增量任务失败错误信息是Communications link failure最后一行写着The last packet sent successfully to the server was 0 milliseconds ago。当时第一反应是网络问题但反复检查Connection、SG、路由都没问题。后来逐步排查发现作业是在读取S3大表时耗时太长Spark任务的某个Executor和MySQL之间的长连接因为空闲超时被MySQL主动断开了。这个问题在数据量大、S3读取阶段耗时超过MySQL的wait_timeout时尤其容易出现S3上文件越碎读取阶段耗时越长越容易触发。解决思路有两个一是在Spark侧给连接设置socketTimeout和connectTimeout以及连接重试机制二是把读和写两个阶段合理编排连接建立后尽快进入写入状态避免跨越过长的空闲窗口。我最终选的是给JDBC URL加上connectTimeout60000和socketTimeout60000并调大了MySQL的wait_timeout问题就再没出现过。这个案例给我最大的提醒是ETL作业的报错信息往往只是个表象真正的根因藏在上下游的配置和时序里。排错的时候先把网络、驱动、超时这些基础项排除掉再往数据和并发方向深挖效率会高很多。6. 中国区落地体验与方案演进6.1 中国区Glue的使用差异标题里特意提到了中国区我的经验也主要来自中国区环境这里把差异点集中说下。中国区的AWS服务和海外区大体一致但Glue这个服务在中国区的版本更新节奏比海外区慢一些某些新功能比如特定版本的运行时、部分可视化组件开放时间会晚。如果你在Console里找不到某个海外区教程里的按钮先别急着怀疑自己很可能是功能没开放。这时候直接用代码创建Job把脚本和参数管理好效果是一样的。另外中国区的S3端点不同Driver JAR包放在S3上时路径要确认在同一个区且端点正确。跨区访问会有额外延迟甚至失败。如果你用的是中国区自己的S3s3.cn-north-1.amazonaws.com.cn注意CLI和SDK的端点配置。还有一个很实际的问题中国区的Glue临时目录、脚本目录约定和海外区一致但不同账号的默认命名可能不同。建议在项目初始化时统一规划好Bucket结构比如s3://etl-assets/scripts/、s3://etl-assets/temp/、s3://etl-assets/jars/避免每个同学各建各的后面清理都无从下手。Bucket多了之后权限管理也会变得麻烦统一规划能省很多事。6.2 和替代方案对比是不是只有Glue能干这事来聊几句实在的。把Data Catalog数据搬到MySQL除了用Glue ETL还有几条路可以走Athena CTAS加UNLOAD。如果你只是偶尔导一次可以直接用Athena把结果写到S3再手动导入MySQL。好处是零运维坏处是不适合定时批量同步每次都要人肉介入。EMR加Spark。如果你已经有EMR集群加一个步骤来跑同步当然可以但集群的启动和运维成本比Glue高。数据量极大规模TB级以上时EMR的灵活性更好因为你可以定制Spark参数、自定义依赖。AWS DMS。DMS更适合异构数据迁移和持续复制但它的源一般是数据库或文件系统直接接Data Catalog加Parquet这种元数据虚拟表不是它的强项。所以我最终选Glue核心原因就三个无服务器、按秒计费不跑空闲、原生与Data Catalog和Athena生态打通。如果你的场景是每天定时从数据湖抽一批数据写进MySQLGlue版方案应该是目前这个需求的最优解。6.3 后续可以演进的方向方案落地之后还有几个方向值得花时间做。写入目标从MySQL换到其他存储。脚本结构基本不变把format(jdbc)换成format(redshift)或format(dynamodb)就能把同样的数据同步到其他系统扩展性很好。这意味着你沉淀下来的其实是一套Data Catalog到任意存储的通用ETL模板而不只是MySQL专用脚本。加一层Schema校验。写MySQL之前用AWS Glue Schema Registry或者Deequ做数据质量检查不合格就阻断写入。数据量大的时候这一步能省下很多下游排错时间。我们后来就在订单表上加了字段完整性校验发现过一次上游漏字段的事故好在数据没来得及污染下游。中文乱码、时区问题统一收口到配置中心。把连接串、时区、字符集这类环境配置从脚本里抽出来放到Glue Job参数或Parameter Store里脚本做到和环境完全解耦换区迁移时只改配置不改代码。这看起来是个很小的工程习惯但在多环境多账号的公司里能少踩很多坑。我在实际运维中吃过好几次测试环境正常、生产环境乱码的亏后来统一收口之后基本绝迹了。这套Data Catalog到MySQL的方案我一直觉得是AWS数据家族里性价比很高的一块拼图。它不需要你专门养一套调度集群也不需要为了几百万行数据去写复杂的Spark调优把基础的事情做扎实剩下的交给托管服务就好。如果你也在搭类似的数据管道希望这篇能帮你少绕几个弯。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价