
我第一次把物联网设备的数据塞进Hadoop的时候其实挺狼狈的。当时手头是一个智慧停车场的项目车位传感器每隔两秒上报一次状态一天就是几十万条记录一个月攒下来几千万条原先的MySQL直接卡到报表都跑不出来。后来把整条链路切到Hadoop生态才真正意识到物联网这种又大、又杂、又急的数据天生就适合分布式存储和分布式计算。这篇指南不是教科书式的概念堆砌而是把我从零装环境、接数据、做分析到踩坑排错的全过程梳理了一遍。涉及的内容包括Hadoop伪分布式搭建、集群模式切换、与Zookeeper整合、Flume数据接入、Hive分析以及作业提交到YARN的完整流程最后还整理了物联网场景下最常见的报错和调优手段。适合刚入门的数据工程师、正在做课程设计的在校生还有所有被传感器数据报表折磨过的开发者。1. 物联网数据为什么非得上Hadoop1.1 物联网数据的三座大山物联网数据有很明显的三个特征我一般跟同事开玩笑叫三座大山你一开始做这个方向就知道它们有多难缠。第一是数据量大。假设你管着1万个设备每5秒上报一条状态算下来一天就是1728万条一年就是60多亿条。这个量级放在单机数据库里基本是无解的正常聚合查询都要扫全表更别提还要保留三到五年的原始数据做回溯分析。别觉得1万个设备很多现实中一个中等规模智慧园区的传感器数量就能轻松超过这个数。第二是格式杂。不同厂商的设备上报格式完全不一样有的是JSON有的是CSV还有的是走二进制私有协议。即使都是JSON字段也经常对不齐有的设备会多出几个扩展字段有的值会丢失。传统数据库在上游格式频繁变化的时候改表结构会改到怀疑人生而Hadoop的先存储后解析模式天然能容忍这种混乱。第三是持续不断。物联网系统不像电商交易有明确的晚高峰设备是7×24小时在线的数据永远在增长。你没法用今天下班后跑个批量这种思路去应对因为每时每刻都有新数据进来。这个时候一个能线性扩展的存储底座就非常重要了加机器就能加容量这个特性对IoT业务来说几乎算刚需。1.2 传统架构在哪里卡住很多人一开始都会想MySQL数据量大就分库分表嘛或者上MongoDB不行吗这条路我不是没走过但代价很高最后发现都不是最优解。MySQL在千万级数据量下建了索引之后单条查询还凑合但一旦要做GROUP BY这类聚合索引基本失效全表扫描加上临时文件排序慢得让人绝望。分库分表确实能缓解写入压力可跨库聚合、全局排序、动态扩容这些问题每一个都能让开发团队陷入泥潭而且设备产生的数据是按时间顺序疯狂增长的分库键怎么选都能遇到热点问题。MongoDB这类文档数据库在写入和单条查询上表现不错但它擅长的是面向应用的点查而不是面向分析的大规模聚合。你让它统计过去一个月每小时的温度平均值它一样要把文档都扫出来性能并不比MySQL好到哪里去。而且它占用的存储空间普遍比列式存储大不少对IoT这种海量数据的存储成本不太友好。Hadoop方案的核心思路是把数据分散到一堆廉价机器上并行处理。HDFS负责把大文件切成块存到多个节点MapReduce或者Hive再把计算任务也分发下去每个节点只处理自己那一份数据最后把结果汇总。这种横向扩展的模式加机器就能提升容量和吞吐正好对得上物联网数据一路疯涨的节奏。理解这个思路后你就明白为什么很多物联网数据平台选择以Hadoop为底座了。1.3 Hadoop生态组件选型搭建之前要先理解每个组件的角色不然会陷入装了Hadoop却不知道用哪个的尴尬。Hadoop不是一个单一软件而是一整套生态每个成员各管一段。HDFS负责存储相当于整个系统的大硬盘YARN负责资源调度相当于操作系统里的进程管理器MapReduce是默认的批处理引擎Hive是SQL化入口让你不用手写Java代码也能做分析Zookeeper是分布式协调服务专门管像NameNode高可用这类多个节点需要商量着办事的场景Flume和Kafka负责数据接入把设备端的数据搬到HDFS里。具体到物联网项目我最常用的组合是FlumeKafkaHDFSHive。如果实时性要求不高就砍掉Kafka设备上报直接打到Flume的HTTP源或者日志源Flume再落地到HDFS。如果后续要做秒级实时监控再引入Kafka和Spark StreamingHadoop这条链路仍然可以作为离线的数据底座。Kafka在这里的意义不只是缓冲它还能解耦设备和存储设备端消费波动不会直接冲击HDFS的写入。选型建议是先跑通最简单的链路不要一开始就堆组件。后面我写的实战部分也是按简化版的架构来的等你跑通之后再逐步升级每一步都有迹可循。2. 从零开始搭建Hadoop环境2.1 环境规划与版本选择先说版本。Hadoop现在稳定版是3.3.x我建议直接用3.3.6或者更新的小版本JDK用8就行Hadoop 3对这些版本的兼容性很稳不需要去追JDK 17踩兼容性的坑。很多人一开始追求新版本结果各种依赖不匹配反而浪费大量时间。机器配置方面如果只是学习虚拟机给4GB内存就能跑伪分布式想在课程设计里演示完整集群的话至少准备三台虚拟机每台8GB内存主节点跑NameNode和ResourceManager另外两台跑DataNode和NodeManager。磁盘不用太大每台40GB足够主要是放模拟数据和计算结果。操作系统我推荐Ubuntu 22.04 LTS包管理方便遇到问题网上的资料也多。Windows上虽然也能跑但脚本兼容和文件配置的坑比较多新手别在这上面浪费时间。用虚拟机软件装三个Ubuntu实例并不复杂基本就是Clone两下的事但比单机伪分布式能演示的东西多很多。2.2 伪分布式搭建全过程伪分布式就是在一台机器上同时跑所有Hadoop角色主要用来学习原理、调试代码和跑小规模数据。很多热门的教程都把它作为第一步因为它能把Hadoop的核心进程都拉起来又不需要多台服务器。我下面把步骤完整列出来每一步都可以直接照着敲。先建一个专用用户避免直接用root导致权限问题。sudo useradd -m hadoop sudo passwd hadoop su - hadoop ssh-keygen -t rsa -P -f ~/.ssh/id_rsa cat ~/.ssh/id_rsa.pub ~/.ssh/authorized_keys chmod 600 ~/.ssh/authorized_keys ssh localhost这里生成SSH免密是为了让Hadoop的脚本能无密码登录本机去启动节点进程。集群模式下这台机器还要能免密登录其他所有节点所以在伪分布式阶段就把免密配好是给后面升级集群打基础。下载并解压Hadoopwget https://dlcdn.apache.org/hadoop/common/hadoop-3.3.6/hadoop-3.3.6.tar.gz sudo tar -zxvf hadoop-3.3.6.tar.gz -C /opt/hadoop建议解压到 /opt/hadoop 下然后配置环境变量export HADOOP_HOME/opt/hadoop/hadoop-3.3.6 export PATH$PATH:$HADOOP_HOME/bin:$HADOOP_HOME/sbin export JAVA_HOME/usr/lib/jvm/java-8-openjdk-amd64把这些写到 ~/.bashrc 里再执行 source ~/.bashrc。JAVA_HOME 要根据实际JDK路径改这一步很多人会漏掉导致后面Hadoop脚本起不来。启动脚本里默认不读系统JAVA_HOME的情况不少官方文档也反复强调要显式配置。然后修改四个配置文件它们的路径都在 $HADOOP_HOME/etc/hadoop/ 下。core-site.xml 配置文件系统默认地址configuration property namefs.defaultFS/name valuehdfs://localhost:9000/value /property /configurationhdfs-site.xml 配置副本数和数据目录configuration property namedfs.replication/name value1/value /property property namedfs.namenode.name.dir/name value/opt/hadoop/data/namenode/value /property property namedfs.datanode.data.dir/name value/opt/hadoop/data/datanode/value /property /configurationmapred-site.xml 让MapReduce跑在YARN上configuration property namemapreduce.framework.name/name valueyarn/value /property /configurationyarn-site.xml 配置辅助服务MR的shuffle要依赖它configuration property nameyarn.nodemanager.aux-services/name valuemapreduce_shuffle/value /property /configuration配置完成后的启动命令是固定的三步hdfs namenode -format start-dfs.sh start-yarn.sh用 jps 查看进程正常情况下能看到 NameNode、DataNode、SecondaryNameNode、ResourceManager、NodeManager 这五个Java进程。Web界面可以访问 http://localhost:9870 查看HDFS状态YARN的界面在8088端口。有一个坑必须提醒NameNode格式化只能在第一次启动前做以后每次重新启动集群都不要再去格式化否则会把元数据清掉DataNode和NameNode的集群ID对不上后面会报一堆Inconsistent FS State错误。很多人第一次遇到这个问题就是手滑重新格式化了。如果确实遇到集群ID不一致最干净的办法是停掉全部进程把NameNode和DataNode的数据目录都清空然后只格式化一次再启动注意这台机器上已有的HDFS数据会全部丢失。2.3 伪分布式升级到集群模式伪分布式跑通之后很多课程设计或者真实项目需要三节点集群。这时候要把机器名规划好我习惯用 master、node1、node2 这样的命名分别对应一台主节点和两台从节点。修改 $HADOOP_HOME/etc/hadoop/workers 文件写入所有DataNode的主机名node1 node2然后保证master可以免密登录node1和node2方法跟前面一样把master的公钥追加到各从节点的authorized_keys里。三台机器的 /etc/hosts 都要互相写上主机名和IP的映射例如192.168.1.10 master 192.168.1.11 node1 192.168.1.12 node2这一步看着不起眼却特别关键。集群模式下NameNode会按主机名去访问DataNode如果主机名解析不了DataNode进程明明拉起来了NameNode的页面上却永远看不到它排查起来很折磨人。配置文件基本不变但要把core-site.xml里的 fs.defaultFS 从 localhost 改成 master。然后你需要把整套配置文件同步到所有机器最简单的办法是用scp把整个 etc/hadoop 目录复制过去。启动时在master上执行 start-dfs.sh 和 start-yarn.sh 就行脚本会自动通过SSH连到其他机器启动对应进程。集群模式还有一个容易忽略的点SecondaryNameNode在一台机器上它和NameNode的职责容易搞混。NameNode负责管理元数据SecondaryNameNode只是定期合并编辑日志的辅助角色不是备份节点千万别指望它能做高可用。真要做高可用得用下面说的Zookeeper方案。2.4 Hadoop与Zookeeper整合的实战价值很多教程把Zookeeper和Hadoop的整合留到很后面才讲但物联网场景我做下来感觉这一点特别值得提前学习。原因是单NameNode一旦挂掉整个HDFS就不可用而物联网数据7×24小时产生高可用几乎是个硬需求。设备端还在不停上报存储端却罢工了这对生产系统来说是不能接受的。Zookeeper在Hadoop里的作用是提供分布式锁和选主机制。配置了HA之后两台NameNode里最多只有一个处于Active状态另一个处于Standby两台NameNode通过JournalNode共享编辑日志。一旦Active节点崩溃Zookeeper会在几秒内通知Standby切换成Active客户端无感知整个过程有点像两套引擎的热备切换备机时刻都在同步数据随时准备接管。部署整合至少要三个Zookeeper节点形成ZooKeeper集群然后在core-site.xml里配置多个NameNode的地址在hdfs-site.xml里指定nameservices、journalnode地址和ZKFC相关的参数。ZKFC是高可用自动切换的核心每个NameNode上都要启动一个独立的ZKFC进程来跟Zookeeper通信它负责检测本节点状态并参与选主。我第一次配置HA的时候卡了很久才意识到一个问题两台NameNode格式化之前必须先启动JournalNode而且只能格式化一次否则集群ID不一致切换也没用。这个顺序问题在官方文档里写得不算醒目但实际项目中十个人有八个会踩。你想象一下所有配置都检查过了Active和Standby却始终不同步费半天劲才发现是格式化的时机不对那种滋味真的很酸爽。3. 物联网数据的接入与存储设计3.1 用Flume把设备数据搬进HDFS数据接入层是物联网数据平台最容易被人忽略、也最容易在后期翻车的地方。很多同学把大量精力花在配置Hadoop、跑分析上却忽略了数据到底怎么进来。其实接入层设计得好不好直接决定后面分析层是否好写。如果只是用脚本把设备数据一条条往HDFS里写很快就会遇到两个问题一是并发能力不够二是产生海量小文件。Flume这类工具的价值在于它能在采集端做缓冲、封装成批次、按时间滚动写入HDFS从源头控制文件数量。我给一个可以拿来就改着用的Flume配置。场景是设备通过HTTP接口上报JSONFlume监听5140端口接收。配置文件放在Flume的conf目录下取名 iot_http.conf。agent1.sources httpSrc agent1.channels fileCh agent1.sinks hdfsSink agent1.sources.httpSrc.type http agent1.sources.httpSrc.port 5140 agent1.sources.httpSrc.bind 0.0.0.0 agent1.channels.fileCh.type file agent1.channels.fileCh.checkpointDir /opt/flume/checkpoint agent1.channels.fileCh.dataDirs /opt/flume/mydata agent1.sinks.hdfsSink.type hdfs agent1.sinks.hdfsSink.hdfs.path /iot/raw/%Y%m%d/%H agent1.sinks.hdfsSink.hdfs.filePrefix iot- agent1.sinks.hdfsSink.hdfs.rollInterval 3600 agent1.sinks.hdfsSink.hdfs.rollSize 134217728 agent1.sinks.hdfsSink.hdfs.rollCount 0 agent1.sinks.hdfsSink.hdfs.fileType DataStream几个关键参数说一下。hdfs.path 里的 %Y%m%d 和 %H 是时间格式Flume会自动按系统时间生成目录这样HDFS天然按小时分目录后面做分区裁剪很方便。rollInterval3600 表示一小时滚动一次文件rollSize134217728 是当文件到达128MB提前滚动两个条件谁先满足都行。rollCount一定要设成0代表不按条数滚动否则默认10条就滚动一次小文件会爆炸。这个参数是很多物联网项目的噩梦源头不信你可以试试不设置rollCount跑半天去看HDFS满坑满谷都是几百KB的小文件。启动命令是/opt/flume/bin/flume-ng agent --conf /opt/flume/conf -f /opt/flume/conf/iot_http.conf -n agent1 -Dflume.root.loggerINFO,console设备基数很大、每秒上万条上报的时候单个Flume agent可能撑不住这时候通常在Flume前面再放一层Kafka做削峰缓冲。设备数据进Kafka下游再消费Kafka写入Hive表对应的目录。Kafka的分区机制天然保证了同一设备的数据按分区有序多个Flume实例同时写也不会乱。实时性要求高的场景Kafka同时也能喂给Spark Streaming一套数据两套用途这也是我在真实项目里最偏好的一条路。3.2 HDFS目录与分区策略接入之后最要紧的是先把目录规划做好。我见过很多新手直接把所有数据塞进一个目录后面想按天查数据只能全量扫描神仙来了也救不了。建议至少分三层原始层、明细层、汇总层。/iot/raw/ 存放设备上报的原始数据不解析、不修改 /iot/ods/ 清洗后的明细数据按时间分区 /iot/ads/ 统计分析后的结果供报表使用以原始层为例按设备类型和时间两级分区/iot/raw/temperature/20241001/10 /iot/raw/humidity/20241001/10这样设计有两个原因。第一按设备类型分开后续单个类型的数据做分析或者做训练时不用每次Filter全量目录第二按时间分区Hive查询只需要读取对应分区扫描量能缩小几十倍。比如你要查某一天的数据Hive会在文件路径层面直接锁定那一天的目录跟传统数据库走索引一个逻辑。在代码里写路径时推荐约定日期用UTC还是本地时间要写清楚。这个问题我在几个项目里都遇到过设备端默认上报UTC时间而服务器和业务系统用的都是北京时间两者差了8个小时。有人直接把UTC时间拼进目录到了做报表的时候才发现数据整体漂移小时报表全是错位的。我一般会在Flume里加一个时间拦截器统一转成服务器本地时区再生成目录名称这样后面分析层做日期过滤就省心很多了。3.3 数据格式怎么选数据格式选得好后面查询快一倍、磁盘省一半这话一点不夸张。很多物联网新手习惯把所有数据都堆成JSON正是因为这个导致了查询效率低。原始层的格式我建议保留JSON。因为上游设备格式变动频繁JSON的冗余字段不会破坏行结构字段缺失也不影响其他字段的解析。这个阶段不用太在意存储空间重点是保真。你会发现设备厂商偶尔新增一个字段或者改一个字段名如果用的是严格的结构化格式整条链路都要跟着改而JSON完全没这个烦恼。清洗后的明细层就不要再存JSON了用Parquet这类列式存储更合适。Parquet按列压缩查询时只读取涉及的列不用像行式存储那样把整行load进来。比如你只需要温度列做平均Parquet可以只扫那一列的数据IO量小得多。我做过对比同一个数据集的同一句SQL存JSON和存Parquet的查询耗时差距经常有3倍以上。一个典型的表结构设计会把原始层建成外部表然后通过Hive或者Spark的ETL任务把JSON解析成Parquet落到明细层分区。格式选型对照我整理成了一张表格式压缩率查询性能适用场景JSON低低原始数据保留CSV低中简单的维度表Parquet高高分析明细层Avro中中流式写入场景Avro我单独说一下它虽然查询性能不如Parquet但支持schema演化写入速度也快适合对写入延迟敏感的场景。物联网数据如果直接通过Kafka Connect进HDFS用Avro会更顺手因为上游schema变化时不必同步改表结构。总的来说分析层优先Parquet流式写入优先Avro原始保留用JSON这个经验可以直接抄作业。4. 数据处理与分析实战4.1 用Hadoop Streaming写一个真实统计任务环境搭好、数据也进来了接下来就是重头戏跑一个真正能说明问题的统计任务。我以统计每台设备每天上报了多少条数据为例这个需求在物联网项目里太常见了相当于最基础的在线率度量。写MapReduce用Java比较啰嗦要改写的模板代码很多用Hadoop Streaming配合Python脚本会更直观也适合快速验证思路。Mapper脚本 mapper.pyimport sys import json for line in sys.stdin: line line.strip() if not line: continue try: obj json.loads(line) device_id obj[deviceId] day obj[timestamp][:10] print(f{day}_{device_id}\t1) except Exception: continueReducer脚本 reducer.pyimport sys current_key None current_count 0 for line in sys.stdin: key, count line.strip().split(\t, 1) if key ! current_key: if current_key: print(f{current_key}\t{current_count}) current_key key current_count 0 current_count int(count) if current_key: print(f{current_key}\t{current_count})提交命令hadoop jar /opt/hadoop/hadoop-3.3.6/share/hadoop/tools/lib/hadoop-streaming-3.3.6.jar \ -input /iot/raw \ -output /iot/result/device_count \ -mapper mapper.py \ -reducer reducer.py \ -file mapper.py \ -file reducer.py输出结果在HDFS的 /iot/result/device_count 目录下用 hdfs dfs -cat 查看。第一次跑的时候注意输出目录不能和输入目录重叠而且必须不存在否则作业会直接报错。每次重新跑要么删掉旧目录要么换个新路径。这个规则刚接触时不适应但养成习惯就好了本质上是为了防止覆盖已有结果。Streaming方式适合快速验证逻辑但生产环境里我更推荐用Hive或者Spark代码可维护性高处理复杂逻辑时也不容易写出一堆难以调试的脚本。MapReduce本身作为原理学习是很重要的它让你理解分布式计算里的分治思想后面用Hive或者Spark心里都有底。你会发现所谓的Hive查询底层其实还是转化成MapReduce或Tez来执行的逻辑完全相通。4.2 用Hive做物联网数据分析Hive是把SQL翻译成MapReduce/Tez任务执行的引擎。物联网场景里写SQL做分析远比手写Java和Python脚本高效。它不仅好写还容易让业务同学参与进来毕竟很多人都会SQL但不会Java。先建一张明细表对应清洗后的Parquet数据CREATE EXTERNAL TABLE iot_sensor_detail ( device_id STRING, device_type STRING, status INT, temperature DOUBLE, humidity DOUBLE, ts STRING ) PARTITIONED BY (dt STRING, hour STRING) STORED AS PARQUET LOCATION /iot/ods/sensor;注意分区字段dt和hour没有出现在普通字段列表里数据文件里也不含这两列它们是靠目录名决定的。查询时用分区字段过滤Hive会先做分区裁剪只扫描匹配的目录。如果直接在WHERE里写 ts 的字符串过滤反而不会触发分区裁剪因为分区字段是dt和hour一个是日期一个是小时。比如看某天每个小时的上报量分布SELECT dt, hour, COUNT(*) AS total FROM iot_sensor_detail WHERE dt 2024-10-01 GROUP BY dt, hour;统计温度异常的告警设备SELECT device_id, COUNT(*) AS alarm_cnt FROM iot_sensor_detail WHERE dt 2024-10-01 AND temperature 65 GROUP BY device_id ORDER BY alarm_cnt DESC LIMIT 20;物联网数据分析还有一个高频需求是补全缺失时间点。设备断网或者信号弱的时候某个小时可能完全没有数据直接GROUP BY看不出缺失只能看到数量少。处理方式通常是生成一张完整的时间维度表再LEFT JOIN上去。比如构建一个包含当天24个小时的临时表然后和上报数据做关联就能看到哪些小时没有该设备的数据这一步对在线率计算非常关键很多教程不讲我建议在自己项目里提前把这个时间维度表建好。Hive的默认执行引擎是MapReduce如果想快一点可以在会话里切换成TezSET hive.execution.enginetez;Tez比MapReduce快不少尤其是多阶段JOIN任务它的DAG执行模型减少了很多中间落盘。前提是你装好了Tez并配置了对应的依赖目录纯Hadoop环境里不能直接用。如果你跑的是课程设计或者小数据量实验MapReduce引擎也没啥问题但作业一旦涉及多个JOIN你会发现Tez的提速非常明显。4.3 作业提交到YARN的完整流程把作业跑起来的全过程其实就是跟YARN打交道的过程。理解这个流程遇到作业卡住、资源不够、Container反复被杀这些问题时你才能一眼猜到原因。很多人只会执行命令出问题后完全不知道去看哪些日志原因就是没有建立起这个执行模型。流程大致是这样的客户端把作业的jar包、配置、依赖打包提交给ResourceManager。ResourceManager分配一个Container给ApplicationMaster启动它。ApplicationMaster启动后向ResourceManager注册并请求运行MapTask和ReduceTask需要的Container资源。ResourceManager根据各个NodeManager上报的可用资源情况分配Container。NodeManager在各个节点上启动对应的任务进程。Map任务读HDFS数据处理完在本地做shuffleReduce任务拉取结果做最终计算。所有任务完成后ApplicationMaster向ResourceManager注销作业状态变为成功。这个机制用餐厅来类比会好懂很多。ResourceManager是餐厅前台只负责接单和下派任务ApplicationMaster是某一桌专属服务员负责那桌客人从点菜到上菜的全流程NodeManager是后厨真正把菜做出来。节点上每个Container就是灶台上一个可用锅位锅位不够了就只能排队等等久了就是作业堆积。实际操作中查看作业状态用yarn application -list yarn application -status application_xxx yarn logs -applicationId application_xxx排查问题的时候优先看两个地方ApplicationMaster有没有正常注册Map任务是不是卡在某个节点反复重试。前者通常是自己提交姿势不对后者往往是数据或者代码的锅。比如某个节点上的任务反复失败十有八九是那台机器上数据有问题或者内存不够Container被反复Kill。有了这个排查路径就不至于在HDFS和YARN界面之间乱点。5. 常见问题排查与性能调优5.1 小文件问题物联网场景的隐形杀手物联网数据接入里最高频的问题就是小文件。设备多、上报频繁如果不做批次控制HDFS里到处都是几KB、几十KB的小文件NameNode内存直接爆炸计算任务也会因为每个小文件都要启动一个MapTask而慢得出奇。可以说十个物联网Hadoop项目九个被小文件折磨过。解决思路有三个方向。第一从源头控制Flume的rollInterval、rollSize、rollCount三个参数要调好默认配置几乎必定产生海量小文件。第二定期合并对已有的HDFS目录用DistCp搬数据或者在Hive里设置自动合并跑一轮INSERT OVERWRITE就把小文件聚成几个大文件。第三分区设计要克制不要按分钟甚至按秒建目录小时和天是最合理的粒度过于精细的分区虽然查询方便但分区数量太多也会给小文件问题火上浇油。Hive里常用的合并参数SET hive.merge.mapfilestrue; SET hive.merge.size.per.task256000000; SET hive.merge.smallfiles.avgsize16000000;这套组合执行时间较长我习惯放到凌晨的调度任务里做。另外要注意合并的过程会重新读写一遍数据如果原始数据量很大会占用较多集群资源所以一般安排在业务低峰期避免影响白天的实时写入和统计作业。5.2 数据倾斜作业卡死的元凶数据倾斜在物联网场景里特别常见原因很简单因为设备不是均匀的。一台热门设备的传感器可能每分钟上报10次另一台冷门设备可能一天只报3次。GROUP BY device_id的时候热门设备对应的Reduce任务要处理的数据量可能是其他任务的几十倍整个作业就被这一个任务拖死现象就是界面上一堆Reduce任务早完成了唯独一个卡在那里转圈。常规解法是加盐打散。把device_id拼接一个随机数或者取模值让数据先均匀散到多个Reduce然后再做一次聚合去掉盐。这就是所谓的两阶段聚合第一次聚合把大Key打散第二次聚合再合并中间结果。代价是会多跑一轮任务但对于热点数据这个代价完全值得否则作业压根跑不完。Hive里可以用统一配置开启倾斜连接自动处理SET hive.optimize.skewjointrue;另外把热点设备单独拆出来处理也能解决问题。比如统计设备在线率时把每天上报量超过百万的热点设备单独拉出来走一个专门的作业正常设备走常规作业最后再合并结果。这样做的好处是热点作业单独调优不会拖累整体。我在真实项目里经常用这个思路因为物联网里的热点设备往往是固定的一小撮识别出来并不难。5.3 常见报错速查表我把物联网Hadoop项目里最常踩的几个错误整理成一张表基本覆盖了新手到入门会遇到的70%问题。这张表我自己带新人的时候偶尔也会直接发给对方省得一遍遍解释。报错现象可能原因排查与处理Call From localhost/127.0.0.1 to localhost:9000 failedNameNode没启动执行start-dfs.sh确认NameNode进程存在Inconsistent FS State误格式化NameNode导致集群ID不一致停止全部进程清掉DataNode目录后重新格式化唯一一次Java heap space内存配置不足调整mapreduce.map.memory.mb和reduce参数Python not foundStreaming脚本没指定-file提交命令加 -file mapper.py -file reducer.pyContainer killed on allocation单个任务内存超限检查数据是否有倾斜或调大容器内存No space left on deviceDataNode磁盘写满检查yarn.nodemanager.local-dirs和dfs.datanode.data.dir做第一个Hadoop项目时记得给每个关键配置留好备份。很多报错其实是改了一半配置、重新格式化导致的有一个能回滚的配置版本能省掉大量无谓的排查时间。我曾经踩过最哭笑不得的坑是为了优化参数一口气改了五个配置项后来作业挂了完全不知道是哪一项引起的最后只能逐个回滚定位。5.4 物联网Hadoop项目的课程设计与面试要点这段时间不少同学在准备Hadoop相关的课程设计也有人是为了应付面试。物联网数据这个方向其实很讨巧数据特征明显、业务场景直观用来做课程设计能展示的东西很多。如果做课程设计建议选一个具体场景比如基于Hadoop的交通信息分析系统或者仓储环境监测平台。思路分成几段环境搭建、数据模拟接入、离线统计、可视化展示。数据量不用真的一万台设备可以写个Python脚本模拟生成即可但ETL和分析链路要走通。答辩时重点讲清楚分区设计、小文件控制、数据倾斜处理这三个点老师就会觉得你是真做过而不是照着教程跑了一遍。面试方面围绕Hadoop和物联网结合的高频问题有这些数据倾斜怎么解决、NameNode高可用怎么实现、HDFS写入流程、MapReduce的Shuffle过程、YARN的资源调度机制。物联网背景会多问一句设备上报数据量很大时你会怎么设计接入层这时候把Flume缓冲、Kafka削峰、按时间滚动写HDFS这套方案讲清楚基本就能过关。注意别只背概念最好能带上自己实操时的具体参数比如Flume的rollSize设成多少、Kafka分区数怎么定一听就知道是做过项目的。我自己面试别人的时候最喜欢听的是候选人承认某次作业卡了很久后来发现是某台设备数据特别多导致倾斜。有具体案例比背一堆八股有用得多因为面试官想看到的不是我知道这个概念而是我知道怎么用这个概念解决真实问题。这篇指南写得比预期长不少不过都是我在物联网和Hadoop交叉项目里反复趟出来的经验。如果让我只留一句话给后来者那就是先把最小链路跑通再把细节磨干净。环境卡住的时候去看看配置备份作业卡住的时候先怀疑数据倾斜和小文件别急着怪框架。最后再分享一个每天都在用的习惯新项目里我会花半天时间把设备上报时间的时区问题彻底确认清楚接入层统一做一次时间标准化。这个动作看起来不起眼但能省掉后面报表阶段无穷无尽的日期对齐烦恼。物联网数据项目从来不是一次性的链路跑起来了后面才有资格谈优化。