大数据第三篇:千万行数据灌进 HBase 要多久?ImportTsv 与 psql.py 双路线实测(附小内存并行度红线)【千万行导入】
本文用 10,000 台设备 × 每台 1000 行 = 1000 万行物联网数据实测两条导入路线:MapReduce 版 ImportTsv 约 4 分钟,Phoenix 批量版 psql.py 约 32 分钟;同时给出小内存机器上客户端并行数的"死亡红线"——超了 RegionServer 会被 OOM Killer 击杀。
目录
- 一、实验设计:表怎么建,数据长什么样
- 二、行键设计:桶号列 vs SALT_BUCKETS
- 三、生成 403MB 的 TSV 数据文件
- 四、路线一:ImportTsv(MapReduce 导入)
- 五、路线二:psql.py(Phoenix 批量导入)
- 六、★小内存并行度红线:4 个进程差点打挂集群
- 七、查询耗时实测与结果校验
- 八、写在最后
一、实验设计:表怎么建,数据长什么样
模拟最常见的物联网场景:一万台设备持续上报温度。全流程约束:不写脚本文件、不编译 Java、不新增工具,只用集群自带命令。
| 项目 | HBase 原生版(iot 表) | Phoenix 版(IOT 表) |
|---|---|---|
| 表名 | iot | IOT |
| 列族/列 | cf1(temp/humid/status) | CF1.TEMP 等 |
| 预分区 | 建表时 SPLITS 16 region | SALT_BUCKETS=16 |
| 行键 | 桶号|设备号|时间戳 | 设备号|时间戳 |
| 数据量 | 10,000 设备 × 1000 行 = 10,000,000 行 | 同左 |
时间戳步长 2592000ms(约 43 分钟)一条,10 万批正好覆盖 30 天,让"按时间范围查"也有真实意义。
二、行键设计:桶号列 vs SALT_BUCKETS
千万行数据最怕写热点——所有设备都往同一个 region 写。两种打散方式各有代价:
- 显式桶号列(HBase 原生版):行键最前面拼
设备序号%16的两位桶号,建表手工指定 15 个 split 点。好处是"按设备前缀查"能定位到单一 region,实测 0.049 秒;代价是行键多 3 个字符,查询时要自己拼桶号。 - SALT_BUCKETS=16(Phoenix 版):Phoenix 自动在行键前加盐字节,写入均匀。代价是 salt 对查询端不可见,按设备查必须 16 路并行扫,实测 0.083 秒(EXPLAIN 显示 16-CHUNK PARALLEL 16-WAY)。
结论一句话:"写打散"用 SALT_BUCKETS,"读前缀单命中"用显式桶号列,二者只取其一。本实验两种都建,正好对比。
三、生成 403MB 的 TSV 数据文件
在 hadoop1 上用一条 awk 生成(参数:设备号 = 序号对 10000 取模,时间戳 = 基准 + 批次×2592000 + 随机抖动):
awk'BEGIN{ srand(42); for(i=0;i<10000000;i++){ dev=i%10000; ts=1789091809000+int(i/10000)*2592000+int(rand()*300000); printf "dev%06d|%d\t%.1f\t%.0f\t%d\n", dev, ts, 20+rand()*15, 40+rand()*30, i%4; } }'>/opt/pkg/iot_data.tsvls-lh/opt/pkg/iot_data.tsv预期结果:约 403MB。列顺序是行键\ttemp\thumid\tstatus,ImportTsv 和 psql.py 都吃这个格式。
四、路线一:ImportTsv(MapReduce 导入)
原理:起一个 YARN 作业,把 TSV 直接写成 HBase 的 HFile,再 bulk load 进表,全程走集群并行。
# 建表(16 个预分区)hbase shell-e"create 'iot','cf1',SPLITS=>['01','02','03','04','05','06','07','08','09','10','11','12','13','14','15']"# 上传数据hdfs dfs-mkdir-p/data/iot hdfs dfs-put/opt/pkg/iot_data.tsv /data/iot/# 导入(命令较长,分两行写)hbase org.apache.hadoop.hbase.mapreduce.ImportTsv\-Dimporttsv.separator=\|\-Dimporttsv.columns=HBASE_ROW_KEY,cf1:temp,cf1:humid,cf1:status\iot /data/iot/iot_data.tsv预期结果:作业完成后hbase shell里count 'iot'(开 INTERVAL/CACHE)确认 1000 万行。实测 1000 万行约 4 分钟。
★ 这条命令直跑会报 ClassCastException(HBase 自带的 hadoop-2.10.2 jar 混进 MR classpath)。零文件改动的解法:用
hbase classpath输出手工拼 classpath、排除所有 2.10.2 jar 后直接java org.apache.hadoop.hbase.mapreduce.ImportTsv ...启动。完整命令太长,思路就是"客户端绕开 bundled jar,服务端一行不动"。
五、路线二:psql.py(Phoenix 批量导入)
原理:单客户端批量 upsert,适合 Phoenix 建的表。先装 Phoenix(系列五详解),表用SALT_BUCKETS=16:
# Phoenix 里建表(行键不含桶号)sqlline.py hadoop1:2181-e"CREATE TABLE IF NOT EXISTS IOT \ (PK VARCHAR PRIMARY KEY, CF1.TEMP VARCHAR, CF1.HUMID VARCHAR, CF1.STATUS VARCHAR) \ SALT_BUCKETS=16;"# psql.py 按文件扩展名判定格式,分片必须带 --additional-suffix=.csv,否则报# "Don't know how to interpret argument"(这是格式识别问题,不是权限问题!)split-l2500000--additional-suffix=.csv /opt/pkg/iot_data.tsv /opt/pkg/part_# 2 个进程并行导入cd/home/hduser/phoenix setsidnohup./bin/psql.py-tIOT hadoop1:2181 /opt/pkg/part_aa>/tmp/psql_aa.log2>&1&setsidnohup./bin/psql.py-tIOT hadoop1:2181 /opt/pkg/part_ab>/tmp/psql_ab.log2>&1&预期结果:每进程约 3000 行/秒,250 万行一片约 15 分钟,2 并行总共约 32 分钟。
六、★小内存并行度红线:4 个进程差点打挂集群
这是我们实测里最惊险的一幕。3.5G 可用内存的节点上开了 4 个 psql.py 并行:
| 并行度 | Mem available | 后果 |
|---|---|---|
| 4 进程(-Xmx192m) | 310MB | hbase shell 失响应,RegionServer 随时被 OOM Killer 击杀 |
| 2 进程 | 789MB | 一切正常 |
经验红线:客户端并行数 × 每进程约 300MB ≤ 节点空闲内存的一半。本例 2 并行就是上限;要更高吞吐应把数据切片分散到 hadoop2、hadoop3 上同时跑,而不是在一台机器上堆进程。
监控命令(进程计数别用grep psql,会匹配到 grep 自己):
ps-eoargs|grep'[P]hoenixRuntime'|wc-lfree-m七、查询耗时实测与结果校验
7.1 耗时基准(导入完成后实测)
| 查询场景 | HBase 命令 | 耗时 | Phoenix SQL | 耗时 |
|---|---|---|---|---|
| 点查 | get 完整行键 | 1.11s(含首连) | WHERE PK=… | 0.061s |
| 单设备全量 | scan 拼前缀 STARTROW/ENDROW | 0.049s | LIKE ‘dev%…|%’ | 0.083s |
| 时间片范围 | 行键时间戳区间 | 0.0094s | PK 区间 | 0.085s |
| 全表 count | count(冷缓存) | 87.7s | SELECT COUNT(*) | 25~29s(走 PQS) |
三个行键设计要点:ENDROW 是开区间,前缀扫描用|~收尾;时间戳在行键尾部,时间范围查询就是前缀拼接,不需要 Filter;千万级数据用 Filter 全表扫会非常慢。
7.2 八项结果校验(全过)
COUNT(*)=10,000,000;设备数=10,000;单设备 1000 行;status 四组各约 250 万;EXPLAIN 点查 1-CHUNK、前缀查 16-CHUNK;region 在 hadoop2/3 各 8 个均匀分布;HDFS 落盘 502.9M × 2 副本。
★ 重建表必须 DROP 后 CREATE:老数据还在表里时追加导入,行键撞不撞完全不可预测,不要赌。
八、写在最后
两条路线的选择标准很简单:一次性大批量灌数用 ImportTsv(快 8 倍,但得绕 jar 坑);表是 Phoenix 建的、或要增量补数,用 psql.py(慢但省心)。比速度更重要的是并行度红线——千万行导入不是性能竞赛,把集群跑挂就得全部重来。
下一篇预告:《系列四:事故复盘——移走 27 个 hadoop-2.10.2 jar 后,HMaster 自杀了》。本文只提了一嘴的 jar 陷阱,下一篇用完整的报错证据链讲透:为什么服务端一个都不能动、为什么"MR 跑通了"不代表安全、以及怎么安全回滚。