尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

EMR中Hive与Spark集成Glue Data Catalog实战指南

EMR中Hive与Spark集成Glue Data Catalog实战指南 1. 问题背景为什么要在EMR里折腾Glue Data Catalog先把话说在前面如果你在EMR上只用Spark、不跑Hive那Glue Data Catalog可能只是一个“锦上添花”的东西。但只要你同时用Hive和Spark尤其在一个团队、一套数仓流程里既有hive命令行操作又有spark-submit跑批任务那这两者能不能“看到同一张表”就是决定性的问题了。我最初遇到的场景就是这么接地气上午用Hive建了一张分区表下午Spark作业直接报Table not found查了一下才发现两者各自维护了一套元数据压根不互通。这种割裂感在做数据仓库的时候特别致命。EMR集群默认自带的元数据服务是Hive Metastore但这个Metastore默认落在集群本地。EMR和自建Hadoop最大的区别在于它不是一台常驻机器而是按需拉起、用完销毁的。集群一终止本地Metastore里存的库表信息就没了。即使集群不销毁只要你有多个集群比如开发集群、生产集群分开每个集群各有一份Metastore那Hive里建的表Spark看不到Spark里建的表Hive也看不到更麻烦的是你可能根本说不清哪一份数据是最新的。Glue Data Catalog这时候就变成一个很自然的选择。它是AWS上的托管元数据服务本质是一个“集中式的Hive Metastore”底层兼容Hive Metastore的协议。EMR在创建集群的时候可以直接指定用它来替代本地的Metastore这样Hive和Spark只要都指向同一个Glue Data Catalog两边的库表元数据就统一了。听起来像个很常规的配置但实际集成过程中牵扯到的细节远不止“开个开关”这么简单。这篇文章就从我自己的操作经验出发把Hive和Spark集成Glue Data Catalog的完整链路拆开讲一遍包括架构理解、具体配置、参数选择还有我踩过的坑和排查思路。如果你也在用EMR搭数仓或者正被“Hive和Spark元数据不互通”折磨这篇应该能帮你省不少时间。2. 整体架构拆解Glue Data Catalog到底在整条链路里扮演什么角色2.1 先搞清楚Hive Metastore的工作机制在进入Glue Data Catalog之前必须先把Hive Metastore的原理讲透。很多人在这一步就懵了因为Hive的部署结构天然比Spark复杂一点。Hive本质上分两层执行引擎和元数据服务。执行引擎干活比如跑MapReduce、跑Tez但表结构、分区信息、字段注释这些“档案”都存放在Metastore里。Metastore本身又是一个独立的服务后端可以接Derby、MySQL、PostgreSQL这类关系型数据库。HiveServer2在启动时会去连Metastore客户端用beeline连接HiveServer2之后你执行的CREATE TABLE语句实际是HiveServer2转给Metastore去写库的。Spark集成Hive的时候逻辑稍微有点不一样。Spark并不强制要求启动HiveServer2它只需要能读到Metastore的元数据就行。所以spark.sql.catalogImplementationhive这个配置的意思就是告诉Spark“你不要自己管元数据去连一个Hive Metastore”。Spark里内置了对Metastore客户端的支持启动时会直接用Hive Metastore Client去连远端的Metastore服务。在自建Hadoop集群里Hive和Spark如果共用同一个MySQL作为Metastore后端那两边天然就能看到同一批表。但在EMR里这个逻辑被“包装”了一层EMR自带的Metastore后端就是用集群本地的一个嵌入式数据库默认并不适合多个组件共享更不适合集群销毁后继续保留。所以你需要的核心能力是让两个计算框架都指向一个“外部、持久、共享”的元数据存储Glue Data Catalog就是干这个的。2.2 Glue Data Catalog没你想的那么“重”很多人一听Glue第一反应是“ETL服务”因为AWS Glue确实有一个托管ETL的功能。但Glue Data Catalog其实是独立于ETL能力的一个组件你可以完全不用Glue的ETL只用它的Catalog功能。Glue Data Catalog对外提供的是Hive Metastore协议的兼容接口。这意味着Hive和Spark不需要什么特殊改造只需要把Metastore地址指到Glue的端点就能把它当成一个远端的Hive Metastore来用。对上层计算框架而言Glue Data Catalog看起来就是一个“永远在线、从来不丢数据”的Hive Metastore服务。这种设计有几个好处。第一是持久性集群销毁重建之后所有表结构还在HDFS或S3上的数据文件还在重新拉起集群后一条SHOW TABLES就能直接恢复现场。第二是跨集群共享开发集群、生产集群、临时调查集群只要都指向同一个Glue Data Catalog就能共用同一套元数据。第三是权限统一可以用IAM策略精细控制谁能建表、谁能查表而不是每套集群单独维护一份权限。但“托管”也意味着你失去了一些控制权。Metastore的底库是什么样的你看不到Metastore的GC、连接数你也不用管但同样地如果Glue服务性能出现抖动你的Hive查询和Spark任务都会受影响。这一点在分区特别多的表上特别明显后面我会单独讲。2.3 数据文件与元数据分离的思维转换理解Glue Data Catalog的另一个关键点是彻底抛弃“Metastore里存了数据”这个错觉。Metastore存的全是元数据真正的数据还是在你的存储系统里EMR上最常见的就是S3。建表的时候Hive或Spark会把表的LOCATION指向一个S3路径Metastore只负责记录“这张表的字段有哪些、分区有哪些、数据在哪个路径”。查询的时候计算引擎先访问Metastore拿到元数据再根据LOCATION去S3拉数据最后在内存里做计算。所以在做集成方案时千万不要把“元数据统一”和“数据统一”混为一谈。Glue Data Catalog解决的是前者让Hive和Spark拿到同一份“数据地图”。至于S3上的数据文件格式是不是两头都能读那是另一回事也就是我们常说的存储格式兼容问题。比如Spark默认写的ParquetHive能读但Hive写的某些TextFile格式Spark也能读。这跟Metastore用哪个没直接关系是SerDe和文件格式层面的问题。我在后面的实操里会说怎么验证这种兼容性。3. Hive与Spark集成Glue Data Catalog的配置实操3.1 EMR集群创建时指定Glue作为Metastore最简单的接入方式是在创建EMR集群时直接指定。控制台和CLI都支持核心就一个参数--configurations里设置hive-site的hive.metastore.glue.catalogid或者在EMR的分类配置里直接选Glue Data Catalog作为Hive Metastore。我一般习惯用CLI创建配置写成一个JSON文件维护方便版本管理。下面是一个典型的配置文件里面已经包含了Hive和Spark两个组件需要指向Glue的配置[ { Classification: hive-site, Properties: { hive.metastore.glue.catalogid: 123456789012, aws.glue.endpoint: glue.us-east-1.amazonaws.com, hive.metastore.client.capability.check: false } }, { Classification: spark-hive-site, Properties: { hive.metastore.glue.catalogid: 123456789012, aws.glue.endpoint: glue.us-east-1.amazonaws.com } } ]这里hive.metastore.glue.catalogid就是你的AWS账号ID一般默认就是当前账号的Glue Catalog不填也会自动使用当前账号的。但如果你用的是Lake Formation做细粒度权限管理或者跨账号共享Catalog那这个参数就必须显式指定了。aws.glue.endpoint理论上也可以不配它会根据Region自动解析但如果你的VPC里配置了Glue VPC Endpoint建议显式指一下内网地址免得流量绕公网。还需要注意hive.metastore.client.capability.check。Hive 3.x版本启动时会做Metastore能力校验连Glue这种托管服务时某些Capability比如ALREADY_FILTERED可能没法正确响应导致Hive启动报错。我遇到的情况是Hive CLI启动慢、偶尔报能力检查失败的错把这个开关设成false之后就稳了。如果你的EMR版本比较新这条配置可能已经不必要但保留它不会有副作用。Spark侧为什么是spark-hive-site而不是hive-site因为EMR上Spark读取Hive配置时有自己的优先级spark-hive-site.xml里配置的值会覆盖Spark进程里Hive Metastore相关的默认配置比直接改hive-site.xml更精准。如果你在EMR控制台里操作界面上也会让你在Spark的hive-site分类里填Glue配置道理是一样的。启动命令示例aws emr create-cluster \ --name emr-glue-catalog-demo \ --release-label emr-6.12.0 \ --applications NameHive NameSpark NameTez \ --ec2-attributes InstanceTypem5.xlarge,InstanceCount3 \ --instance-groups InstanceGroupTypeMASTER,InstanceTypem5.xlarge,InstanceCount1 \ InstanceGroupTypeCORE,InstanceTypem5.xlarge,InstanceCount2 \ --configurations file://./glue-config.json \ --use-default-roles集群起来之后先别急着建表先验证一下Hive和Spark是不是真的都指向Glue了。3.2 验证Hive侧是否成功连接Glue Data Catalog集群起来之后先在Master节点上跑一下Hive的命令。注意我建议用beeline连HiveServer2做验证而不是直接在Master上执行hive命令行因为后者触发的是一条完整MapReduce或Tez任务跑得慢而且输出结果里不一定能看到Metastore信息。用beeline更直接。beeline -u jdbc:hive2://localhost:10000/default -n hadoop连上之后执行SHOW DATABASES; CREATE DATABASE test_glue_db; CREATE TABLE test_glue_db.employee ( id INT, name STRING, dept STRING ) PARTITIONED BY (dt STRING) STORED AS PARQUET LOCATION s3://my-bucket/warehouse/employee;执行完之后打开AWS控制台进入Glue服务在Data Catalog的Databases列表里应该能看到test_glue_dbTables列表里能看到employee。只要出现了就说明Hive侧已经写到Glue里了。我特别强调这个验证步骤是因为很多人配置完直接跑业务等出了问题才回头查底层。实际上5分钟就能确认的事情没必要拖到数仓里几百张表之后才发现建表路径不对。注意一下建表时的LOCATION。在EMRS3的组合里表的真实数据写在S3元数据写在Glue。如果你不指定路径Hive会用默认的/user/hive/warehouse目录这在本地集群可能没问题但在EMR上如果不是放S3集群销毁数据也就没了。所以尽量在建表时显式指定S3路径。3.3 Spark侧读取Glue中的元数据Hive写完了接下来就看Spark能不能读到。进入spark-sql交互环境spark-sql然后执行SHOW DATABASES; USE test_glue_db; SHOW TABLES; SELECT * FROM employee;如果Spark能列出employee表说明Spark已经通过Glue Data Catalog读取到了Hive建的元数据。这时候整个集成链路已经通了Hive和Spark开始共享同一套元数据。但“读到”只是一个起点接下来要验证“互写”。在Spark侧创建一个新表然后回Hive侧看能不能看到CREATE TABLE test_glue_db.spark_created_table ( id BIGINT, value STRING ) USING PARQUET LOCATION s3://my-bucket/warehouse/spark_created_table;创建完成后回到beeline执行SHOW TABLES IN test_glue_db;如果能看到spark_created_table恭喜你双向互通已经验证完成。这套流程跑通之后你的数仓架构里最烦人的元数据隔阂问题就消失了。3.4 补充使用Hive Metastore模式的Spark配置有一种情况需要特别说明。如果你在EMR上不想用spark-sql而是用spark-submit提交一个Scala或Python作业那代码里大概率会用到SparkSession。SparkSession读取Glue Data Catalog的配置跟spark-sql命令行是一致的但有些老代码里会显式设置enableHiveSupport()这会触发Spark把Hive Metastore的配置加载进来。如果EMR上的spark-hive-site.xml已经正确配置那没问题但如果你的作业是在一个自建Spark环境里跑的就需要在代码里或者提交命令里补上相关配置。一个典型的spark-submit提交命令可以这样写spark-submit \ --master yarn \ --deploy-mode cluster \ --conf spark.sql.catalogImplementationhive \ --conf spark.hadoop.hive.metastore.glue.catalogid123456789012 \ --conf spark.hadoop.aws.glue.endpointglue.us-east-1.amazonaws.com \ application.py注意spark.sql.catalogImplementationhive很关键。Spark 3.x默认的Catalog实现是in-memory这会导致spark.sql里看不到任何Hive表。你要明确告诉Spark走Hive Metastore协议它才会去连Glue。很多人在Spark作业里看不到Hive表第一反应是“权限不够”或者“Glue配置错了”但实际上大概率是catalogImplementation没改成hive。这个配置是Spark所有会话的“总开关”优先级是最高的。所以我的建议是提交作业时显式写出来不要依赖环境的默认值。4. 常见问题与排查技巧实录4.1 Hive建的表Spark看不到第一时间查什么这个问题我在不同环境里遇到过不下五次每次根因都不一样但排查顺序基本是固定的。第一步确认Spark的Catalog实现。用spark-sql执行SET spark.sql.catalogImplementation;如果没有返回hive那不需要再看别的了Spark压根没走Metastore。把它设为hive之后再试一次。第二步查看Spark进程里的Hive Metastore配置。在spark-sql里执行SET hive.metastore.uris; SET hive.metastore.glue.catalogid;正常情况下使用Glue Data Catalog时hive.metastore.uris应该是空或者没配置的而hive.metastore.glue.catalogid应该有值。如果hive.metastore.uris指向了一个不存在的Metastore地址那Spark就会去连那个地址连不上就抛异常连上了也不是Glue自然看不到Glue里的表。第三步检查IAM权限。Spark作业的Execution Role必须有Glue相关权限最基本的几个是glue:GetDatabase、glue:GetDatabases、glue:GetTable、glue:GetTables、glue:GetPartition、glue:GetPartitions。如果权限缺失Spark会报AccessDeniedException这个错误比较明显。但有时候权限策略写得太复杂比如用了Condition限定资源路径那排查起来就麻烦一些。我的建议是先用一个宽松的Policy验证链路通畅再加限制条件一层层收紧。4.2 建表都成功了但两边查到的字段对不上还有一种很隐蔽的情况Hive和Spark都能看到表但两边执行DESCRIBE的结果不一样或者Hive能查出数据、Spark查出的结果全是NULL反过来也一样。这种情况十有八九是存储格式兼容问题。举一个我实际踩过的例子。Spark默认用USING PARQUET建表底层文件是Parquet格式。Hive要求通过STORED AS PARQUET来识别Parquet如果你的建表语句没指定STORED ASHive会按默认的TextFile格式去读然后发现读出来的字段对不上。反过来也一样Hive建表时STORED AS TEXTFILESpark虽然能读到元数据但Spark读TEXTFILE时对字段类型和分隔符的处理方式跟Hive不完全一致也可能导致读不出内容。这个问题的本质在于Metastore只是元数据地图但真正读文件的时候每个引擎还需要知道“文件的物理格式是什么、字段怎么解析”。Glue Data Catalog里存的SerDe信息、InputFormat、OutputFormat如果两个引擎对同一份SerDe信息的解释有差异就会出现“能看见但读不对”的情况。我的习惯是数仓里的核心表统一使用Parquet格式并且两边的建表语句都显式指定格式。Hive侧用STORED AS PARQUETSpark侧要么用USING PARQUET要么为了跟Hive完全一致直接通过CREATE TABLE ... STORED AS PARQUET这种方式建表。这样能最大程度避免格式识别差异。还有一个相关但容易被忽略的点字段注释和复杂类型。Hive对STRUCT、MAP、ARRAY这类复杂类型的展示方式跟Spark有细微差别但不是存储层面的错乱只是显示的字符串格式不同。看到这种情况不用慌用DESCRIBE对比一下字段类型和嵌套结构只要基础类型一致基本不影响查询。4.3 使用SHOW CREATE TABLE导出并重建表如果你已经有一批表在旧的Metastore里想整体迁移到Glue Data Catalog最快的办法不是手工重建而是用SHOW CREATE TABLE把每张表的建表语句导出来替换掉LOCATION里旧的路径然后在新的Metastore下执行。具体的批量操作可以写成脚本。先通过Hive把库下表名捞出来然后用SHOW CREATE TABLE逐张导出存成SQL文件。要注意几点第一旧的LOCATION如果指向HDFS路径迁移到S3之后需要改成对应的S3路径第二如果表有大量分区建表之后最好逐分区执行MSCK REPAIR TABLE或者用ALTER TABLE ADD PARTITION批量修复分区元数据第三如果表有权限控制比如基于Lake Formation的列级权限迁移后需要重新授权。按照我的经验几十张表的迁移是很快的瓶颈一般不在加表的速度而在分区修复。因为Glue Data Catalog的API调用有速率限制分区特别多的表比如上万个分区如果一次性MSCK REPAIR很容易触发限流。4.4 Glue Data Catalog性能问题分区多的时候Hive查询变慢这个是集成之后比较常见的“慢性病”。表结构没问题、查询能出结果但就是慢。尤其SHOW PARTITIONS、SELECT COUNT(*)这类需要扫描大量分区元数据的操作在Glue Data Catalog上会明显比本地Metastore慢。原因不复杂Glue Data Catalog的元数据操作是网络请求每查一次分区都要调一次API跟本地连数据库比天然有网络开销。如果你一张表有几千个分区Hive在执行查询计划时需要枚举所有分区这个枚举过程在Glue上就是几千甚至几万次API调用。优化思路有两个方向。第一是提升单次请求的效率调整hive.metastore.glue.max-retry和hive.metastore.glue.num-retries这类参数增加重试次数把hive.metastore.glue.thrift.connection-timeout调大一点避免网络波动时直接超时。第二是从业务侧优化减少不必要的全分区扫描。比如按天分区的时候能走分区裁剪就走分区裁剪少用SELECT *全表扫。另外一个细节是如果一张表的分区非常多但历史分区已经不需要了可以考虑归档比如把一年前的数据移到一个单独的库或单独的表里别让它继续污染主表的元数据。这样Hive和Spark的元数据检索速度都能回来。4.5 集群重启后Glue里的表“消失”了还有一种情况容易引起恐慌集群重启之后SHOW TABLES返回空第一反应是Glue里的元数据丢了。但绝大多数情况下不是丢了而是新集群没有指向同一个Catalog。EMR集群默认会按照创建时的配置决定Metastore用哪个。如果你在控制台创建集群时没有选Glue或者配置分类里没有包含正确的spark-hive-site那新集群会自建一个本地Metastore跟Glue完全是两套自然看不到Glue里的表。这个问题的排查顺序是登录新集群执行SET hive.metastore.uris和SET hive.metastore.glue.catalogid确认是否指向了正确的Glue库。如果配置没问题再在Glue控制台里确认Data Catalog下确实有对应表。两边都核对了还是看不到才需要考虑是不是账号权限隔离或者Catalog被切换了。还有一点要注意Glue Data Catalog是有Region概念的。你在us-east-1建的库表在us-west-2的EMR集群里默认是看不到的。EMR集群的Region和Glue的Region必须一致或者至少EMR侧要显式指定跟Glue同一个Region的Endpoint。这个在跨Region部署的时候经常被踩。5. 表格式与数据湖方案的选型建议5.1 Hive和Spark的表存储格式怎么选更稳如果你在EMR上同时用Hive和Spark表的存储格式建议遵循一个基本原则核心表用Parquet不要用TextFile也不要轻易用ORC。Parquet在Spark里的支持是最成熟的读写性能好压缩率高还支持列裁剪和谓词下推。Hive读Parquet也没问题只要SerDe是org.apache.hadoop.hive.ql.io.parquet.serde.ParquetHiveSerDe就行。ORC的问题在于虽然Spark 3.x已经支持读取ORC但写入和读取时的优化参数跟Hive原生场景还是有差异。如果你是一个团队同时用两种引擎跑数仓任务Parquet是最小公倍数两边都不会有性能上的坑。TextFile能不用就不用。不是不能读而是TextFile在Spark上跑起来慢而且字段类型有可能会被默认推断成string跟Hive里定义的int、double不一致到时候查出来类型不匹配又要返工。5.2 分区策略从Metastore角度看合理分区数量分区是Hive和Spark性能优化的核心但从Glue Data Catalog的角度看分区不是越多越好。我见过有人把表按小时分区一天24个分区跑了一年就是8760个分区一张中等规模的表手动查一个SHOW PARTITIONS要卡好几秒。在这种场景下即使查询能走分区裁剪元数据枚举的延迟也会拖慢整体任务。从实践来看日分区是数仓最合理的默认选择。你需要的报表和跑批任务绝大多数都是“按天产出、按天消费”小时级分区尽量只在实时性要求极高的场景用。另外如果你的表是按天分区但一天有多批次数据可以通过INSERT OVERWRITE覆盖当天分区而不是把每小时都拆成一个分区。分区数量过大还有一个隐患Glue Data Catalog的API有配额默认情况下单个账号每秒钟的API调用次数有上限。如果一张表的分区特别多你的Spark任务在做全表扫描时触发的分区枚举API调用会非常密集很容易触发限流表现就是任务间歇性失败报ThrottlingException。如果已经有一张大分区表可以考虑用ALTER TABLE ... ARCHIVE PARTITION或者干脆做历史分区的冷热分离。冷数据挪到单独的表或单独的库不仅Metastore轻了查询速度也会快很多。5.3 什么时候要升级到Lake FormationGlue Data Catalog只是一个元数据目录它本身不提供库表级别的细粒度权限。EMR上的Hive和Spark默认是通过IAM的glue:GetTable这类权限来控制访问粒度到表。如果你需要列级、行级的安全控制那就需要上Lake Formation。Lake Formation跟Glue Data Catalog是深度集成的你可以理解成给Glue Data Catalog套了一层统一鉴权层。Hive和Spark通过Lake Formation访问Glue里的元数据时会把用户的IAM身份传过去Lake Formation再做细粒度鉴权然后返回允许访问的数据范围。升级到Lake Formation不是必须的。如果你的团队规模不大、权限控制只到库或表级别IAM策略完全够用。但如果你做的是金融、医疗这类对数据安全要求极高的项目需要审计和细粒度控制那尽早规划Lake Formation相关的改造会省更多后期的麻烦。但有一个现实问题Lake Formation的集成配置比单纯用Glue Data Catalog复杂需要在EMR的安全配置里打开相关选项还要为计算引擎配置专门的集成角色。上线前一定要在测试环境多验证几轮别直接上生产。6. 实操总结与效率提升经验集成Glue Data Catalog这件事本质上不是“配一下就能跑”的问题而是一套元数据中心化的架构改造。完成Hive和Spark两个引擎的配置后你的数仓体系等于有了一个稳定的数据地图层后续开发、查询、权限控制都在这张地图上操作效率和可维护性都会明显提升。我在实际使用中感受最深的一点是这套方案彻底省掉了“表结构同步”的日常操作。以前Hive建表、Spark还要同步信息两边哪怕只差一个字段跑批任务就可能挂掉。现在两边读的是同一份元数据这个问题基本消失了开发时间省出来不少。最后分享两个小技巧。第一个EMR集群如果经常销毁重建建议把Glue相关的配置封装成一个独立的glue-config.json文件纳入代码库管理不要每次手动在控制台里点。这样新建集群时一条CLI命令就能复现完全相同的Metastore配置避免人为遗漏。第二个在跑Spark作业前先在spark-sql里执行一条SHOW TABLES确认能连上Glue这个习惯能帮你早5分钟发现配置问题而不是等作业跑了一半才报错。这些小细节看起来不起眼但在生产环境里就是实实在在的稳定性和效率。
返回列表