
這兩年做大數據基礎設施跟 Hadoop 集群打交道下來最明顯的感受是Hadoop 2.x 時代那套“三副本 固定物理機”的玩法越來越貴也越來越笨重。業務方要省錢老板要效率運維要省心這三個訴求疊在一起逼著團隊往 Hadoop 3.x 走——先是上糾刪碼把存儲成本降下來再是借著云原生的風把集群塞進 Kubernetes。這篇文章就把我實際落地 Hadoop 3.x 的過程、取舍和踩坑記錄一次理清楚從糾刪碼講到容器化部署給正在做同樣演進的團隊一個參考。1. 為什么企業選擇 Hadoop 3.x 而不是繼續守在 2.x1.1 從 2.x 到 3.x到底改了哪些東西很多團隊其實不是不想升而是不清楚 3.x 跟 2.x 的本質差別。我見過不少集群還在 2.7/2.9 上跑著運維同學每天都在跟容量告警和 Full GC 耗時間。Hadoop 3.x 最大的變化并不是某個參數調優而是一套新的底層能力我把關鍵差異列出來能力點Hadoop 2.xHadoop 3.x我的評價默認副本策略3 副本兼容 3 副本但默認支持糾刪碼EC3.x 真正的殺手锏存儲成本直接對半砍NameNode HA需要手動配置 QJM內置更成熟的 Quorum Journal Manager可用性提升故障切換更穩支持多 NameNode 聯邦有但配置繁瑣Router-based FederationRBF更成熟超大規模集群必備YARN 資源模型只認 CPU 和內存支持 GPU、FPGA 等異構資源機器學習負載也能統一調度容器化支持基本沒有官方開始支持 NodeManager 容器化社區方案成熟上云、上 K8s 的基礎默認 Java 版本Java 7/8官方支持 Java 8/11新集群沒有歷史包袱這里要特別說一句3.x 不是“改個版本號重啟一下”那么簡單。它是把 HDFS 的存儲模型從“復制存儲”改成“編碼存儲”把 YARN 從“單一調度”改成“可擴展資源調度”這套底層的改變才支撐了后面的云原生容器化。如果只是升了版本但策略全用舊的那升了跟沒升差不多。1.2 什么業務值得為升級買單不是所有集群都需要立刻升但從我接觸的企業來看下面這幾種情況越早升越劃算。第一種是存儲大頭在冷數據的。比如數據倉庫的離線層、日志歸檔、歷史訂單這類數據寫入之后很少再改但量大占著 3 倍副本的空間。這類場景上糾刪碼存儲成本能肉眼可見地降下來。第二種是被 NameNode 性能卡脖子的。3.x 在 NameNode 內存模型、RPC 處理上有明顯優化大目錄、超多小文件的場景比 2.x 穩不少。第三種是準備做容器化、混合云或者多集群統一調度的。2.x 里的 YARN 在容器支持上很弱想在 K8s 上跑幾乎得靠各種 hack。3.x 配合社區方案才能談得上“云原生大數據”。1.3 升級前需要掂量的成本與風險我見過太多團隊倉促升級然后回滾的案例核心問題不是技術而是沒想清楚成本。首先是兼容性成本。Hive、Spark、Flink 這些上層組件要跟著 Hadoop 3.x 匹配尤其是 Spark 和 Hadoop 的 RPC 協議、shuffle 機制在 3.x 有變化版本搭配不對跑起來全是坑。其次是人肉成本3.x 的新特性你不能光看文檔要真的在小集群壓一遍光 EC 重建、滾動升級這兩項我建議預留至少兩周的驗證時間。最后才是資源成本上 EC 不是零成本后面會專門講。所以我的建議是先小規模試點比如幾十個節點把核心業務跑上去觀察一兩個月穩定之后再擴大范圍。直接把整個集群一刀切升級的風險比想象中大得多。2. 糾刪編碼EC用 1.5 倍存儲留住 3 倍可靠性2.1 糾刪碼的數學直覺與 HDFS 落地方案如果讓我用最簡單的話解釋糾刪碼Erasure CodingEC我一般會這樣說三副本的本質是把一份數據復制三份存著哪份壞了用哪份EC 的本質是給數據算一道校驗題壞掉一部分用剩下的部分也能算出原數據。HDFS 3.x 里默認的 EC 策略是 RS-6-3-1024k。把 1024k1MB的單元作為計算粒度一份數據邏輯上切成 6 份數據塊再用里德-所羅門Reed-Solomon編碼算出 3 份校驗塊總共 6 3 9 份分布在集群里。這里的“6 3”意味著任意丟失 3 份剩下的 6 份依然能還原全部原始數據。從數學上講RS 編碼是在有限域GF(2^8)上做矩陣乘法和逆運算。通俗點說6 個數據塊相當于 6 個“已知量”你通過編碼矩陣生成 3 個“校驗方程”只要剩下的塊數不少于 6 個方程組就能解出原始數據。這也是為什么 EC 能容忍丟失 3 個塊的原因。對應到存儲開銷三副本是 3 倍存儲而 RS-6-3 只需要 (63)/6 1.5 倍。一個 100TB 的數據目錄用三副本要占 300TB 物理空間用 EC 只要 150TB直接省一半。這是我在生產環境里親眼驗證過的數字不是概念推演。2.2 三種編碼策略怎么選Hadoop 3.x 內置的 EC 策略不止一種我給團隊培訓時常說一句話別默認選 RS-6-3策略選錯比不選更難受。策略數據塊校驗塊存儲開銷容錯能力適用場景RS-6-3-1024k6 31.5 倍任意 3 塊丟失冷數據、歷史數據、備份歸檔RS-3-2-1024k3 21.67 倍任意 2 塊丟失溫數據、近實時分析XOR-2-1-1024k2 11.5 倍僅 1 塊丟失臨時數據、重建優先級低的場景注意看XOR-2-1 的存儲開銷也是 1.5 倍但容錯能力只有 1 塊為什么因為 XOR 編碼的數學復雜度遠低于 RS沒有 RS 那么強的糾錯空間。所以它在成本和容錯上并不比 RS-6-3 劃算只有在 CPU 資源緊張、臨時數據損壞可接受的情況下才建議用。對于大多數企業我建議冷數據直接用 RS-6-3因為容錯能力強重建窗口內再壞一塊也能扛住。RS-3-2 適合那些還要被 Spark SQL 偶爾掃一下的溫數據存儲開銷略高一點但編碼和解碼的 CPU 開銷更低查詢性能會好一些。2.3 生產落地的設置步驟與實操記錄EC 的落地很簡單但細節決定成敗。第一步是確認集群開了 EC 相關配置然后在目標目錄上設置策略最后檢查寫入的塊分布。先用 hdfs ec 命令查看并設置策略# 查看當前集群支持的 EC 策略 hdfs ec -getPolicies # 為冷數據目錄設置 RS-6-3 策略 hdfs ec -setPolicy -path /data/cold/warehouse -policy RS-6-3-1024k # 確認策略已生效 hdfs ec -getPolicy -path /data/cold/warehouse這里有一個關鍵點EC 策略只對目錄下“新建的文件”生效已有的文件不會自動重編碼。所以生產上正確的做法是先建好新目錄并設置策略再用 distcp 把舊數據遷過去# 建新目錄并設置 EC 策略 hdfs dfs -mkdir -p /data/cold/warehouse_ec hdfs ec -setPolicy -path /data/cold/warehouse_ec -policy RS-6-3-1024k # 把舊目錄數據全部遷移過去 hadoop distcp -i -m 100 -numListstatusThreads 20 \ hdfs://namenode:8020/data/cold/warehouse \ hdfs://namenode:8020/data/cold/warehouse_ec # 遷移后用 fsck 檢查塊健康度 hdfs fsck /data/cold/warehouse_ec -files -blocks -locations我在實際遷移中吃過一個虧distcp 默認會保留源目錄的復制策略如果源目錄沒有設置 EC遷移到目標目錄后文件可能仍然是 3 副本。解決辦法是在 distcp 命令里帶上-p相關屬性或者遷移完成后用 fsck 抽查塊數量確認當前文件確實是 EC 編碼而不是 3 副本。再補充幾個生產環境里的經驗不要對正在高頻寫入或隨機更新的目錄開 ECEC 不支持追加寫append和隨機寫這類場景等著被坑。先看業務再上策略像是 Spark Streaming 寫臨時狀態表的目錄不建議開 EC寧愿多花點存儲也要保證寫入性能。EC 和 HDFS 快照Snapshot不兼容如果業務依賴快照做誤刪恢復這塊要做額外備份方案。EC 重建會吃 CPU 和網絡帶寬默認情況下后臺重建線程有限遇到批量壞塊會導致長時間降級。建議在業務低峰期做數據遷移并預留足夠的網絡帶寬。從成本角度算一筆賬一個 500TB 的冷數據目錄三副本要 1500TB用 RS-6-3 只要 750TB按 1TB 硬盤成本折算省下的硬件成本非常可觀。EC 絕對是我認為 Hadoop 3.x 最值得先上的特性。3. 把 Hadoop 搬進 Kubernetes存儲計算分離的容器化實踐3.1 容器化面臨的三座大山EC 是存儲層的改造容器化是部署形態的改造。很多團隊卡在“Hadoop 能不能上 K8s”這個問題上我的答案是能但前面有三座山要翻。第一座山是“有狀態服務”。HDFS 的 DataNode 和 NameNode 天生是有狀態的NameNode 的元數據、DataNode 的數據塊都得持久化。K8s 默認的 Deployment 是無狀態的Pod 說沒就沒數據不能跟著沒。解決方案是用 StatefulSet 加 PVC 把數據和 Pod 生命周期綁定Pod 可以重建數據不能丟。第二座山是“數據本地性”。Hadoop 的設計哲學是“計算靠近數據”DataNode 在哪NodeManager 最好就在哪。但 K8s 的調度器可不管你這套它把容器放哪完全看資源水位。如果不干預很可能計算出現在 A 節點數據在 B 節點每次 MR/Spark 都走遠程讀性能直接崩。第三座山是“服務發現和網絡”。原來的 Hadoop 集群靠 hostname 互相認容器化之后 Pod 的 IP 和名稱動態變化NameNode 怎么知道 DataNode 在哪YARN 的 ResourceManager 怎么知道 NodeManager 在哪這就要靠 Headless Service 和穩定的網絡標識來兜底。我的建議是在動手寫 YAML 之前先想清楚棧怎么搭。先用 StatefulSet 解決“有狀態”用“DataNode 和 NodeManager 同 Pod”解決“數據本地性”用 Headless Service 解決“服務發現”。這三件事想透了后面都是細節。3.2 可復用的部署骨架StatefulSet 加本地盤下面是我在項目里實際用過的 DataNode NodeManager 同 Pod 的骨架跑在 Kubernetes 1.24、Hadoop 3.3.x 上12 個節點、單節點 8Ti 本地盤的配置下表現穩定apiVersion: apps/v1 kind: StatefulSet metadata: name: hadoop-datanode namespace: bigdata spec: serviceName: hadoop-datanode-headless replicas: 12 selector: matchLabels: app: hadoop-datanode template: metadata: labels: app: hadoop-datanode spec: affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: labelSelector: matchExpressions: - key: app operator: In values: - hadoop-datanode topologyKey: kubernetes.io/hostname containers: - name: datanode image: hadoop:3.3.6 command: [hdfs, datanode] ports: - containerPort: 9866 name: dfs-datanode volumeMounts: - name: dfs-data mountPath: /data/dn - name: nodemanager image: hadoop:3.3.6 command: [yarn, nodemanager] ports: - containerPort: 8042 name: nm-web volumeMounts: - name: dfs-data mountPath: /data/dn env: - name: YARN_NODEMANAGER_LOCAL_DIRS value: /data/dn/nm-local - name: YARN_NODEMANAGER_LOG_DIRS value: /data/dn/nm-log volumeClaimTemplates: - metadata: name: dfs-data spec: accessModes: [ReadWriteOnce] storageClassName: local-hdd resources: requests: storage: 8Ti這個方案有幾個設計點值得講DataNode 和 NodeManager 共享同一個 Pod 和同一個/data/dn目錄。因為它們在同一個網絡命名空間、共享主機文件系統NodeManager 分配出去的容器讀取的數據其實就在本機天然滿足數據本地性。這比用 podAffinity 去“盡量湊在一起”要可靠得多。用 StatefulSet 的volumeClaimTemplates為每個 DataNode 綁定一塊獨立的本地盤。Pod 重建后PVC 重新掛載數據不丟。注意要用支持本地盤的 StorageClass比如 local-static-provisioner否則把云硬盤跨節點重掛載會有性能問題。Pod 反親和性podAntiAffinity保證同一條物理機不會同時跑兩個 DataNode避免單節點故障導致多個副本同時不可用。這是一個非常“樸素但管用”的方案。不用高深的 Operator不搞復雜的調度策略一個 StatefulSet 就把數據本地性和有狀態問題解決了。3.3 NameNode 高可用怎么容器化DataNode 容器化之后NameNode 必須保持高可用這是整個集群的命門。我在 K8s 上部署了兩臺 NameNode 外加三臺 JournalNode全部用 StatefulSet 管理。關鍵是 JournalNode 的數量和故障域三臺 JournalNode 必須分散到三個不同節點并且保證同一時刻最多只能掛一臺。這樣才能在 QJM 協議下維持“多數派”可用。JournalNode 的 edits 日志目錄要用獨立的 PVC跟 DataNode 數據盤分開。容器化的 NameNode 與物理機部署的主要差別在于ar 和服務的互認需要通過 DNS 而不是 IP。所以在 core-site.xml 里把fs.defaultFS配成hdfs://hadoop-nn這個地址由 K8s 的 Service 解析到當前 active 的 NameNode。然后用hdfs haadmin -transitionToActive做主動切換或者依賴 ZKFC 自動切換。這里有一個容易踩的坑YARN 的 ResourceManager 也需要高可用但很多團隊只配了 NameNode HA忘了 RM。實際跑起來RM 掛了整個集群任務全掛。我的建議是 RM 也要至少兩個實例配合 ZK 做 active/standby并且保證 RM 和 NM 的地址能通過 Service 解析否則節點重啟后 NM 找不到 RM。3.4 數據本地性怎么保前面說了“同 Pod”方案是最省心的但如果團隊出于隔離性或者升級便利考慮一定要把 DataNode 和 NodeManager 拆開部署那數據本地性就得靠調度策略硬撐。可以用 podAffinity 讓 NodeManager 盡量調度到 DataNode 所在節點affinity: podAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: labelSelector: matchExpressions: - key: app operator: In values: - hadoop-datanode topologyKey: kubernetes.io/hostname但我要提醒preferred是“盡量”不是“必須”。如果 DataNode 所在節點資源不足NodeManager 仍然會被調度到別處數據本地性就破了。如果一定要強制可以把preferred改成required但那樣做容易導致資源碎片化Pod 調度不上去。我個人還是推薦同 Pod 方案它的本地性是 100% 保證的而且運維起來就是“重啟一個 Pod 就是重啟一個 DNNM”心智負擔小很多。4. 云原生數據湖的周邊配套CMDB、監控與對象存儲4.1 HDFS、對象存儲與訪問協議的選擇Hadoop 3.x 容器化之后很多團隊會順手把數據湖的那套也接進來。這時候一個繞不開的問題是到底是繼續用 HDFS 還是上對象存儲OSS/S3。我的觀點很明確HDFS 不該被完全替代但也不會是唯一存儲。對于高并發、低延遲、強一致性的核心數倉層HDFS 依然是最穩的選擇對于海量非結構化數據、日志歸檔、機器學習樣本這類“寫一次很少改”的數據直接放在對象存儲里更省錢、擴容性也更好。Hadoop 3.x 對對象存儲的兼容已經做得很成熟S3A 文件系統插件可以直接讓你用 HDFS 的命令行和 API 去讀寫對象存儲property namefs.s3a.endpoint/name valuehttp://oss-cn-hangzhou-internal.aliyuncs.com/value /property property namefs.s3a.access.key/name valueyour-access-key/value /property property namefs.s3a.secret.key/name valueyour-secret-key/value /property配置好之后在 Spark、Hive 里可以直接這樣讀SELECT * FROM parquet.s3a://your-bucket/path/to/table;這么做的最大好處是計算容器化的 Spark/Flink和存儲對象存儲完全分離計算集群可以隨時縮容到零存儲成本按量付費。這就是標準的云原生數據湖姿勢。HDFS 在中間扮演的角色會逐步降級為“本地熱數據緩存”或“數倉核心層”而不是所有數據都往里面塞。4.2 容器化集群如何納入 CMDB 和監控體系容器化之后最直觀的痛點是主機和進程變成了“動態資源”今天這個 Pod 在 node-3明天可能就漂到 node-5。傳統 CMDB 里維護“哪臺機器跑了哪個進程”的做法徹底失效了。我在項目里的做法是讓 CMDB 丟掉靜態主機清單改成對接 K8s API 和 YARN REST API。定期拉取 Pod 列表、Node 列表、Pod IP、標簽和狀態把 DataNode、NodeManager 的動態 IP 和端口登記到 CMDB 的資產表里。只要 Pod 重建IP 變了CMDB 在下一輪采集周期就能自動更新核心是“以 API 為準不做人工維護”。與之配套的是監控體系。HDFS 和 YARN 都暴露 JMX 指標我們通過 Prometheus 定期抓取 NameNode、DataNode、ResourceManager、NodeManager 的 JMX 端口再配合 Grafana 做告警。我維護監控時必看的幾個指標指標來源告警閾值我的經驗值NameNode RPC 處理平均時間JMXRpcActivityForPort超過 50ms 就要排查DataNode 存活數HDFS FSNamesystem低于配置副本數即告警EC 重建任務積壓數JMXPendingReconstructionBlocks持續上漲要關注NodeManager 可用內存YARN ClusterMetrics低于集群總量 10% 告警容器本地讀命中率YARN NodeManager 指標低于 70% 檢查調度策略容器化集群里網絡監控特別容易被忽略。Pod 之間的流量經過 service 轉發后ServiceMesh 或 DNS 解析失敗會直接讓跨 Pod 的 RPC 超時。我建議把 CoreDNS 的監控指標也加進去Pod 沒有就重啟 CoreDNS是排查分布式集群詭異問題時最常做的操作之一。4.3 漸進式遷移路線從雙跑到存量搬遷最后說遷移路線。真的到了要把生產 Hadoop 集群從物理機遷到 K8s 的階段我強烈建議不要做“大爆炸式遷移”而是下面三步走第一步做雙跑。新集群容器化部署成功之后把核心的數據導入流程復制一份跑增量或抽樣任務兩側做數據校驗確認新老集群跑出來的結果一致。這個階段別急著切流量觀察至少兩周。第二步切寫入。把新的寫入任務比如 Flink 實時入庫、離線調度逐步切到新集群老集群只保留只讀服務避免雙寫帶來的數據不一致問題。這時候重點觀察新集群的吞吐和延遲是否滿足業務要求。第三步存量搬遷。老集群的歷史數據用 distcp 或 ReplicationManager 遷移到新集群或對象存儲。遷移過程要分批執行每遷完一批就做一次 fsck 和數據校驗確認無誤后再釋放老集群資源。我個人看到過太多團隊跳過前兩步直接做第三步結果數據校驗不過關、業務回滾鏈路斷裂最后花了幾倍的時間收拾爛攤子。云原生是趨勢但遷移一定要穩扎穩打。5. 常見問題與排錯實錄5.1 糾刪碼相關的坑問題 1設置了 EC 策略但新文件還是 3 副本這個坑我幾乎每次跟人聊都會提。EC 策略是綁定“目錄路徑”的而且是目錄創建時生效的一種屬性。如果你是在已有目錄上執行hdfs ec -setPolicy那它只對設置之后“新創建的文件”生效已有的文件不會自動重編碼。另外如果你用 distcp 遷移默認情況下 distcp 會把源目錄的復制策略帶過去目標目錄如果本身沒有 EC 策略文件可能仍是 3 副本。排查方式很簡單hdfs ec -getPolicy -path /path看目錄策略再hdfs fsck /path -files -blocks -locations看具體文件的塊數量。如果是 3 副本每個 block 會出現在 3 個 location如果是 ECblock 組會分布在 9 個 locationRS-6-3。問題 2EC 目錄快照失敗EC 和 Snapshot 不兼容是官方限制。如果你業務上依賴目錄快照要么放棄在這個目錄上用 EC要么改造備份方案用 distcp 定期把 EC 目錄同步到另一個普通目錄做快照。問題 3EC 重建慢壞了塊幾天都沒補回來我在實際環境里遇到過一臺物理機宕機180 多個 EC block 處于重建狀態但后臺重建速度很慢。原因有兩個一是 NameNode 默認的 EC 重建調度線程數量有限二是重建任務本身要大量讀取其他數據塊做解碼計算帶寬被業務流量擠占。解決方式是在低峰期臨時調大重建并發量并把重建流量限制在一個可控范圍比如調整dfs.namenode.ec.reconstruction.threads和相關帶寬設置。具體配置名稱以你所用發行版文檔為準不同發行版可能不太一樣。5.2 容器化相關的坑問題 1NodeManager 找不到 ResourceManagerK8s 里最常見的故障。NM 啟動后要向 RM 注冊如果 NM 的配置里 RM 地址是某個 Pod IP 或者已經變化的 Service 地址注冊就失敗。解決方法是在 yarn-site.xml 里配置 RM 的地址為 K8s 的 Service DNS 名稱別寫 IP并且在所有 NM 的配置里保持一致。問題 2DataNode 反復重啟報端口沖突或盤掛不上DataNode 用的端口如果和其他服務沖突容器起不來要先kubectl logs看日志。盤掛不上的問題通常出在 PVC 的 StorageClass 上本地盤和網絡盤要區分清楚別把ReadWriteOnce的盤配成跨節點共享那是 CephFS 和 NFS 該干的活。問題 3Pod 調度不上去通常是資源不足或者親和性把 Pod 卡死了。我用kubectl describe pod xxx看調度事件時經常見到0/12 nodes are available這類信息。如果是反親和性太嚴格把requiredDuringSchedulingIgnoredDuringExecution改成preferredDuringSchedulingIgnoredDuringExecution會緩解很多。5.3 一條好用的排錯路徑容器化集群排錯我總結了一套固定路線團隊新同學照著走基本能解決 80% 的問題先用kubectl get pods -n bigdata -o wide確認 Pod 狀態和所在節點。再用kubectl logs pod -c container --tail200看日志優先找 ERROR、WARN、Exception。如果是跨 Pod 通信問題kubectl exec -it pod -- curl telnet://service:port或nslookup service檢查 DNS 解析和連通性。最后再用 Hadoop 側命令確認集群狀態hdfs dfsadmin -report、yarn node -list、hdfs fsck。這套路徑的好處是先看 K8s 層再看 Hadoop 層邏輯清晰不會在兩邊來回跳。我見過很多同事在 Hadoop 日志里翻半天結果發現就是 DNS 解析失敗導致連不上另一臺 Pod。6. 最后說幾句實操心得內容寫到這兒該聊的架構、步驟、坑都聊得差不多了最后就分享幾個花了錢和時間才換來的體會。第一糾刪碼不要貪心。RS-6-3 省空間但不適合所有數據。我的原則是冷數據、大文件、批量導入的數據用 EC熱數據、小文件、要求低延遲的路徑老老實實留 3 副本。省下的空間最終要還回去一部分給性能和運維成本。第二容器化不是目的彈性和成本才是。如果你當前物理機集群跑得穩、容量夠沒有資源利用率低或者擴縮容慢的痛點沒必要為了“上容器”而上容器。容器化最大的價值是把基礎設施變成可編程資源適合業務波動大、多環境隔離要求高的團隊。第三Hadoop 3.x 的升級路徑最好是“小步快跑”。先在一個低風險業務上做 EC 試點再在一個新環境上做容器化驗證每一步都跑出結果再擴大范圍。不要同一時間既改存儲模型又改部署方式否則出了問題你都分不清是 EC 的鍋還是 K8s 的鍋。這套從 EC 到容器化的玩法我前后推了接近一年才穩定下來。如果你正在折騰類似的事希望這篇能幫你少走幾條彎路。