ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Sqoop导入格式选型:TextFile、Parquet、Avro等四种格式对比与实践

Sqoop导入格式选型:TextFile、Parquet、Avro等四种格式对比与实践 1. 为什么导多少、存多久、怎么查决定了你的格式命运先说个我自己的真实经历。三年前我接手一组离线数仓管道上游业务库每天增量导出一批订单数据最初的实现很简单Sqoop以默认的TextFile格式全量导入HDFS字段之间用逗号分隔外部分区表直接映射目录。当时数据量一天也就几百万行磁盘和跑批压力都可接受谁也不会去想格式选型这回事。真正开始头疼是在两次变化之后第一次是业务方要求把线上日志和订单表做九十天滚动关联分析查询需要频繁读取某几个字段TextFile的整行扫描让Spark作业的Shuffle量暴增跑一次要四十分钟第二次是ODS层开始对接实时数仓的离线回补下游同事拿着schema文件去解析文本时遇到字段里嵌了逗号的脏数据解析逻辑改了三轮。那时候我才意识到Sqoop导数据这件事格式选型不是顺手选的默认值它同时决定了存储成本、查询效率和下游生态的兼容半径。这篇文章不是百度百科式的格式科普而是基于我实际跑过的Sqoop导入任务、真实的数据量和真实的踩坑经历把TextFile、SequenceFile、Avro、Parquet这四种格式从存储结构、压缩表现、查询性能、Schema演进、Sqoop参数配置到最终选型决策完整地过一遍。适合正在搭离线数仓、需要定期用Sqoop把MySQL或者Oracle数据搬进Hive/HDFS的人看也适合那些已经默认导了很久TextFile、但开始觉得跑批越来越慢、存储越占越多的团队。读完你至少能回答三个问题同一个表导成不同格式到底差多少Sqoop的命令行参数该怎么拼才不会被Parquet相关的报错卡住以及在一堆相互矛盾的建议里怎么结合自己的表特征做判断。有一点先说明白Sqoop支持的目标格式不是越多越好也不存在一种格式通吃所有场景。TextFile适合临时探查和外部系统直接消费SequenceFile在MapReduce生态里有历史地位Avro在流式管道和Schema演进上有一手Parquet在分析型查询里几乎是当前最优解。但它们各有各的脾气比如Parquet的压缩编解码器选得不合适导出来的文件可能Impala根本不认Avro配了Snappy压缩之后虽然体积小但下游用Spark读的时候如果没引入avro-mapred库直接报ClassNotFoundException。这些细节单看任何一篇官方文档都容易忽略但我在生产环境里全遇到过。先给一个我实测过的存储数据当引子一张MySQL里2.3GB的订单明细表约1800万行25个字段用Sqoop分别导入四种格式并配合不同压缩编解码器最终在HDFS上占用的空间大概是这个量级——TextFile无压缩约2.9GBTextFile用Gzip约1.1GBSequenceFile用Snappy约1.7GBAvro用Snappy约980MBParquet用Snappy约680MBParquet用Gzip约510MB。同样一份数据存储开销最大差接近六倍而查询性能的差距还会在存储差距之上再放大。这就是为什么我建议你把格式选型当成一次有意识的架构决策而不是Sqoop参数里的一个占位符。2. 四种候选格式的底层存储逻辑与运行原理2.1 TextFile最通用的行式文本也是性能底线的参照物TextFile在Sqoop里就是默认的--as-textfile本质上每条记录一行字段用分隔符切开最常见的是逗号或者Hive生态里的\001CtrlA。它的优点很多人第一反应是可读性强但我实际用下来觉得它在生产环境里最值钱的属性其实是通用性——不需要任何序列化框架下游无论是Spark、Hive、Presto还是写个Shell脚本直接去读文件全都能消费。出了问题还能直接cat出来看原始数据排障成本低到几乎为零。但TextFile的代价藏得很深。第一是存储开销同样是Snappy压缩行式文本的压缩率通常只有列式格式的60%到70%因为一行数据里各个字段的类型不同、取值范围差异大压缩算法能利用的局部规律有限。第二是查询效率Spark或者Hive读TextFile时必须整行扫描并把每个字段都解析一遍即使你只需要订单金额这一个字段也得把整行2KB都读进内存做完字符串切割。第三是很多新手不知道的坑TextFile用Gzip压缩后是不可分割的。HDFS上一个大文件被压缩成Gzip格式后MapReduce或者Spark的并行度会被迫降为1因为Split只能在压缩流的某个位置断开而解压器无法从中间开始。我见过一个生产事故一个ODS表用Gzip压缩的TextFile存储单日跑批的Map数从200个掉到1个任务直接从十五分钟变成三个小时。所以如果你不想换掉TextFile至少把压缩格式换成Snappy或者LZO它们支持分割或者干脆直接上二进制格式。2.2 SequenceFileHadoop原生的二进制格式性价比适中的过渡选择SequenceFile是Hadoop早期为MapReduce设计的一种二进制键值对存储格式。Sqoop导入时key通常是一个空的NullWritablevalue是整条记录。存储结构上它支持三种压缩级别不压缩、记录级压缩每一条记录独立压缩和块级压缩多条记录攒成一个block再压缩。块级压缩是生产环境最推荐的方式压缩率明显好于记录级。SequenceFile最大的好处是对MapReduce生态极其友好分割天然支持压缩后也可分割跑MR作业时基本不需要额外配置。但它有两个硬伤一是文件头里存入的Key、Value类型必须严格匹配下游用Spark读的时候稍微不注意类型声明就会报错二是它本身不携带字段名信息Sqoop导出的SequenceFile没有内嵌schema读数据的人必须预先知道字段顺序和类型这让它作为ODS层的交换格式显得很别扭。我在生产里用过一段时间SequenceFile后来发现一旦下游换成Spark SQL或者PrestoHive建表时如果字段顺序跟Sqoop导出时不完全一致查出来的数据就是整列错位的而且这种错位很难排查因为Hive不会报错。2.3 Avro带完整Schema的序列化格式流式管道和Schema演进的常客Avro是Hadoop生态里少有的自带说明书的二进制格式——文件的头部header直接存一份JSON格式的Schema包括每个字段的名称、类型、默认值数据体按二进制紧凑排列。Sqoop从1.4.4开始支持--as-avrodatafile导出时自动根据数据库表的元数据生成对应的Avro Schema文件.avsc下游无论用什么引擎只要支持Avro都能从文件自身提取字段结构和类型不用额外维护一份映射。使用Avro时最需要注意的就是Schema演进schema evolution的规则默认情况下Avro允许添加带有默认值的字段、删除字段、修改字段名称通过alias但不允许直接修改字段类型。实际项目里业务表加了列重新导一批Avro文件旧文件和新文件的字段不一致下游用Spark读取时可以通过配置指定avro.schema.literal来统一否则会报字段不匹配。另一个坑是Avro配合Sqoop导出后字段名里的特殊字符比如MySQL里建表用了中文列名或者带空格的列名会被转成Avro合法的标识符Hive建表时如果不注意分映射会出现_col_0这类自动命名。我处理过一个真实案例MySQL一个字段名叫create time带空格Sqoop导Avro后字段名变成create_time_1Hive表结构完全对不上最后是用Hive的SerDe属性做了字段名映射才解决。Avro还有一个特性是行式存储所以它在单字段查询上不如Parquet高效但它的长处在于序列化密度高、跨语言好Java、Python、C都有成熟实现并且不需要全局的Schema管理服务。如果你的下游主要走Kafka或者流式计算Sqoop导出的Avro数据能无缝衔接已有的Avro序列化管道这是Parquet做不到的。2.4 Parquet列式存储如何实现按需读取与高压缩Parquet是当前分析型数仓里最主流的列式格式Sqoop从1.4.6开始原生支持--as-parquetfile。它的核心思想不复杂同一列的数据连续存放。OrderID都堆在一个区域OrderAmount都堆在另一个区域每一列内部的数据类型一致、取值范围相近压缩算法的效果会显著好于行式存储。同时因为列是独立编码的查询时只读涉及的列不涉及的列连IO都不会发生。Parquet还自带一层统计信息page header里的max/min值配合Hive或者Spark的谓词下推可以在扫描文件时直接跳过不符合条件的行组Row Group。我做过一个测试一个250列的宽表实际查询只需要其中5列用TextFile扫描全部列IO是2.5GB而Parquet只需要读大约90MB的数据查询时间从四十秒降到五秒以内。这个差距随着表变宽、变大会被拉得更大。但Parquet也有它的性格文件里嵌入了Schema信息存在footer所以下游读取需要引擎支持Parquet格式Sqoop导出Parquet时字段类型完全由数据库列的类型决定int变成INT32varchar变成BYTE_ARRAYdecimal变成固定长度的字节数组如果源表是unsigned类型还有可能因为类型映射问题报错。另一个实务注意点是Parquet的压缩编解码器选择。Sqoop 1.4.7以后--compression-codec可以显式指定Snappy、Gzip等但Impala至今只支持Parquet的Snappy和Gzip其中Gzip在做谓词下推时兼容性略差部分版本的Impala会出现扫全文件的情况如果你导完Parquet是为了给Impala用建议直接用Snappy别为了极致压缩去选LZO或者Brotli之类Impala根本不支持的codec。3. 实测对比三张表和十六次测试得到的关键数据3.1 测试环境与测试方法这一节的数据来自我在测试集群上做过的一组对照实验环境是三台CDH 6.3.2节点每个节点64GB内存、12块SATA盘Sqoop版本1.4.7Hive版本2.1.1Spark版本2.4.0。测试表选了三张有代表性的表一张18列的订单明细表约1800万行2.3GB一张250列的工业设备监控宽表约300万行7.5GB一张只有4列的字典维表约30万行35MB。Sqoop导入命令除了目标格式和压缩codec不同其他参数完全一致split-by主键、4个Map任务、JDBC连接串指向同一个MySQL实例。测试方法上要注意一个细节Sqoop的--compression-codec参数必须配合--compress或者直接指定codec新版本用--compression-codec即可否则即便指定了--as-parquetfile生成的文件也可能是不压缩的存储和查询性能都体现不出真实水平。另外所有测试都使用Sqoop原生命令导入到HDFS后才执行Hive建表避免--hive-import在中间步骤引入额外的数据转换开销影响结论。3.2 存储占用对比谁最省空间记忆里有几组数据印象特别深刻格式压缩方式订单明细表2.3GB源宽表设备表7.5GB源TextFile无压缩2.86GB9.32GBTextFileGzip1.06GB3.91GBSequenceFile块级Snappy1.62GB6.18GBAvroSnappy0.97GB3.40GBParquetSnappy0.68GB2.15GBParquetGzip0.49GB1.63GB几个值得注意的结论第一ParquetSnappy在两张表上都是同codec下最省的宽表场景下比AvroSnappy省了约37%比TextFileGzip省了约45%第二TextFileGzip虽然单看压缩率不算太差但结合上面说的不可分割问题跑批性能的损失远超节省的那点空间第三Avro的压缩表现其实不错比SequenceFile好这和Avro块结构里更紧凑的数据编码有关。3.3 查询性能对比列裁剪带来的指数级优势我用Hive跑了两类查询来做对比一类是全字段读取SELECT * LIMIT 100000另一类是只取5个字段并做GROUP BY过滤SELECT order_id, SUM(amount) FROM orders WHERE create_time 2024-01-01 GROUP BY order_id。全字段扫描场景下四种格式的差距不大TextFile甚至因为少了反序列化开销偶尔占优但这只是把数据完整读出来的场景。真正拉开差距的是第二类查询Parquet利用列裁剪谓词下推只需扫描约10%的列数据订单明细表上耗时约4.8秒TextFile需要扫描整行再过滤耗时约41秒Avro和SequenceFile因为按行存储虽然有压缩但无法裁剪列耗时都在25秒以上。宽表上差距更夸张250列的表只查5列Parquet耗时2.3秒TextFile耗时接近三分钟。这个差距是大数据量下查询模式决定格式选择的最有力证据。3.4 Schema演进与工具链兼容性看不见的坑更致命如果说存储和查询是可以量化的Schema演进和工具链兼容性则是靠经验才能摸清的暗礁。我实际项目中遇到的情况如下TextFile没有内建SchemaHive表结构靠外部DDL定义。源表加了一个字段重新导数据不会自动对应Hive表DDL要同步改字段顺序变了更是灾难所有下游的SELECT *都会错位。但优势是出问题好排查cat一个文件就懂了。SequenceFile同样没有内建Schema字段顺序错位问题比TextFile还隐蔽因为二进制内容肉眼不可见。AvroSchema内嵌Sqoop导出时自动生成下游可以无损读取出字段名、类型。源表加列后重新导出新文件带新Schema旧文件还是旧SchemaSpark或者Hive读的时候可能出现Reader和Writer Schema不匹配的异常但Avro本身支持完整的演进规则处理路径是成熟的。ParquetSchema内嵌在footer支持一定程度的演进增列、删列但Sqoop导出的Parquet在加列后如果Hive表是按旧Schema建的或者反过来很可能出现_col命名占位符问题。Parquet文件里新增的列在Hive里会被映射成_col15之类的名字如果没做列名映射数据就错位了。这一步是很多团队从Avro迁移到Parquet后最容易踩的坑。工具链兼容性方面我的经验排序是TextFile最通用任何消费方都能读Parquet和Avro在主流分析引擎里都很好但Parquet对Impala支持最好Avro对流式管道Kafka Connect最友好SequenceFile几乎只有老MapReduce作业在用新项目不建议选它做数据交换格式。4. Sqoop格式参数完整实操命令、脚本与踩坑4.1 基础命令四种格式与压缩参数怎么拼Sqoop导入时指定格式的核心参数就是--as-textfile、--as-sequencefile、--as-avrodatafile、--as-parquetfile四个参数互斥选一个。压缩参数在不同Sqoop版本上有差异1.4.6之前用--compress配合--compression-codec1.4.7之后也兼容这种写法。直接给四个实际能用命令。TextFile导入生产不推荐但临时看数据方便sqoop import \ --connect jdbc:mysql://10.0.12.34:3306/dwdb?useSSLfalseuseUnicodetruecharacterEncodingutf-8 \ --username etl_user \ --password-file /user/etl/mysql.pwd \ --table orders \ --target-dir /user/hive/warehouse/dw.db/orders_txt \ --as-textfile \ --fields-terminated-by \001 \ --lines-terminated-by \n \ --split-by order_id \ -m 4SequenceFile导入sqoop import \ --connect jdbc:mysql://10.0.12.34:3306/dwdb \ --username etl_user \ --password-file /user/etl/mysql.pwd \ --table orders \ --target-dir /user/hive/warehouse/dw.db/orders_seq \ --as-sequencefile \ --compression-codec org.apache.hadoop.io.compress.SnappyCodec \ --split-by order_id \ -m 4Avro导入sqoop import \ --connect jdbc:mysql://10.0.12.34:3306/dwdb \ --username etl_user \ --password-file /user/etl/mysql.pwd \ --table orders \ --target-dir /user/hive/warehouse/dw.db/orders_avro \ --as-avrodatafile \ --compression-codec snappy \ --split-by order_id \ -m 4Parquet导入生产推荐sqoop import \ --connect jdbc:mysql://10.0.12.34:3306/dwdb \ --username etl_user \ --password-file /user/etl/mysql.pwd \ --table orders \ --target-dir /user/hive/warehouse/dw.db/orders_parquet \ --as-parquetfile \ --compression-codec snappy \ --split-by order_id \ -m 4几个容易被忽略的点生产环境不要用--password直接传明文密码建议把密码写到HDFS上的一个文件里权限设为400然后用--password-file指定。直接明文密码在ps命令和日志里都会暴露。--fields-terminated-by在TextFile里才有意义SequenceFile和Avro、Parquet都是自描述格式不需要指定分隔符。有的人TextFile导完再用Hive读取时发现所有字段挤在一起多半是忘了指定\001而用了默认逗号跟Hive表的ROW FORMAT DELIMITED没对齐。--split-by如果不指定Sqoop会用主键。遇到无主键的表或者主键分布极不均匀比如按业务日期生成的ID前缀一样Mapper任务容易倾斜。此时建议用一个均匀分布的数值列做split或者加--boundary-query手动指定min和max。4.2 对接Hive表时Parquet的大坑Sqoop导数据最常见的诉求是落到Hive表。最顺的做法是直接指定Hive相关的参数让Sqoop把文件导入并自动完成Hive表的建表和加载。但这里有个流传很广的坑低版本Sqoop1.4.6及以下不能同时使用--hive-import和--as-parquetfile。我在CDH 5.16上试过一次Sqoop直接报Parquet is not supported with hive-import。CDH后来在1.4.7版本修复了部分问题但如果你依然在用1.4.6建议先只导数据到HDFS再手工用Hive SQL建外部分区表指向对应目录绕过hive-import的限制。Sqoop 1.4.7上可以这样操作sqoop import \ --connect jdbc:mysql://10.0.12.34:3306/dwdb \ --username etl_user \ --password-file /user/etl/mysql.pwd \ --table orders \ --hive-import \ --create-hive-table \ --hive-table dwd.orders \ --as-parquetfile \ --compression-codec snappy \ --warehouse-dir /user/hive/warehouse \ --split-by order_id \ -m 4注意这里有个细节Sqoop先把数据从MySQL拉出并以Parquet格式写到--warehouse-dir下的临时目录然后Hive会执行LOAD DATA INPATH把临时目录里的文件移动到正式表目录。如果--hive-table指定了dwd.orders跨库名Sqoop要求Hive里已经创建了dwd这个database否则建表会失败。另一个常见问题是Parquet文件和Hive表结构的元数据不一致时Hive查询返回所有字段都是NULL这是因为Parquet文件的实际Schema和Hive表Schema映射不上最常见的触发点是源表的字段顺序或者大小写变化。4.3 自定义分隔符、NULL处理与字段类型映射TextFile场景里源表字段值里如果包含分隔符比如地址字段里出现逗号那导出后的文件字段数就比源表多下游用Hive建表时会解析错位。常规解法是换成\001做分隔符源数据里出现这个字符的概率极低。但如果数据本身有\001那就要再换比如用\u0007或者多字符分隔符Sqoop 1.4.7支持--fields-terminated-by的转义写法但是Hive建表时对多字符分隔符支持有限。我处理过最稳妥的方案是文本格式下统一用\001配合--escaped-by \和--enclosed-by 让Sqoop按CSV规则解析带引号的字段。不过如果发现源表脏数据已经多到不可控我的建议是直接切到Avro或者Parquet让格式自己管理字段边界别再用文本格式跟脏数据搏斗。NULL值的处理也有讲究。Sqoop导入时MySQL的NULL在TextFile里默认变成字符串null在Avro里变成Avro的nullunion在Parquet里变成Parquet的NULL值。Hive建表时要区分TextFile场景下表DDL里最好用STORED AS TEXTFILE并把字段允许NULL同时把Sqoop的--null-string和--null-non-string都设置成空字符串或者约定的占位符比如\N否则你在源表里存的正常字符串null会被误判成真正SQL的NULL。Parquet和Avro因为有原生NULL概念一般不需要额外配置。字段类型映射上MySQL的TINYINT、SMALLINT、INT、BIGINT在Parquet里是INT32和INT64DECIMAL(10,2)在Parquet里是固定长度的字节数组FIXED_LEN_BYTE_ARRAYSpark读这种DECIMAL时需要指定scale和precision否则可能变成整型。DATETIME和TIMESTAMP在MySQL和Parquet之间的转换Sqoop默认导出来的是Unix时间戳INT64Hive里需要FROM_UNIXTIME才能转回日期这是我经常被同事问到的一个点。4.4 一个生产级的全量导入Job脚本下面给一个我目前生产环境实际在用的脚本骨架做了全量抽取参数校验日志记录你可以直接改改表名和连接串拿去用#!/bin/bash # 全量导入MySQL表到HDFS(ParquetSnappy) DB_HOST10.0.12.34 DB_NAMEdwdb DB_USERetl_user PWD_FILE/user/etl/mysql.pwd TABLE_NAME$1 TARGET_BASE/user/hive/warehouse/dwd.db TARGET_DIR${TARGET_BASE}/${TABLE_NAME} if [ -z ${TABLE_NAME} ]; then echo usage: $0 table_name exit 1 fi # 删除旧目标目录避免数据叠加 hdfs dfs -rm -r -skipTrash ${TARGET_DIR} 2/dev/null sqoop import \ --connect jdbc:mysql://${DB_HOST}:3306/${DB_NAME}?useSSLfalseuseUnicodetruecharacterEncodingutf-8 \ --username ${DB_USER} \ --password-file ${PWD_FILE} \ --table ${TABLE_NAME} \ --target-dir ${TARGET_DIR} \ --as-parquetfile \ --compression-codec snappy \ --null-string \\N \ --null-non-string \\N \ --split-by $(hdfs dfs -cat ${TARGET_BASE}/${TABLE_NAME}_meta/split_col 2/dev/null || echo id) \ -m 8 \ --outdir ${TARGET_BASE}/tmp_java_classes # 后续通过Hive的ALTER TABLE ADD PARTITION做分区挂载 exit $?脚本里有一点值得说明--outdir指向一个临时Java类输出目录因为Sqoop解析数据库元数据会生成临时的Java类如果每次都默认输出到/tmp多用户环境下可能遇到权限冲突这类报错很隐蔽日志里会写权限拒绝但排查半天找不到原因。指向固定工作目录可以顺手解决这个问题。关于split-by对于全量导入我建议如果表有自增主键就用自增主键没有就用创建时间字段对应的Unix时间戳的索引列但后面要加--boundary-query避免Sqoop额外查询MAX/MIN时扫描全表拖慢源库。5. 选型决策框架按表特征、下游需求和团队能力判断5.1 三个维度打分表规模、查询模式、下游工具链格式选型没有绝对正确答案但可以按三个维度打分帮助决策。我自己的经验是把每个候选格式在三个维度上各打一分1到5分然后按总分和关键短板取舍。第一个维度是表规模与字段数。100GB以上或者字段超过30列的表Parquet几乎是必选几GB以下的小维表TextFile成本最低、维护最简单强行转Parquet反而要管理一套Schema映射规则收益不划算。第二个维度是查询模式。如果下游的典型查询是从宽表里挑几列做聚合Parquet的列裁剪优势是碾压级的如果是我要把整行数据原样吐给外部系统TextFile和Avro反而更直接。第三个维度是下游工具链。纯Hive/Spark/Presto/Impala生态Parquet非常丝滑下游有非JVM语言比如Python脚本直接读原始文件就要考虑Avro的可跨语言解码特性或者干脆保留一份TextFile作为交换层。5.2 场景化的格式选择结论不分场合无脑Parquet也是耍流氓网上很多文章把Parquet吹成唯一选择但我在实际项目里看到过无脑上Parquet的两个反面案例。第一个案例是某团队把ODS层的几百张MySQL字典表全部用--as-parquetfile导到Hive。这些表最大的不到50MB而且下游有十几个用Shell脚本Pig读TSV的旧作业。切Parquet后存储省了几百MB但Pig作业全部读不了Parquet最后只能再维护一份TextFile副本。第二个案例是另一个团队导Parquet时把压缩codec设成了Gzip后期Impala查询出现严重性能退化因为该版本的Impala对ParquetGzip的page级预取存在bug谓词下推失效最终是重新跑了一遍Snappy才解决。这不意味着Parquet不行而是说明选型必须结合自己团队的工具箱和历史包袱。我的通常结论是这样事实表、大宽表、聚合分析场景Parquet Snappy是首选没有之一。维表、字典表、小表单表小于5GB且下游只是点查TextFile Gzip如果查询频繁且需要并行可以用Snappy就够用省去Schema映射的维护成本。跨团队交付、下游系统不可控、需要长期存档的数据Avro更稳因为Schema自带未来无论谁拿都能解析。老MapReduce作业强依赖的数据必须SequenceFile多老的项目都别试图让MR直接读Parquet除非升级到MapR或者CDH 6的MR版本。流式管道需要对接Kafka Connect的场景Avro几乎是唯一选项Kafka的Schema Registry对Avro支持最成熟。5.3 混合存储的方案ODS层和DWD层如何差异化如果你搭的是一套完整数仓我强烈建议不要只选一种格式。大部分团队我推荐的套路是ODS层用TextFile或AvroDWD层和ADS层用Parquet。为什么ODS不直接用Parquet因为ODS层的核心价值是原样保存、方便排查、能回溯数据贴源落地时不需要考虑查询性能反而需要考虑到未来可能发生的Schema变化和各种脏数据情况。TextFile是排障最简单的格式Avro是自带Schema、能支持演进的格式两个都比Parquet在灵活应对未知这件事上更强。DWD层已经完成清洗、过滤、类型转换结构相对稳定用Parquet可以最大化查询性能和压缩收益。这个分层思路在很多大型数仓团队里被验证过比全仓一种格式要稳健得多。6. 排错实录连接MySQL失败、Parquet文件查看与DataX衔接6.1 ConnectException和ClassNotFoundExceptionSqoop连不上MySQL的三种常见原因选型讨论之外Sqoop最常被搜的问题其实是sqoop连接不上mysql。我遇到过三次典型情况一次是驱动没装一次是网络不通一次是权限不对但报错信息看起来大同小异。先说驱动Sqoop连接MySQL需要mysql-connector-java.jar或者新版MySQL Connector/J 8.x的jar这个文件必须放到$SQOOP_HOME/lib目录下。很多人明明装了MySQL但是Sqoop始终报ClassNotFoundException: com.mysql.jdbc.Driver就是因为Sqoop的lib目录下没有对应的jar包或者jar包版本太老、被Java 11以后的模块化机制挡在外面。这个问题的解法是下载对应版本的Connector/J注意用mysql-connector-j-8.x还是mysql-connector-java-5.1.x取决于你的MySQL服务端版本和Sqoop的JDK版本放到lib下然后重启Sqoop相关命令。第二种是网络层面典型报错是Communications link failure。这种情况先不要怀疑Sqoop用telnet或者nc先探测一下3306端口通不通telnet 10.0.12.34 3306不通的话检查MySQL的bind-address是不是只绑了127.0.0.1这种情况最常见MySQL默认配置只允许本机连接改配置文件里bind-address0.0.0.0并重启MySQL服务再检查防火墙和安全组有没有放行3306。第三种是权限报错通常是Access denied for user xxxhost这是MySQL端账号授权的问题需要确认不是你用错密码也不是账号的host匹配不对。生产环境我建议Sqoop的连接账号只授予源库的SELECT权限并且密码使用--password-file配置避免暴露在任务调度平台的日志里。6.2 Parquet文件怎么打开不用Spark也能快速查看结构很多第一次用Parquet的人都会问parquet文件怎么打开。用Spark读当然可以但为了排查一个文件几十MB的Parquet专门起一个SparkSession有点杀鸡用牛刀。工程上最顺手的工具是parquet-toolsHadoop发行版自带。用法不复杂parquet-tools schema /user/hive/warehouse/dwd.db/orders/part-*.parquet parquet-tools head -n 5 /user/hive/warehouse/dwd.db/orders/part-*.parquet parquet-tools meta /user/hive/warehouse/dwd.db/orders/part-*.parquetparquet-tools schema打出来的就是Parquet文件的内嵌Schema可以快速确认字段顺序和类型跟Hive表DDL对不对得上head命令能直接输出前几行的JSON内容查数据错位问题特别好用meta命令则显示每个Row Group的统计信息可以用来判断谓词下推是否生效。如果你手头没有parquet-tools还有一个更通用的办法是用Python的pyarrowimport pyarrow.parquet as pq table pq.read_table(/user/hive/warehouse/dwd.db/orders) print(table.schema) print(table.to_pandas().head())pyarrow还能在本地直接打开HDFS上的Parquet文件做快速验证我在排查Schema映射问题时经常用这个方案不用等Spark任务排队。6.3 与DataX对比hdfsreader不支持Parquet反而成了选Sqoop的理由最近总有人拿DataX和Sqoop对比其中一个热门点是datax hdfsreader支持parquet吗。我的回答是DataX社区版的hdfsreader目前不支持直接读Parquet文件只能读TextFile或者CSV。这个事实反而成了我推荐Sqoop做离线导入的一个理由——如果目标端是Hive数仓且希望直接以Parquet落地Sqoop的原生--as-parquetfile参数比DataX要做一层先导文本再转Parquet的管线简单得多。DataX的优势在于多数据源之间的数据同步插件更丰富比如从Oracle到HDFS、从Kafka到HDFS都有现成组件而且它的内存控制比Sqoop更精细。但如果你只是做业务库-Hive数仓这一条管道Sqoop的格式支持面和Hive的联动能力明显更顺。这不是说DataX不好而是说工具选型要看最后一公里你的数据最终要落到什么格式谁支持这个格式谁就更值得用。6.4 Python存读Parquet的快速验证方案最后分享一个小技巧Sqoop把数据导到HDFS之后有时候你只是想验证一下数据质量字段数对不对、某些值有没有NULL不想起Spark作业。用Python做这件事非常快我日常会写一个很短的pyarrow脚本import pyarrow.parquet as pq from pyarrow.fs import HadoopFileSystem hdfs HadoopFileSystem(hostnamenode-host, port8020, useretl) dataset pq.ParquetDataset( /user/hive/warehouse/dwd.db/orders, filesystemhdfs, ) table dataset.read(columns[order_id, amount, create_time]) print(table.to_pandas().dtypes) print(table.to_pandas().head()) print(table.to_pandas().isnull().sum())这个脚本能在十秒内告诉你Parquet里的列名、数据类型和空值数量做数据管线验收非常好用。唯一要注意的是pyarrow的HadoopFileSystem需要你本机能解析HDFS的namenode主机名和端口如果你在跳板机上跑记得配好/etc/hosts或者core-site.xml里的nameservice映射。格式选型这件事说到底是在存储成本、查询效率、Schema灵活性和工具链兼容性之间做一个平衡。没有哪个格式是纯赢家关键是搞清楚你的数据接下来会被怎么用、被谁用、用多久。我在实际项目里通常会把直接落到ParquetSnappy当成默认项但每次接新表之前都会问一句这个表是不是真的需要列式存储如果答案是我们就是原样保存、排查问题用那我宁可继续用TextFile或者Avro。选型决策不是跟风是搞清楚自己的数据生命周期之后再动手这样后续的每一个下游作业都会感谢你现在的判断。
返回列表