:從告警到根因定位)
1. 從一次凌晨告警說起WRITE_BLOCK 報錯到底在說什么凌晨兩點多集群監(jiān)控突然推來一批告警核心鏈路的寫入任務大面積超時。登錄到出問題的 DataNode 節(jié)點上翻日志滿屏都是同一句話DataXceiver error processing WRITE_BLOCK operation。這個報錯在分布式存儲運維里算是老熟人了但每次出現(xiàn)的原因都不太一樣處理起來不能一概而論。先把這句話拆開看。DataXceiver是 DataNode 內部負責處理數(shù)據(jù)讀寫請求的工作線程每一個來自客戶端或者其他 DataNode 的數(shù)據(jù)傳輸請求都會交給一個 DataXceiver 線程去處理。WRITE_BLOCK operation指的是寫數(shù)據(jù)塊這個具體操作。所以整句話的字面意思是DataNode 在處理寫數(shù)據(jù)塊請求時拋出了異常。它本身是一個外層包裝異常真正的原因往往藏在后面的堆?;蛘逤aused by里。很多人第一次遇到這個報錯會慌因為它看起來像是數(shù)據(jù)丟了或者磁盤壞了。實際上絕大多數(shù)情況下它只是某一次寫塊請求失敗了HDFS 本身有副本機制和管線恢復機制單次失敗通常不會導致數(shù)據(jù)丟失。真正需要關注的是為什么失敗、失敗頻率有多高、是否影響到了上層任務的可用性。這篇內容適合誰看如果你正在維護一套 HDFS 集群或者負責基于 HDFS 的數(shù)據(jù)平臺被這個報錯反復騷擾過那這篇就是寫給你的。我會從報錯的觸發(fā)鏈路講起把常見的幾類根因拆開給出可復現(xiàn)的排查步驟最后分享幾個我在實際處理中踩過的坑和總結出來的經(jīng)驗。全文基于常見的生產(chǎn)實踐整理具體參數(shù)請以你自己集群的實際情況為準。需要先明確一個認知WRITE_BLOCK失敗從來不是孤立事件它背后一定對應著某個資源維度出了問題——磁盤、網(wǎng)絡、內存、線程、配置或者上游客戶端的異常行為。排查的核心思路就是順著寫管線把每一段都驗證一遍而不是盯著這一行日志反復看。2. 寫塊請求的完整鏈路先搞清楚失敗發(fā)生在哪一段要排查這個報錯得先知道一次WRITE_BLOCK請求從發(fā)起到完成中間經(jīng)過了哪些環(huán)節(jié)。只有把鏈路畫清楚才能定位到具體是哪一段斷了。2.1 客戶端發(fā)起寫請求到 DataNode 接收的流程當客戶端要寫一個文件時它先向 NameNode 申請創(chuàng)建文件NameNode 返回一組 DataNode 列表默認 3 副本構成一條寫管線pipeline??蛻舳税褦?shù)據(jù)切成 packet發(fā)給管線上的第一個 DataNode。第一個 DataNode 收到后一邊往本地磁盤寫一邊把 packet 轉發(fā)給第二個 DataNode第二個再轉發(fā)給第三個。這個轉發(fā)過程用的就是DataXceiver線程。每個 DataNode 上會有一個DataXceiverServer在監(jiān)聽數(shù)據(jù)傳輸端口默認 50010 或新版本的 9866每來一個連接就 fork 一個DataXceiver線程去處理。所以DataXceiver error processing WRITE_BLOCK operation這個報錯可能出現(xiàn)在管線上的任意一個節(jié)點不一定是你最初懷疑的那個。關鍵點在于報錯日志里通常會帶上block的標識和datanode的信息還會有一個Caused by。如果你只看到外層這一句說明日志被截斷了需要去翻完整的堆棧。這一點后面會專門講。2.2 管線上的三個角色與各自的失敗表現(xiàn)寫管線上有三種角色它們的失敗表現(xiàn)和排查重點完全不同角色職責典型失敗表現(xiàn)排查重點管線首節(jié)點接收客戶端數(shù)據(jù)并轉發(fā)報錯常伴隨客戶端連接超時網(wǎng)絡、線程數(shù)、接收緩沖區(qū)管線中間節(jié)點接收上游并轉發(fā)下游報錯常伴隨上游節(jié)點重試磁盤 IO、下游連通性管線末節(jié)點只接收不轉發(fā)報錯多為本地磁盤寫入失敗磁盤空間、磁盤健康、權限理解這個表格很重要。比如你發(fā)現(xiàn)報錯總是集中在某幾個節(jié)點而且這些節(jié)點都是管線末節(jié)點那大概率是磁盤問題如果報錯隨機分布且伴隨大量連接超時那更可能是網(wǎng)絡或者線程資源問題。2.3 為什么這個報錯容易被誤判我見過不少團隊一看到WRITE_BLOCK失敗第一反應就是磁盤壞了然后急急忙忙去換盤。結果換完盤發(fā)現(xiàn)報錯還在白白折騰一場。原因就在于這個報錯是外層包裝它把底層各種異常都統(tǒng)一成了這一句話。常見的Caused by包括java.io.IOException: Too many open files、java.net.SocketTimeoutException、java.nio.channels.ClosedChannelException、DiskOutOfSpaceException、java.io.EOFException等等。每一種對應的根因和處理方式都不一樣。所以排查的第一步永遠是拿到完整的異常堆??辞宄?Caused by 是什么。提示如果日志里只有外層這一句先檢查日志配置是否把堆棧截斷了或者去 DataNode 的hadoop-hdfs-datanode-*.log里搜索同一個 block id通常能找到更完整的上下文。3. 按 Caused by 分類五類高頻根因的定位方法拿到完整堆棧之后就可以按 Caused by 來分類處理了。下面這五類是我在實際運維中遇到頻率最高的基本覆蓋了八成以上的場景。3.1 磁盤類空間不足、壞盤與 IO 瓶頸磁盤問題是最直觀的一類。DiskOutOfSpaceException說明 DataNode 的數(shù)據(jù)目錄寫滿了。HDFS 有預留空間機制當某個數(shù)據(jù)目錄的可用空間低于閾值由dfs.datanode.du.reserved控制默認 0但很多生產(chǎn)環(huán)境會設置成幾十 GBDataNode 就會拒絕往這個目錄寫。排查方法很直接# 查看各數(shù)據(jù)目錄的使用率 df -h | grep -E data|hdfs # 查看 DataNode 數(shù)據(jù)目錄配置 grep -A5 dfs.datanode.data.dir /path/to/hdfs-site.xml如果確認是空間問題處理方式有兩種一是清理過期數(shù)據(jù)或者調整副本策略釋放空間二是臨時調低dfs.datanode.du.reserved不推薦長期這么做。更根本的做法是擴容或者做數(shù)據(jù)均衡。壞盤的表現(xiàn)則更隱蔽。磁盤可能還有空間但讀寫出現(xiàn)大量錯誤日志里會出現(xiàn)Input/output error或者DiskChecker相關的報錯。這時候要用smartctl之類的工具檢查磁盤健康狀態(tài)確認是物理壞道還是文件系統(tǒng)損壞。IO 瓶頸是另一回事。磁盤沒壞、空間也夠但 IO 被打滿了導致寫塊超時。這種情況通常伴隨SocketTimeoutException因為 DataNode 在規(guī)定時間內沒寫完上游等不及就斷了。排查時看iostat -x 1的%util和await指標如果%util長期接近 100%說明磁盤已經(jīng)是瓶頸了。3.2 網(wǎng)絡類連接超時、丟包與帶寬打滿網(wǎng)絡問題在跨機架、跨機房部署的集群里特別常見。SocketTimeoutException和ClosedChannelException是典型表現(xiàn)。寫管線是串行的任何一個節(jié)點之間的網(wǎng)絡抖動都會導致整條管線失敗。排查網(wǎng)絡問題我一般按這個順序來先用ping和traceroute確認基礎連通性和路由是否正常。用netstat -s看有沒有大量的重傳retransmit和丟包統(tǒng)計。用iftop或者sar -n DEV 1看網(wǎng)卡帶寬是否被打滿。檢查交換機端口是否有 CRC 錯誤或者丟包計數(shù)。帶寬打滿是個容易被忽略的點。HDFS 寫數(shù)據(jù)本身就很吃帶寬如果同一臺機器上還跑了其他吃帶寬的服務比如另一個大數(shù)據(jù)組件就容易互相擠占。我遇到過一臺 DataNode 上同時跑了數(shù)據(jù)同步任務把網(wǎng)卡跑滿導致 HDFS 寫塊頻繁超時。后來把任務錯峰調度就解決了。3.3 資源類文件句柄、線程數(shù)與內存Too many open files是資源類問題里最經(jīng)典的。DataNode 每個連接、每個塊文件都要占用文件句柄如果ulimit -n設置得太小或者句柄泄漏就會在寫塊時失敗。檢查方法# 查看 DataNode 進程的句柄限制 cat /proc/$(pgrep -f DataNode)/limits | grep open files # 查看當前打開的句柄數(shù) ls /proc/$(pgrep -f DataNode)/fd | wc -l如果發(fā)現(xiàn)句柄數(shù)接近上限要么調大ulimit要么排查是否有句柄泄漏。句柄泄漏通常和某些異常路徑?jīng)]有正確關閉流有關需要結合代碼或者版本已知問題來判斷。線程數(shù)問題表現(xiàn)為DataXceiver線程創(chuàng)建失敗。DataNode 對并發(fā)傳輸線程數(shù)是有上限的dfs.datanode.max.transfer.threads默認 4096。如果并發(fā)寫請求特別多線程池被打滿新的寫請求就會被拒絕。這種情況在高負載集群里會出現(xiàn)表現(xiàn)為報錯集中在業(yè)務高峰期。內存問題相對少見但也不能忽視。DataNode 的堆內存如果設置過小在大量并發(fā)寫的時候可能觸發(fā)頻繁 GC導致處理變慢甚至 OOM???GC 日志和jstat輸出能快速判斷。3.4 配置類超時參數(shù)與副本策略不匹配配置問題往往是最冤的一類因為硬件和網(wǎng)絡都沒問題就是參數(shù)沒調好。幾個關鍵參數(shù)值得關注參數(shù)默認值作用調優(yōu)建議dfs.client.socket-timeout60s客戶端讀寫超時高延遲網(wǎng)絡可適當調大dfs.datanode.socket.write.timeout480sDataNode 寫超時大塊寫入場景可調大dfs.datanode.max.transfer.threads4096最大傳輸線程數(shù)高并發(fā)集群需調大dfs.datanode.du.reserved0預留磁盤空間生產(chǎn)環(huán)境建議設置這些參數(shù)之間是有聯(lián)動關系的。比如你把客戶端超時調得很短但網(wǎng)絡延遲本來就高那就會頻繁超時反過來超時調得太長故障發(fā)現(xiàn)就會變慢。我的經(jīng)驗是先測量實際的 P99 寫入延遲再據(jù)此設置超時留出 2 到 3 倍的余量。副本策略不匹配也會引發(fā)問題。比如某些目錄設置了很高的副本數(shù)但集群節(jié)點數(shù)不夠導致寫管線無法湊齊足夠的節(jié)點寫塊自然就失敗了。3.5 客戶端類上游異常行為引發(fā)的連鎖反應有時候問題不在 DataNode而在客戶端。比如客戶端程序有 bug寫數(shù)據(jù)時頻繁創(chuàng)建和關閉連接導致 DataNode 端連接數(shù)暴漲或者客戶端寫入速度遠超 DataNode 處理能力把管線壓垮。還有一種情況是客戶端所在機器資源不足比如客戶端自己的網(wǎng)絡或者磁盤有問題導致它發(fā)給 DataNode 的數(shù)據(jù)本身就是殘缺的DataNode 校驗失敗后報錯。這類問題的排查需要同時看客戶端日志和 DataNode 日志把兩邊的時間線對齊。我處理過一個案例某任務在特定時間段必然失敗查了半天 DataNode 沒問題最后發(fā)現(xiàn)是客戶端所在機器在那個時間段有定時任務在跑搶占了資源。這種跨組件的關聯(lián)問題單看 HDFS 日志是看不出來的。4. 一套可復現(xiàn)的排查流程從告警到定位根因前面講了分類這一節(jié)把排查流程串起來給一套可以直接照著做的步驟。這套流程我在多個集群上驗證過基本能在半小時內定位到大部分問題。4.1 第一步確認影響范圍與失敗頻率不要一上來就扎進單條日志。先回答幾個問題是單節(jié)點還是多節(jié)點是偶發(fā)還是持續(xù)影響的是所有寫入還是特定任務# 統(tǒng)計最近一小時內報錯涉及的節(jié)點 grep DataXceiver error processing WRITE_BLOCK /var/log/hadoop-hdfs/*.log | \ awk -F[: ] {print $4} | sort | uniq -c | sort -rn # 統(tǒng)計報錯頻率按分鐘 grep DataXceiver error processing WRITE_BLOCK /var/log/hadoop-hdfs/*.log | \ awk {print substr($2,1,5)} | uniq -c如果報錯集中在單節(jié)點重點查那臺機器的磁盤和網(wǎng)絡如果分散在多節(jié)點重點查集群級別的資源或者客戶端行為如果只在特定時間段出現(xiàn)重點查那個時間段的負載情況。4.2 第二步抓取完整堆棧與關聯(lián)上下文拿到報錯所在的日志文件后用 block id 把相關的日志都撈出來# 假設 block id 是 BP-xxx-192.168.1.1-1234567890 grep BP-xxx-192.168.1.1-1234567890 /var/log/hadoop-hdfs/*.log重點看Caused by那一行以及報錯前后幾秒內同一節(jié)點上的其他日志。很多時候真正的根因日志就在報錯的前幾行比如磁盤告警、GC 日志、連接拒絕等。4.3 第三步按維度逐項驗證拿到 Caused by 之后按下面的清單逐項驗證磁盤df -h看空間iostat -x 1看 IOsmartctl -a看健康。網(wǎng)絡ping、traceroute、netstat -s、sar -n DEV 1。資源ulimit、/proc/pid/fd、jstat -gc、top -H。配置核對hdfs-site.xml和core-site.xml里的關鍵參數(shù)??蛻舳丝纯蛻舳巳罩敬_認寫入模式是否異常。這一步的關鍵是用數(shù)據(jù)說話不要憑感覺。比如懷疑磁盤慢就拿出iostat的數(shù)據(jù)懷疑網(wǎng)絡丟包就拿出netstat -s的重傳計數(shù)。4.4 第四步驗證修復效果定位到根因并處理后不能只看報錯有沒有消失還要驗證寫入性能是否恢復正常。我一般會跑一個小的寫入測試# 寫入一個測試文件觀察是否成功和耗時 hdfs dfs -Ddfs.client.socket-timeout120000 -put /tmp/testfile /tmp/testfile_hdfs # 查看文件塊分布和副本狀態(tài) hdfs fsck /tmp/testfile_hdfs -files -blocks -locations如果測試通過且耗時正常再觀察一段時間線上任務的報錯率。確認穩(wěn)定后把這次的根因和處理過程記錄下來形成知識庫下次遇到類似問題能快速響應。5. 幾個真實踩坑案例教科書不會告訴你的細節(jié)理論講完了分享幾個我在實際處理中踩過的坑。這些細節(jié)在官方文檔里基本找不到但往往就是解決問題的關鍵。5.1 案例一句柄數(shù)夠用但句柄泄漏了有一次集群報Too many open files我第一反應是調大ulimit。調完之后確實好了幾天但很快又復現(xiàn)。后來用lsof分析發(fā)現(xiàn)DataNode 進程的句柄數(shù)在緩慢增長說明有泄漏。進一步排查發(fā)現(xiàn)是某個版本的已知問題在特定異常路徑下塊文件的輸入流沒有正確關閉。升級到修復版本后問題消失。這個案例的教訓是句柄數(shù)不夠有兩種可能一種是限制太小一種是泄漏處理方式完全不同。前者調參數(shù)后者要升級或者打補丁。5.2 案例二磁盤沒滿但 inode 用完了df -h顯示磁盤還有 30% 空間但寫塊就是失敗。查了半天才發(fā)現(xiàn)是 inode 用完了。HDFS 會為每個塊創(chuàng)建多個文件數(shù)據(jù)文件、校驗文件、元數(shù)據(jù)文件如果集群里小文件特別多inode 消耗會非???。# 查看 inode 使用情況 df -i這個坑的隱蔽性在于df -h和df -i是兩個維度很多人只看前者。解決辦法是治理小文件或者調整 DataNode 的塊文件組織方式新版本有相關優(yōu)化。5.3 案例三網(wǎng)絡看起來正常但 MTU 不匹配跨機架通信時如果兩端 MTU 設置不一致大包會被分片或者丟棄表現(xiàn)為偶發(fā)的寫塊失敗。ping小包正常但ping -s 1472大包就丟。這種問題在混合網(wǎng)絡環(huán)境里特別常見。排查方法# 測試大包連通性 ping -M do -s 1472 target_ip如果大包不通就要檢查兩端的 MTU 配置和交換機設置。這個坑我踩過一次查了整整一天最后發(fā)現(xiàn)是某臺新加入的機器 MTU 沒對齊。5.4 案例四GC 停頓導致的假性超時DataNode 堆內存設置偏小在高峰期頻繁 Full GC每次停頓幾秒。寫塊請求在 GC 期間被掛起超過客戶端超時時間就報錯了。表面看是網(wǎng)絡超時實際是 GC 問題。判斷方法把 GC 日志和報錯時間線對齊如果報錯總是出現(xiàn)在 GC 停頓之后基本就能確認。解決辦法是調大堆內存或者優(yōu)化 GC 參數(shù)。6. 預防勝于救火把 WRITE_BLOCK 失敗擋在發(fā)生之前排查和修復是事后手段真正省心的做法是提前預防。這一節(jié)分享幾個我在日常運維中堅持做的動作。6.1 監(jiān)控指標要盯住這幾個與其等報錯不如盯住幾個關鍵指標在問題惡化前就介入磁盤使用率和 inode 使用率設置閾值告警比如 80% 就提醒。磁盤 IO 的 await 和 %util長期高位說明磁盤是瓶頸。DataNode 的句柄數(shù)和線程數(shù)接近上限就要處理。GC 停頓時間單次超過 1 秒就要關注。網(wǎng)絡重傳率持續(xù)升高說明網(wǎng)絡質量在下降。這些指標用常見的監(jiān)控系統(tǒng)都能采集到關鍵是要設置合理的閾值并且有人響應。6.2 參數(shù)配置的幾條經(jīng)驗參數(shù)沒有萬能值但有幾條經(jīng)驗可以借鑒dfs.datanode.du.reserved一定要設置給系統(tǒng)留出緩沖空間避免磁盤被寫滿導致節(jié)點不可用。dfs.datanode.max.transfer.threads根據(jù)集群并發(fā)量調整高并發(fā)場景適當調大。超時參數(shù)要基于實測延遲設置不要照搬默認值。定期 review 配置集群規(guī)模變化后原來的參數(shù)可能就不合適了。6.3 定期做健康檢查我習慣每周做一次集群健康檢查包括磁盤健康、網(wǎng)絡質量、關鍵指標趨勢、報錯日志統(tǒng)計。這個習慣幫我提前發(fā)現(xiàn)過好幾次潛在問題比如某塊盤的 SMART 指標在惡化、某個機架的網(wǎng)卡錯誤計數(shù)在增長。健康檢查不需要很復雜寫個腳本把關鍵命令跑一遍輸出一份報告就行。關鍵是堅持做并且對異常項跟進處理。7. 寫在最后關于這個報錯的一點個人體會處理了這么多次WRITE_BLOCK失敗我最大的體會是不要被報錯的字面意思帶偏。它只是一個信號告訴你寫塊這件事出問題了但具體是什么問題必須靠完整的堆棧和系統(tǒng)性的排查來確定。另一個體會是很多看似突發(fā)的故障其實早有征兆。磁盤使用率緩慢上升、GC 停頓逐漸變長、網(wǎng)絡重傳率悄悄升高這些都是預警信號。如果平時有監(jiān)控和健康檢查的習慣就能在問題爆發(fā)前處理掉而不是等到凌晨被叫起來救火。最后分享一個小技巧建立一個故障-根因-處理的對照表每次處理完問題就更新進去。時間長了這張表就是你自己集群的專屬排查手冊比任何通用文檔都好用。因為每個集群的硬件、網(wǎng)絡、業(yè)務負載都不一樣只有自己積累的經(jīng)驗才最貼合實際。如果你正在被這個報錯困擾希望這篇內容能幫你理清思路。排查的過程可能有點繁瑣但只要按鏈路一段段驗證總能找到根因。