ARTICLE DETAIL

资讯详情

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

Hive基础、安装配置、SQL语法与数据倾斜治理实战笔记

Hive基础、安装配置、SQL语法与数据倾斜治理实战笔记 做数仓这行的人几乎绕不开 Hive。哪怕现在 Spark SQL、Flink SQL、各种湖仓一体方案满天飞只要你翻开任何一家中大型公司的离线数仓代码库Hive SQL 依然是那个占比最高的角色。我从最早用 Hive 0.13 那会儿开始写 SQL中间被元数据搞崩过 MySQL、被数据倾斜拖到凌晨三点、被collect_list出来的乱序坑过也经历过从 MapReduce 引擎切到 Tez 之后任务时间直接砍掉一半的爽感。这篇东西不算教程就是我把这些年关于 Hive 基础知识、安装配置、SQL 语法、行转列列转行、数据倾斜处理、常见报错排查的个人笔记重新梳理了一遍该说透的地方会说透该给参数的地方会给参数。不管你是刚接触 Hive 的小白还是写了两三年 SQL 想补一补底层原理的老手应该都能从里面捞到点东西。1. Hive到底是个什么东西先把本质讲透1.1 一句话讲清Hive的本质很多人刚学 Hive 会误以为它是个数据库其实不对。Hive 本质上是一个把 SQL 翻译成分布式计算任务的翻译器它自己不存数据也不负责计算。真正的数据躺在 HDFS 上真正的计算交给 MapReduce、Tez 或者 Spark 去跑Hive 干的事情是把一条人写得出来的 SQL经过解析、编译、优化变成一堆能在集群上跑的作业。这个定位决定了三件事。第一Hive 的查询延迟注定是分钟级的它不是给在线业务用的别拿它跟 MySQL 比响应速度。第二Hive 的表只是元数据 HDFS 目录的映射你把表删了数据可能还在也可能一起没了这取决于你是内表还是外表。第三Hive 的能力上限受限于底层计算引擎所以同一个 SQL 换引擎跑出来的耗时可能差好几倍这就是为什么大家都要把 Tez 配上。我一般跟新人这么解释Hive 就像餐厅的服务员你把菜名SQL告诉他他跑到后厨Hadoop 集群去下单厨师MapReduce/Tez做菜服务员再把菜端回来。服务员自己不会做菜也不存菜但他能让你不用进后厨就能点餐。1.2 四个核心组件各自干什么活Hive 的架构拆开来真正需要记住的是这四块组件干什么你要关心什么用户接口层CLI、Beeline、JDBC/ODBC、Web UI用哪种方式提交任务超时和并发怎么控制Driver 驱动器解析 SQL、生成执行计划、调度作业执行计划长什么样怎么用 explain 看MetaStore存库、表、分区、字段、存储位置等元数据元数据放哪备份怎么做挂了怎么办Hadoop 集群存 HDFS 数据 跑计算任务资源够不够引擎选哪个MetaStore 这块我要单独强调一下因为它是最容易被忽视、出问题却最致命的部分。默认情况下 Hive 用内嵌的 Derby 数据库存元数据问题是 Derby同一时刻只允许一个会话连接你开两个终端窗口跑 Hive第二个必定报错。所以生产环境一律换成 MySQL 或者 PostgreSQL团队协作时必须这么干不然连表都建不了。我见过不止一个新人装完 Hive 发现怎么只能开一个窗口纠结半天就是这个原因。1.3 内表和外表删表那一刻才见真章内表Managed Table和外表External Table的区别是所有 Hive 面试题里的常客但很多人只是背了结论没理解为什么。内表建表时不加EXTERNALHive 认为这份数据归自己管。DROP TABLE的时候元数据和 HDFS 上的数据一起删掉。外表建表时加EXTERNAL并指定LOCATIONHive 只负责读DROP TABLE只删元数据HDFS 上的文件原封不动。-- 内表 CREATE TABLE inner_user ( id BIGINT, name STRING ) ROW FORMAT DELIMITED FIELDS TERMINATED BY \t; -- 外表 CREATE TABLE ext_user ( id BIGINT, name STRING ) ROW FORMAT DELIMITED FIELDS TERMINATED BY \t LOCATION /warehouse/ods/ext_user;选型逻辑很简单别人给你的数据、原始日志、多个团队共享的数据一律用外表。因为一旦误删数据还在重建表指一下目录就恢复了损失可控。只有那种完全由 Hive 自己生产、中间过程性质的临时表才用内表。注意外表删表不删数据看着很安全但也意味着你以为删干净了其实没删HDFS 空间一直在占着。线上做数据清理的时候光DROP TABLE是不够的还得手动去清LOCATION指向的目录这一步千万别忘。2. 从零把Hive装起来安装与配置的完整路径2.1 前置依赖和版本匹配是第一道坎装 Hive 九成的失败不是因为 Hive 本身难装而是因为版本没对齐。我总结了一套稳妥的搭配自己用和带新人都跑得通组件推荐版本说明JDK1.8Hive 3.x 对 JDK 11 支持不完善别图新Hadoop3.1.x ~ 3.3.x与 Hive 3.1.x 配合最稳Hive3.1.2 / 3.1.3生产上最广泛的两个小版本MySQL5.7 或 8.0存元数据5.7 更省心mysql-connector-java5.1.47 或 8.0.2x必须和 MySQL 版本对应顺序不能乱JDK → Hadoop → MySQL → Hive。Hadoop 必须先能正常跑起来hdfs dfs -ls /能出结果、yarn node -list能看到节点再动 Hive。很多人跳过这步直接装 Hive结果报一堆连接异常白折腾。环境变量这块HADOOP_HOME和HIVE_HOME都要配并且HIVE_HOME/bin要加进PATH。Hive 启动时会去找HADOOP_HOME找不到就直接起不来。2.2 元数据换成MySQL配置要点逐条说把元数据库从 Derby 切到 MySQL一共就四步但每一步都有坑。第一步在 MySQL 里建库建用户CREATE DATABASE hive_metastore DEFAULT CHARSET utf8mb4; CREATE USER hive% IDENTIFIED BY Hive2024; GRANT ALL PRIVILEGES ON hive_metastore.* TO hive%; FLUSH PRIVILEGES;字符集必须指定utf8mb4不然中文注释和字段名可能乱码。授权用%方便远程访问但生产环境建议限定具体 IP。第二步把mysql-connector-java-x.x.x.jar丢进$HIVE_HOME/lib。这一步最容易漏漏了之后报的是java.sql.SQLException: No suitable driver看到这个报错先去检查 jar 在不在。第三步初始化元数据库 schemaschematool -dbType mysql -initSchema如果报版本不兼容可以用-initSchemaTo 3.1.0指定版本。初始化成功会打印Initialization script completed。这一步只能执行一次重复执行会报表已存在。第四步改hive-site.xmlconfiguration property namejavax.jdo.option.ConnectionURL/name valuejdbc:mysql://node01:3306/hive_metastore?useSSLfalseamp;createDatabaseIfNotExisttrueamp;characterEncodingUTF-8/value /property property namejavax.jdo.option.ConnectionDriverName/name valuecom.mysql.jdbc.Driver/value /property property namejavax.jdo.option.ConnectionUserName/name valuehive/value /property property namejavax.jdo.option.ConnectionPassword/name valueHive2024/value /property property namehive.metastore.warehouse.dir/name value/user/hive/warehouse/value /property property namehive.metastore.schema.verification/name valuefalse/value /property /configurationconnectionURL里那个必须写成amp;这是 XML 的转义规则不转义直接解析报错这个坑我踩过。hive.metastore.schema.verification设成false是为了避免每次启动都去校验 schema 版本本地开发省事生产上建议开着。2.3 把Tez接进来性能提升最明显的一步Hive 默认用 MapReduce 执行每个 SQL 阶段都要落盘多个阶段串联就是几十次磁盘 IO。换成 Tez 之后多个阶段可以合并成一个 DAG 一次性执行完中间结果不落盘实测同一张表、同一个统计 SQL耗时能压到原来的三分之一到二分之一小文件多的情况下差距更夸张。配置分两步。先在hive-site.xml里指定引擎并打开 Tezproperty namehive.execution.engine/name valuetez/value /property property namehive.tez.container.size/name value2048/value /property property namehive.tez.java.opts/name value-Xmx1638m/value /property然后是 Tez 自己的tez-site.xmlproperty nametez.lib.uris/name value${fs.defaultFS}/apps/tez/tez-0.9.2.tar.gz/value /property property nametez.use.cluster.hadoop-libs/name valuefalse/value /propertytez.lib.uris指向的包要提前传到 HDFS 上做法是把 Tez 解压后打成一个 tar.gz 上传。tez.use.cluster.hadoop-libs建议设false让 Tez 用自己带的 Hadoop 依赖能规避一大类版本冲突问题后面讲报错的时候还会提到它。提示容器内存hive.tez.container.size和 JVM 堆hive.tez.java.opts之间要留出约 20% 的余量给堆外内存和栈2048 配 1638m 就是按这个比例来的。堆设得跟容器一样大大概率会被 YARN 干掉报Container killed by YARN for exceeding memory limits。2.4 在线实训环境里练手要注意什么如果你是在在线实训平台上练 Hive会发现它和真实集群有几个差异得提前知道。第一环境通常已经装好了你不需要自己装但端口和路径是预设的照着文档里的地址来别自己改。第二元数据可能每次重置你上一关建的表下一关就没了所以脚本要能重复执行用CREATE TABLE IF NOT EXISTS和DROP TABLE IF EXISTS打头。第三这类环境资源有限跑大表聚合容易超时测试数据尽量控制在几万行以内。在实训环境里最值得花时间练的不是建表语句而是行转列、列转行、窗口函数、数据倾斜模拟这四类。前三个是日常写 SQL 的手感第四个是排查问题的直觉。手感这东西只能靠敲看教程没用。3. Hive CLI任务类型与SQL执行链路3.1 三种提交方式的区别和适用场景Hive 提交任务主要有三条路别混着用各有各的场景第一种原生 CLIhive命令hive -e SELECT count(*) FROM dwd_user; hive -f /home/hadoop/job/user_stat.sql-e直接跟 SQL适合快速验证-f跟脚本文件适合跑固定作业。这两种写法在调度系统里用得最多crontab或者 DolphinScheduler 里配置的就是这种命令。缺点是只能串行提交一次只能跑一个会话。第二种Beelinebeeline -u jdbc:hive2://node01:10000/default -n hive -p hiveBeeline 是走 HiveServer2 的 JDBC 客户端支持多会话、支持连接远程集群是生产环境的标准姿势。它和原生 CLI 最大的区别是资源隔离。多个用户同时通过 Beeline 连 HiveServer2服务端统一管理连接池不会互相干扰。第三种JDBC 直连Java 应用里通过 JDBC 驱动连 HiveServer2适合把 Hive 查询嵌到业务系统里做报表。但要注意HiveServer2 连接很重别在业务代码里频繁开关连接一定要用连接池。方式场景并发能力备注hive -e/-f脚本、定时调度弱简单直接调试首选Beeline多人协作、远程访问强生产标准JDBC嵌入应用、报表强需要连接池3.2 一条SQL从敲下回车到出结果中间发生了什么理解这条链路排查问题时能少走一大半弯路。完整流程是这样的解析ParseDriver 用 ANTLR 把 SQL 文本切成语法树。语法错误、关键字拼错在这一步就报出来报错信息里的line x:x就是这个阶段给的。语义分析Semantic Analysis去 MetaStore 查表存不存在、字段对不对、类型能不能匹配。报Table not found、Invalid column reference都是这一步。逻辑计划生成与优化生成 Operator Tree然后跑一堆优化规则比如谓词下推把WHERE条件下推到 TableScan、列裁剪只读用到的列、分区裁剪只扫命中的分区。物理计划生成把逻辑计划转成具体的 Tez/MapReduce 任务决定几个 Map、几个 Reduce、怎么 shuffle。执行提交给 YARN跑完把结果写到临时目录或者直接返回。这里面最值得关注的是第 3 步的分区裁剪和列裁剪。执行时把裁剪打开SET hive.optimize.ppdtrue; -- 谓词下推 SET hive.optimize.index.filtertrue; SET hive.cbo.enabletrue; -- 基于代价的优化我用EXPLAIN看过很多次执行计划最常见的性能问题就是分区裁剪没生效。比如你写WHERE dt 2024-01-01但如果dt字段建表时类型写成了int而值给的是字符串裁剪就会失效直接全表扫。这类问题不看执行计划是发现不了的。EXPLAIN SELECT count(*) FROM dwd_order WHERE dt 2024-01-01;养成习惯复杂 SQL 上线前先EXPLAIN看一眼重点看输入的分区数和是否走了全表扫描。3.3 数据类型里的两个大类基本类型和复杂类型很多人问两个类型是什么意思在 Hive 基础里最值得掰开讲的就是基本数据类型和复杂数据类型这两大类。基本类型基本就是 MySQL 那套的简化版TINYINT、SMALLINT、INT、BIGINT、FLOAT、DOUBLE、DECIMAL、BOOLEAN、STRING、VARCHAR、CHAR、TIMESTAMP、DATE。这里有两个必须记住的点一是Hive 的 STRING 相当于 MySQL 的 VARCHAR没有长度限制所以日期一律用STRING存2024-01-01比用DATE更省事也避免时区坑二是金额千万别用 DOUBLE浮点精度会出问题一律DECIMAL(20,2)。复杂类型是 Hive 的特色也是它区别于传统数据库的地方一共三种CREATE TABLE complex_demo ( id BIGINT, tags ARRAYSTRING, -- 数组 attrs MAPSTRING, STRING, -- 键值对 addr STRUCTprovince:STRING, city:STRING, street:STRING -- 结构体 );ARRAY用来存一个用户的多标签MAP用来存不定长的属性STRUCT用来存层级固定的对象。这三种类型是后面行转列、列转行的基础取值的语法分别是tags[0]、attrs[k]、addr.city。注意复杂类型虽然好用但写 SQL 的时候要格外小心。MAP类型的键如果不存在取出来是NULL而不是报错这会导致WHERE attrs[level] vip静默过滤掉一堆数据。我建议凡是MAP取值的条件都显式写成WHERE attrs[level] IS NOT NULL AND attrs[level] vip虽然啰嗦但能避坑。4. Hive SQL高频语法与行列转换实战4.1 建表、改表、改表名的SQL语句速查日常最常用的 DDL 就那几个我把它们和坑点列在一起方便随时翻。改表名ALTER TABLE dwd_user RENAME TO dwd_user_bak;Hive 3.x 里改表名会同时改 HDFS 目录名内表外表则只改元数据。改名之前确认没有下游任务依赖旧表名不然调度直接挂。加字段这个最常用ALTER TABLE dwd_user ADD COLUMNS (age INT COMMENT 年龄);加字段默认加在最后而且对已有分区不会回填老分区查这一列全是NULL。想调整到指定位置得用CHANGE COLUMN配合AFTER关键字。ALTER TABLE dwd_user CHANGE COLUMN age age INT COMMENT 年龄 AFTER name;改字段类型ALTER TABLE dwd_user CHANGE COLUMN id id STRING;改类型有个大坑Hive 只改元数据不动底层文件。所以INT改STRING是安全的读的时候按字符串解析但STRING改INT就要保证原有数据真的能转成数字否则查询时报转换异常。分区相关的操作用得比建表还频繁ALTER TABLE dwd_order ADD IF NOT EXISTS PARTITION (dt2024-01-01); ALTER TABLE dwd_order DROP IF EXISTS PARTITION (dt2024-01-01); SHOW PARTITIONS dwd_order;ADD PARTITION只是在元数据里登记它不会去创建 HDFS 目录这点和很多人想的不一样。如果目录不存在查这个分区会返回空。所以正确的做法是数据先落到 HDFS再ADD PARTITION或者用LOAD DATA让它自动创建。4.2 行转列collect_list和concat_ws的经典组合行转列就是把多行压成一行。最典型的场景是一个用户有多条订单你要把它拼成一个字符串放进一行里。先看数据uid item u1 苹果 u1 香蕉 u1 橙子 u2 书目标uid items u1 苹果,香蕉,橙子 u2 书写法SELECT uid, concat_ws(,, collect_list(item)) AS items FROM user_item GROUP BY uid;拆开讲这两个函数。collect_list会把分组内的值收集成一个数组保留重复和顺序collect_set会去重但顺序不保证。concat_ws用指定分隔符把数组拼成字符串。这里有三个坑必须知道。第一collect_list的顺序在理论上是不确定的它依赖数据到达 Reduce 的顺序。如果你要严格保序正确姿势是先在子查询里用ORDER BY加一个排序字段或者用sort_arraySELECT uid, concat_ws(,, sort_array(collect_list(item))) AS items FROM user_item GROUP BY uid;第二collect_list会把NULL也收进数组concat_ws虽然会跳过NULL但数组长度和实际输出可能对不上。稳妥起见在子查询里先WHERE item IS NOT NULL。第三数据量大时collect_list可能 OOM。一个用户几百万条记录全塞进一个数组内存直接爆。这种场景要么先LIMIT要么改成分页拼接别硬扛。4.3 列转行explode配lateral view是标准答案列转行是反过来把一行拆成多行最典型的场景是把ARRAY或者MAP类型的字段展开。数据uid tags u1 [苹果,香蕉,橙子]目标uid tag u1 苹果 u1 香蕉 u1 橙子标准写法SELECT uid, tag FROM user_tags LATERAL VIEW explode(tags) tmp AS tag;explode是 UDTF用户自定义表生成函数它能把一行变成多行但它不能和普通字段一起出现在 SELECT 里这就是必须用LATERAL VIEW的原因。LATERAL VIEW的作用是给explode出来的结果侧向关联回原表让你还能带上uid这样的字段。tmp是虚拟表别名tag是列别名这两个名字随便起但必须写。拆分MAP类型稍微绕一点SELECT uid, key, value FROM user_attrs LATERAL VIEW explode(attrs) tmp AS key, value;最容易翻车的点是空数组。如果tags是空数组或者NULLexplode一行都不会输出整个用户直接消失。很多人数仓报表做出来总数对不上就是因为这个。解决办法有两种-- 方案一先过滤掉空的不需要保留这些用户时 SELECT uid, tag FROM user_tags LATERAL VIEW explode(tags) tmp AS tag WHERE tags IS NOT NULL AND size(tags) 0; -- 方案二保留空值用 outer 关键字 SELECT uid, tag FROM user_tags LATERAL VIEW OUTER explode(tags) tmp AS tag;加OUTER之后空数组会输出一行tag为NULL的记录用户不会丢。这个细节我觉得是 Hive 基础里最该记住的一条实际工作里救过我好几次。行转列和列转行的对比我整理成一张表方向核心函数典型场景主要坑点行转列collect_list / collect_set concat_ws多行标签合并、订单拼接顺序不确定、NULL 混入、大数组 OOM列转行explode lateral view数组/Map 展开、标签打散空数组丢行、必须加 LATERAL VIEW4.4 cube、rollup、grouping sets一次算出多维度这三个是 Hive 里做多维聚合的语法写报表的时候能省掉一堆UNION ALL。GROUPING SETS最灵活你指定要哪几个维度组合SELECT province, city, count(*) AS cnt FROM dwd_order GROUP BY province, city GROUPING SETS ((province, city), (province), ());WITH ROLLUP是层级递进从最细粒度往上卷SELECT province, city, count(*) AS cnt FROM dwd_order GROUP BY province, city WITH ROLLUP;它会输出(province,city)、(province)、()三层。WITH CUBE是全排列所有维度组合都算一遍SELECT province, city, channel, count(*) AS cnt FROM dwd_order GROUP BY province, city, channel WITH CUBE;三个维度会产生 8 种组合。注意这是指数级增长4 个维度 16 种5 个维度 32 种落到 Reduce 上就是 32 份数据。我见过有人对一个有 7 个维度的表用 CUBE结果任务跑了六个小时纯粹是维度爆炸。实用性排序是GROUPING SETS ROLLUP CUBE能用前两个就别用 CUBE。还有个配套函数GROUPING__ID用来判断某一行属于哪个组合输出一个数字SELECT province, city, GROUPING__ID, count(*) AS cnt FROM dwd_order GROUP BY province, city WITH CUBE;拿到GROUPING__ID之后下游可以据此区分这个NULL是真没有这个省份还是这一行是汇总行。这是CUBE输出里一个很隐蔽的点汇总行的维度字段也是NULL跟真实数据里的NULL混在一起分不清必须靠GROUPING__ID或者grouping()函数区分。5. 数据倾斜从现象到解决的完整套路5.1 怎么判断是不是数据倾斜数据倾斜的表现非常典型任务卡在 99%长时间不动最后几个 Reduce 跑得极慢。你去 YARN 上翻日志会看到一个 Reduce 处理了几千万条记录其他 Reduce 几分钟就完事了。判断方法也很直接看 Map 和 Reduce 阶段的耗时对比。如果 Map 阶段很快就完了Reduce 阶段有一两个任务拖到最后基本就是倾斜。还有一个更直观的看 distinct count 或者 group by 的 key 分布。SELECT key, count(*) AS cnt FROM dwd_log GROUP BY key ORDER BY cnt DESC LIMIT 20;如果第一名是第二名的一百倍以上倾斜就成立了。最常见的三个元凶是空值NULL、热点 Key比如某个默认 ID、爬虫 IP、大小表 Join 时大表 Key 集中。空值导致倾斜是最容易被忽略的。GROUP BY时所有NULL会跑到同一个 Reduce 上如果数据里NULL占了三成那这个 Reduce 必然爆。5.2 三种典型场景的对症打法场景一空值倾斜处理方式很粗暴但有效——给空值加随机后缀SELECT COALESCE(key, concat(null_, cast(rand() * 100 as int))) AS key, count(*) FROM dwd_log GROUP BY COALESCE(key, concat(null_, cast(rand() * 100 as int)));这样空值被打散成 100 份均匀分布到不同 Reduce 上。注意如果业务上不需要统计空值直接WHERE key IS NOT NULL更简单。场景二热点Key倾斜比如有个shop_id 0的店铺占了六成数据。打法是把热点 Key 单独拎出来处理-- 第一步非热点数据正常聚合 SELECT shop_id, count(*) FROM dwd_order WHERE shop_id 0 GROUP BY shop_id UNION ALL -- 第二步热点数据加随机前缀打散 SELECT shop_id, sum(cnt) FROM ( SELECT shop_id, count(*) AS cnt FROM dwd_order WHERE shop_id 0 GROUP BY shop_id, cast(rand() * 10 as int) ) t GROUP BY shop_id;思路就是把一个大 Key 拆成 10 个小 Key 先局部聚合再合并。场景三小表 Join 大表倾斜如果小表不大几百 MB 以内直接开 Map Join让小表广播到每个 Map 端根本不进 Reduce倾斜自然消失SET hive.auto.convert.join true; SET hive.mapjoin.smalltable.filesize 25000000; -- 25MB 阈值hive.mapjoin.smalltable.filesize默认是 25MB如果小表有 50MB调大到50000000就行。这个参数是我用得最多的一个效果立竿见影。5.3 参数层面的兜底手段除了改 SQL还有几个参数可以缓解倾斜。开启负载均衡SET hive.groupby.skewindata true;开启后 Hive 会起两轮 MR第一轮给 key 加随机前缀做预聚合第二轮再去掉前缀做最终聚合。代价是多一轮作业整体耗时会增加所以只在确实倾斜时开。控制 Reduce 数量SET hive.exec.reducers.bytes.per.reducer 256000000; -- 每个 Reduce 处理 256MB SET hive.exec.reducers.max 1009;Reduce 数量是按输入数据量除以bytes.per.reducer算出来的设太小会导致单个 Reduce 数据量过大。我自己习惯把这个值设在 256MB ~ 512MB 之间。实操心得排查倾斜最快的路径是——先看 YARN 上各 Reduce 的记录数分布找出异常的那个再拿它的 key 去原表里GROUP BY数一遍看是不是某个值特别多最后判断是空值、热点还是 Join 引起的对号入座。这三步走下来一般十五分钟内能定位。6. 常见报错与排查技巧实录6.1 启动类报错从NoClassDefFoundError说起java.lang.NoClassDefFoundError: org/apache/hadoop/crypto/...这类报错我遇到过好几次本质是classpath 里的 jar 版本不一致。这个类在hadoop-common里报找不到通常有三种原因一是$HIVE_HOME/lib下同时存在多个不同版本的hadoop-commonjar加载顺序不确定二是 Tez 运行时用的是集群上的 Hadoop 包版本比 Hive 依赖的低缺了这个类三是HADOOP_HOME环境变量指错了地方。排查顺序我固定这么走# 1. 看 lib 下有几个 hadoop-common ls -l $HIVE_HOME/lib | grep hadoop-common # 2. 确认 Hadoop 版本 hadoop version # 3. 查这个类在哪个 jar 里 for j in $HIVE_HOME/lib/*.jar; do unzip -l $j 2/dev/null | grep -q org/apache/hadoop/crypto echo $j done解决办法清掉$HIVE_HOME/lib下低版本的 hadoop 相关 jar只保留与集群一致的那一份。如果是 Tez 引起的把tez.use.cluster.hadoop-libs设成false让 Tez 用自带的依赖这类问题基本就消失了。另一类启动报错是元数据连不上最常见的是Unable to instantiate org.apache.hadoop.hive.metastore.HiveMetaStoreClient。九成是 MySQL 连不通或者驱动 jar 没放。先mysql -uhive -p手动连一次能连上再去查 jar。6.2 运行期报错内存、小文件、动态分区报错一java.lang.OutOfMemoryError: Java heap spaceHive 场景里这个多半是collect_list或者大JOIN引起的。处理方式先调 Map/Reduce 内存SET mapreduce.map.memory.mb 4096; SET mapreduce.map.java.opts -Xmx3276m; SET mapreduce.reduce.memory.mb 8192; SET mapreduce.reduce.java.opts -Xmx6553m;但我不建议一上来就调内存。先想想 SQL 本身能不能优化是不是可以不collect_list、能不能先过滤再 JOIN。内存只是把问题往后推数据量再涨一倍还是会爆。报错二动态分区写入报错或者写不进去SET hive.exec.dynamic.partition true; SET hive.exec.dynamic.partition.mode nonstrict;这两个必须同时开。只开第一个模式还是strict要求至少有一个静态分区写纯动态分区会报错。另外动态分区写入大量小分区时要开hive.merge.mapfiles合并小文件SET hive.merge.mapfiles true; SET hive.merge.mapredfiles true; SET hive.merge.size.per.task 256000000; SET hive.merge.smallfiles.avgsize 16000000;报错三Vertex failed/Container killed by YARN这是 Tez 上的典型错误。八成是容器内存不够或者堆内存设得跟容器一样大被 YARN 干掉了。检查hive.tez.container.size和hive.tez.java.opts的配比堆内存留出 20% 余量。6.3 报错速查表实战里翻得最多的就是这张表我把它整理出来出问题时直接对号入座报错信息大概率原因处理方式NoClassDefFoundError: org/apache/hadoop/cryptojar 版本冲突清理重复 hadoop jarTez 设use.cluster.hadoop-libsfalseNo suitable driver缺 MySQL 驱动把 mysql-connector jar 放进$HIVE_HOME/libTable not found元数据没同步检查 MetaStore 连接确认表在正确的库Java heap space聚合数据量过大优化 SQL 优先其次调 map/reduce 内存Container killed by YARN内存超限调容器内存堆留 20% 余量动态分区写不进去模式未放开dynamic.partition.modenonstrictReduce 卡在 99%数据倾斜按第 5 章的三类场景对症处理输出大量小文件分区过多 / Reducer 过多开 merge控制 reduce 数量注意小文件问题在 Hive 里是慢性病。每个分区几十个 KB 的文件NameNode 上每个文件都要占一份元数据内存几百万个小文件能把 NameNode 拖垮。所以写完数据顺手 merge 一下这个习惯值得养成。另外长任务记得设超时mapreduce.task.timeout默认 600 秒超过十分钟没有心跳就会被判定为僵死任务杀掉数据量大的任务容易踩到。6.4 我踩过的几个不写在文档里的坑有几个问题官方文档基本不提但实际会碰上。第一个是中文分区值。分区字段写中文在某些文件系统上会乱码导致分区读不出来。我的做法是分区值一律用英文或者数字中文放到普通字段里。第二个是ORDER BY和SORT BY的区别导致的性能雪崩。ORDER BY是全局排序只会用一个 Reduce数据量一大直接卡死SORT BY是每个 Reduce 内部局部排序可以并行。做排序展示的时候先DISTRIBUTE BY再SORT BY效果接近全局有序但快得多。SELECT * FROM dwd_order DISTRIBUTE BY dt SORT BY amount DESC;第三个是浮点数比较。WHERE amount 0.1这种写法在DOUBLE类型上可能查不到数据因为二进制表示有误差。解决办法就是前面说的金额一律DECIMAL或者用区间比较WHERE amount BETWEEN 0.0999 AND 0.1001。第四个是时区。from_unixtime出来的时间默认按集群时区走如果集群没配结果可能差 8 小时。所有跟时间相关的 UDF 用之前先确认一遍集群时区设置或者干脆统一在应用层处理。这几条看着零碎但都是真金白银换来的。我个人的体会是Hive 这东西语法不难难的是知道它在什么情况下会不按你想的走。把分区裁剪看清楚、把空数组的丢行问题记住、把倾斜的三类场景烂熟于心、把 jar 版本对齐日常九成的问题就都能自己解决了。剩下的那些就得靠多跑任务、多看执行计划、多翻 YARN 日志慢慢攒经验没有捷径。
返回列表