做大数据运维这行,有一种“特别熟悉的恐惧”——手机屏幕在凌晨三点亮起来,群里跳出一条“任务失败”的告警,你闭着眼睛都能感觉到机房或者云端的哪个进程正在哀嚎。我过去五年的大部分精力都耗在这上面,从CDH到MRS,一条迁移路走下来,踩过的坑比吃过的盐还多。今天这篇不聊虚的,就是把那些让我凌晨爬起来改配置的血泪教训,一条条捋给你看。
之所以想写这个,是因为后台经常有人问:你们当初为什么从CDH迁到MRS?迁移过程中最痛的是什么?迁完之后是不是就好了?答案可能和很多人想象的不太一样。迁移本身的技术难度其实有限,真正磨人的是那些藏在配置项、依赖关系、权限模型里的“隐形地雷”。这篇博文适合所有正在做或准备做大数据平台迁移的朋友,不管是机房自建的CDH、HDP,还是云上的MRS、EMR,思路都能复用。我会把选型考量、迁移准备、实操步骤、迁后运维和问题排查一次讲透,尤其是那些常规文档里不写的细节,都会拿出来晒一晒。
1. 五年前盯上CDH,后来为什么又决定迁走
1.1 初始选型时看中的,恰恰是后来最拖后腿的
当年上这套CDH集群,图的是省心。Cloudera Manager一装,整个Hadoop生态的组件像商品货架一样摆在那里,勾选、配置、部署,一条龙。那时候团队里就三个人,前端后端一大堆业务要支持,没精力也没能力从零编译、手工编排一套Apache Hadoop。CDH的版本管理和组件兼容性测试做得确实可以,Spark、Hive、HBase、Kafka装好基本能跑,日常运维靠Web界面点一点,也算岁月静好。
但现在回头看,这种“省心”的代价是黑盒化。CM虽然把配置项都给你摆出来了,可背后的默认值、依赖关系、服务发现机制是封装好的。你习惯了在界面上点“重启”,点“刷新”,却并不清楚它到底改了哪几个配置、动了哪些目录。等到要迁走或者要做深度调优的时候,这种黑盒就成了大麻烦。我记得当时想查清楚HiveServer2实际生效的JDBC连接池参数,翻半天CM界面也没找到明细,最后是去节点上抓进程的JVM参数才看明白。
1.2 迁移的真实原因:成本、演进和可控性
这里得说清楚,不是因为CDH跑出了什么大事故才被迫迁移,更多是主动选择。首先是成本账,CDH的商业授权和整个集群的硬件运维成本逐年上涨,而业务增量已经趋于平稳,用同样的钱放在云上能买到更弹性的资源。其次是技术演进,CDH的版本更新节奏偏慢,新组件、新特性跟不上主流社区,很多新工具没法直接用。最后是可控性,我们希望把配置、监控、权限全部用代码管理起来,而不是依赖一个图形界面。
所以某一天,我们决定用MRS作为新的底座。MRS是托管式大数据集群服务,组件形态和CDH比较接近,但底层是云原生基础设施——跨可用区部署、对象存储对接、弹性扩缩容,这些都是机房环境给不了的。定这个方向的时候,团队内部其实争论过好几轮:要不要直接上原生Apache Hadoop?要不要顺便切到数据湖方案?后来达成的共识是,先从CDH平稳迁到MRS,把架构问题和新组件引入解耦,降低一次性改造的风险。
2. 迁移前夜,必须摸清的三张清单
很多迁移项目失败,不是死在迁的过程中,而是死在根本没想清楚自己有什么。CDH用久了,组件多到你想不起来有哪些,版本混乱到连CM都记录不全。我们当时准备了一个完整的盘点期,核心是拉三张清单:组件清单、端口目录清单、权限清单。这三张清单做完,迁移路径基本上就清晰了一半。
2.1 组件与版本依赖清单:先搞清楚家里有多少“老古董”
CDH集群长期运行,上面养了一批“老古董”作业,有的用Hive on MapReduce,有的用Spark 2.x,个别甚至还有靠HBase协处理器跑的业务。迁移第一件事,就是把所有组件和版本摸清楚。我们用的办法是导CM配置快照,再结合节点上的rpm包和进程命令行反推,最终列出一张表:HDFS、YARN、Hive、HBase、Kafka、ZooKeeper、Spark、Sqoop、Flume,每样对应CDH版本里的具体子版本。
这里有个容易被忽略的点:CDH虽然统一打包,但组件之间的版本兼容是被Cloudera测试过的,到了MRS上,哪怕大版本名字一样,底层细节也可能不一样。最典型的是Spark和Hive的整合方式。CDH里Spark默认不关联Hive元数据,跑SQL要么走Hive on Spark,要么靠SparkSession自己连MetaStore;而MRS往往默认做好了Spark与Hive的元数据互通,直接读表就能用,但一些隐含的jar包冲突就得靠环境变量和classpath策略来处理。我们在迁移前就盯着这张清单做差异核对,把每一个组件的版本和连接方式都记下来,后续改造脚本时才没乱套。
2.2 端口、目录与配置规划清单:默认值就是最深的坑
迁到MRS之后,最直接、最气人的变化就是端口和目录。CDH有自己的一套命名习惯,比如NameNode的HTTP页面走50070,YARN的ResourceManager页面走8088,这些端口在MRS上不一定对得上,甚至有些组件为了安全直接把Web UI绑到内部地址,外部根本访问不了。目录方面,CDH默认把数据放在/dfs、/yarn、/tmp这样的根路径下,而MRS会使用独立的挂载点,命名空间完全不一样。
如果一个作业里硬编码了IP、端口或者绝对路径,迁移之后必炸。我们当时干了一件笨但管用的事:写了一个脚本扫描所有作业代码、配置文件里的IP和路径关键词,把所有“看起来像地址和路径”的地方全部列出来,然后对照MRS的规划逐项修正。这块别嫌麻烦,漏一个就是一次凌晨事故。我后来总结出一个原则:凡是写在代码里的环境信息,全部抽出来放到配置中心;凡是能在集群层面统一的资源路径,全部用抽象路径代替。宁可多写几行代码,也不要把环境细节散落在业务逻辑里。
2.3 认证与权限清单:Kerberos不是你想开就能开
CDH环境里我们之前没开Kerberos,因为内网环境加上应用紧耦合,一直没狠下心启用认证。而MRS的默认安全模式下,Kerberos认证是强制性的,所有客户端、所有作业都得先过认证才能访问服务。这一步的改造量其实比迁移大得多。首先要给每类业务创建独立的Kerberos账号,配置keytab文件;然后所有代码连接组件时都得带认证上下文;最后就Kerberos票据过期还要配置自动续期,否则凌晨跑批一定会出现“票据过期导致连接失败”的告警。
权限方面,CDH如果用的是简单的Linux用户映射,到了MRS上则需要适配新的权限模型。比如Hive表的授权、HDFS目录的授权、YARN队列的授权,三种权限体系的判定逻辑不完全一样。我记得迁移之后有个业务组反馈说能提交任务但删不了表,排查半天,就是Ranger权限策略没把delete权限配上。所以迁移前一定要把权限清单整理出来:每个应用的用户、需要的组件访问范围、数据目录的读写要求,逐条映射到新的权限模型。
3. 迁移实操:从CDH到MRS的完整闭环
3.1 元数据迁移的硬骨头:Hive、HBase、Kafka的差异
先说Hive元数据,这是最典型的例子。CDH一般用外部MySQL承载Hive的元数据库,表结构是Cloudera定制过的,和Apache版本有细微差异。迁移时要把这些表数据导入到MRS对应的元数据库里,听起来简单,实战里全是坑。比如自增主键的起始值可能不对,导致新增分区ID冲突;又比如表字段里某些特殊字符在字符集转换时会乱掉;再比如分区信息量大到跑一晚上都导不完。
我们当时是分两步做的:先把所有元数据导出成SQL文件,做一遍全量校验;然后在目标集群重置自增序列,再按依赖顺序导入。这里尤其注意外键关联:Hive元数据表之间是有关联的,导入顺序错一个就全崩。我的建议是,别嫌弃“先drop再导入”的笨办法,在维护窗口内用最保守的方式保证数据一致,比追求所谓“增量同步”然后半夜出问题要强得多。我也见过用第三方工具做在线迁移的,但版本兼容和特殊表处理始终是高危区,非必要不冒险。
HBase元数据相对简单,主要是坐标和Split信息,但要注意协处理器的jar包——业务自定义的协处理器如果在目标集群没有对应的jar包,RegionServer起来后会直接崩溃。Kafka要处理的是Topic的分区数和副本数规划,CDH上如果用过老版本Kafka,消息格式可能和MRS默认支持的不一致,需要开兼容参数或做数据迁移。
3.2 数据同步那场“马拉松”:distcp的调参与重试
数据迁移的主体是HDFS数据,我们用的工具是distcp。这个概念跟名称一样直白,只要两个集群放通网络,就能从源路径拷贝到目标路径。但真跑起来,几十个TB的数据隔着网络同步,各种各样的超时和Map失败就来了。
distcp的坑集中在几个参数上。第一是带宽限制,如果源集群和MRS之间的链路不是独占的,不设限的distcp会直接把专线带宽打满,影响线上业务。解决方式是加-bandwidth参数,给任务设置上限,跑慢一点但求稳。第二是Map数,Map太多会造成大量小文件,太少又发挥不了并行度,通常按文件数量和总体积来估算,我一开始直接按默认值跑,结果几十万个文件的元数据扫描阶段就把NameNode搞得不轻。第三是文件校验,distcp默认会做CRC校验,但有些文件可能是特殊格式或者之前损坏过,校验失败会使得任务反复重试,定位起来特别费劲。我们的做法是先跑一遍不带校验的批量拷贝,再用快照对比文件数和大小,最后随机抽一批文件做内容比对,既控制了时间又保证了可靠性。
还有个大坑是网络层面的RPC端口映射。机房环境直接访问MRS的NameNode RPC端口可能受网络策略限制,需要配置跳板扇出或者端口映射。这个在文档里通常只给一个连接示例,实际跑的时候还得调整客户端侧的参数,比如dfs.client.use.datanode.hostname、dfs.client.socket-timeout之类。遇到任务频繁中断别急着重跑,先抓客户端的日志看是哪一层的超时,对症下药。
3.3 作业和调度器的适配:从CM API到RESTful的一次性改造
CDH时代,很多运维脚本依赖Cloudera Manager API去查服务状态、触发重启。这套API在MRS上没有,得换成MRS提供的RESTful接口。不要小看这个转换,接口路径、参数结构、返回字段全变了,写正则去解析的老脚本基本废掉。我们当时把所有脚本分层处理:最底层封装一个“服务操作模块”,统一提供启停、状态查询、日志拉取的方法,上层脚本只调用模块,不直接跟API打交道。这样改造完一次,之后再换平台,只改底层模块就够了。
作业治理方面,CDH默认的YARN调度器很多是Fair Scheduler,MRS则普遍用Capacity Scheduler的模型。两个调度器的队列配置思路完全不同,迁移后不仅队列名要变,每个队列的容量比例、最大资源、优先级也要重新设计。我们有过一次深刻的教训:原来的高频小任务队列在Fair策略下可以快速抢占资源,到了Capacity模型里如果没有给足最大容量,一到晚高峰就被大任务挤兑,跑批从半小时拖到三小时。后来重新按业务重要度划分队列,大任务和小任务物理隔离,才稳定下来。建议迁移前就根据业务流量模型画出队列规划,而不是迁完再按报错反应式调整。
4. 迁完之后,真正的“噩梦”才刚开始
4.1 监控告警体系重建:别把“修复”当成“迁移”
迁移完成不是终点,运维体系的适配才决定你后续能不能睡好觉。CDH的监控界面是CM自带的那套,界面友好,集成度高。MRS的监控入口在云管侧,指标字典和阈值语义都不太一样。如果直接把旧告警规则翻译过来,很可能告警根本不触发或者天天误报。
我推荐的做法是:先对照两份指标清单,逐个确认核心指标在目标平台的语义。比如CDH的“HDFS容量使用率”和MRS的“HDFS容量使用率”虽然名字一样,但统计口径可能不同,一个是除以总配置容量,一个是除以当前实际挂载容量,阈值完全对不上。我们踩过一次,迁移后按老规矩在80%设告警,结果容量到90%了还没提示,排查才发现新平台的统计方式把预留空间剔除了。监控体系一定要在迁移完成后的冷启动阶段逐项验证,用脚本模拟故障看告警是否及时、通知是否到位。
4.2 Kerberos和权限模型的自适应调整
MRS默认安全模式下,Kerberos和权限的问题会在你第一次提交作业时扑面而来。常见的问题是:业务账号没有正确的服务端主体,导致从客户端连接HDFS时报如下错误“Server not found”;还有keytab文件过期未更新,任务一跑就是一串“Permission denied”。这些都需要写自动化运维脚本来管理,不能靠人工。
最让我头疼的是YARN队列和HDFS目录权限的叠加。之前CDH的权限模型比较粗,一个用户基本能访问所有目录;MRS下Ranger策略管得细,目录、表、列、函数都能控制。好处是安全可控,坏处是每个新任务都有可能踩“权限边界不够”的雷。我们当时的应对是建了一个权限申请审批的自动化流程,业务提交权限申请单,脚本根据模板生成Ranger策略并自动刷新。这样才能在安全和效率之间找到平衡,不然运维光处理权限工单就不用干别的了。
4.3 凌晨三点爬起来调配置的三次实录
讲几个真实发生过的凌晨事故,比任何原理都直观。
第一次是迁完后第二周的凌晨,跑批任务大面积变慢。爬起来查看YARN界面,发现某个大任务把队列资源全占了,其他任务都在等。调整了容量调度器的最大容量和抢占策略,让批处理任务在高峰期间不被单一任务阻塞。这个调整其实早该做,纯粹是迁移时漏了队列规划导致的问题。
第二次是HDFS节点掉线触发了NameNode切换,当时业务侧直接报“文件系统无法使用”。排查发现客户端缓存的NameNode地址还是旧的,切换后不知道更新。MRS的客户端配置一般会用nameservice而不是具体的Active节点地址,但机房老作业里写死了IP。那次是全局搜索替换,把所有代码和配置里的NameNode节点IP改成nameservice名称,凌晨四点改完,五点半跑批恢复。
第三次是磁盘写满导致HDFS进入安全模式。根本原因是OBS转储任务配置的Overflow目录和数据目录在同一块盘上,日志把磁盘撑爆了。处理方式是把临时目录和冷数据目录拆分到不同存储,然后调低安全模式的自动触发阈值,留出人工介入时间。那次之后,我的巡检脚本里加了一项:磁盘使用率Top10目录变化趋势,超过阈值提前扩容清理,不能等告警来叫你起床。
5. 五大经典坑排查思路,直接照着做
5.1 从报错反推原因的分析框架
运维终究是大量时间在排查问题,与其大海捞针,不如建立一个稳定的分析框架。我平时的排查路径是这样的:先看任务日志最后1MB,确认是应用报错还是框架报错;再看YARN日志和集群总体状态,排除资源瓶颈;然后看组件目标日志,比如HDFS的NameNode日志、HBase的HMaster日志;最后看系统层面的指标,CPU、IO、网络、GC。只要按这个顺序走,80%的问题都能在半小时内定位到根因。
使用频率最高的排查命令汇总一下:
# 查看YARN任务日志,定位应用退出原因 yarn logs -applicationId <application_id> # 确认HDFS文件读写状态,检查是否处于安全模式 hdfs dfsadmin -safemode get # 检查Kerberos票据情况,确认认证是否过期 klist -e -k -t /etc/security/keytabs/xxx.keytab # 查看节点磁盘和文件系统挂载状态 df -h && mount | grep dfs # 追踪组件进程的GC情况,排查JVM层面的异常 jstat -gcutil <pid> 1000 20举一个实际例子:某天凌晨Spark任务频繁报“Failed to connect to ... StandbyException”。按照上面的流程,先看YARN日志发现TaskExecutor反复尝试连接NameNode失败,再看HDFS状态发现当前NameNode是Standby,但客户端配置的地址还是旧主节点。最终解决就是把客户端配置文件里的nameservice参数统一修正,并重启客户端服务。如果不按框架来,可能还在Spark参数里找半天,白白浪费一两个小时。
5.2 问题速查表
这里整理一份我在MRS环境中长期使用的问题速查表,每一条都是真金白银换来的经验。无论是自己排查还是分享给团队新手,都很实用。
| 问题现象 | 常见根因 | 排查方向 | 解决建议 |
|---|---|---|---|
| 作业提交到YARN后一直PENDING | 队列资源不足或队列配置错误 | 查看YARN的队列状态和正在运行的任务数 | 调大队列容量,或优化单个任务资源配置 |
| 访问HDFS报StandbyException | 客户端缓存了旧的NameNode地址 | 检查客户端配置中的nameservice参数 | 统一配置文件,替换为nameservice并重启客户端 |
| Hive SQL跑着跑着卡住 | 元数据连接池满或锁竞争 | 查看HiveServer2日志和元数据库连接数 | 调大连接池大小,拆分耗时查询或加索引 |
| Kerberos票据过期导致认证失败 | keytab未续期或脚本未刷新票据 | 执行klist检查票据时间 | 在调度脚本中增加kinit刷新逻辑 |
| 磁盘空间告警但找不到大文件 | 隐藏目录或临时文件没清理 | 用du按目录逐层排查 | 配置目录配额,每日巡检Top目录 |
| HBase RegionServer频繁宕机 | 协处理器jar包缺失或配置冲突 | 看RegionServer日志中的Error信息 | 将协处理器统一放到指定目录,并同步到所有RegionServer |
| 数据写入慢且伴随小文件激增 | 文件块大小设置不合理或并发过高 | 查看NameNode的Block数量和文件数 | 调整写入批次大小,使用CombineFile或小文件合并策略 |
还有一个容易触发、但常常被忽略的坑是小文件问题。Hive、Spark写数据默认会按并行度产出文件,如果每五分钟跑一个批,一天下来几千个几十KB的小文件就堆在HDFS上。元数据膨胀、NameNode内存压力上升,整个集群都会“变重”。处理方案是:要么在写入时加参数做文件合并,要么定时用工具做小文件归并,绝不能让小文件躺在那里不管。
5.3 给新环境留一个“应急逃生门”
运维的规则永远是先恢复后诊断,但恢复手段要提前准备好。我的习惯是在目标MRS集群之外保留一份经过验证的回滚脚本,内容包含:全量配置备份、关键组件重启顺序、数据校验指令。真出现短时间内无法解决的问题,先按回滚脚本把服务恢复到上次稳态,再细看原因。别把时间浪费在“当时为什么这么改”的悔恨上。
我在实际使用中还发现,每两周做一次配置整体备份非常有效,不止是云管上导出的配置,还包括客户端所有环境变量、properties文件、keytab清单、调度脚本的版本。这个备份塞在一个Git仓库里,和集群状态有个简单的任一一对应关系。出问题的时候,先把改动和备份做diff,往往一秒钟就能定位到人为误操作。
末尾补一句实在话
从CDH到MRS这条迁移路,走了整整一年多。网上很多人只讲架构、讲方案,很少讲那些凌晨拉起来改配置的狼狈。我想说的是:迁移方案做得再漂亮,最终拼的还是运维细节——那个端口通不通、那个路径名一致不一致、那个keytab过期了没有。把这些细节管好,迁移就成功了一大半。如果你现在正处在这个阶段,希望这篇笔记能让你少瞪几次凌晨三点的天花板。