| 作者: 周曉,騰訊遊戲 CROS 體系高級工程師,負責多套HDFS集羣的維護管理,併爲Apache Hadoop社區提交過2個Patch。同時也作爲遊戲DBA,穩定支撐包括穿越火線、天天酷跑等在內多款遊戲的DB管理維護。

這次我吐血整理了一些在維護hdfs工作中遇到的問題,有的是血的教訓,有的是花了不少功夫定位,也有的是一些知識點或者技巧,其中有兩個補丁已經合併到apache hadoop官方。最後根據這些問題處理經驗,彙總了hadoop hdfs集羣需要關注的告警指標。

一、定期block全盤掃描,引起dn心跳超時而脫離集羣

1. 現象:

hdfs有一個目錄掃描機制,默認6小時會全盤掃描一次所有block,判斷與內存裏的那份blockMap是否一致。參考鏈接
https://blog.cloudera.com/hdfs-datanode-scanners-and-disk-checker-explained/ 。

在小文件比較多的情況,掃描的時候特徵很明顯——磁盤的iops很高,但吞吐量很低。當然這不是引起datanode心跳超時的原因,真正的原因是處理掃描後的結果,比如比較完發現有20000個block不一致,在修復這些block時不斷的持有了 FsDatasetImpl 這個對象的一把鎖,在磁盤比較慢的情況下,可能需要5分鐘甚至10分鐘處理完,從而一直阻塞讀、寫、心跳的線程。

詳細的可以瞭解 HDFS-14476 (網址鏈接https://issues.apache.org/jira/browse/HDFS-14476),包括一些特徵、證據,以及block修復邏輯,細節比較多。

2. 解決:

我們這邊加了個patch(已合入官方版本 2.10和3.x),在處理異常block的時候,中間休息2秒,處理一下正常的請求,不至於datanode卡住甚至離線。

修復後的結果也是很明顯,datanode心跳平滑了許多,如下圖所示。

二、namenode遷移裁撤,遇到客戶端無法寫入

1. 現象:

在需要遷移/裁撤namenode時,一般思路是保持 namenode hostname 不變,滾動遷移 standby 的方式遷移。

但是在我們的遷移實踐中,發現 hdfs namenode 完成遷移後,集羣正常,但 hdfs 客戶端訪問異常。在 yarn 這樣的長任務場景下,會導致文件讀寫一直失敗,直到 yarn nodemanager 重啓。

具體問題是這樣的, client使用的是 ConfiguredFailoverProxyProvider ,client啓動之後會根據當時的 inetsocket創建nn1,nn2兩個namenode proxy,這個在任何網絡異常的情況下都不會重新創建。

client 的 updateAddress 方法能檢測到namenode ip發生了變化,但由於那個異常沒有捕獲,本該在下次循環使用正確的 namenode ip 就能正常,但拋出異常後導致client重新連接namenode,然而上面的 namenode proxy 還是舊地址,SetupConnection 異常,又進入updateAddress判斷邏輯,返回true又去建連接,陷入了死結。

2. 復現步驟:

① 打開一個hdfsclient,長時間寫一個文件 hdfs put;

② 更新hdfs新namenode hostname-ip;

③ stop old nn2, start new nn2;

④ 更新客戶端的namenode hostname-ip (client還在操作文件);

⑤ 切換到新namenode hdfs haadmin -failover nn1 nn2;

⑥ 此時會發現client一直報錯, 在yarn客戶端啓動的週期內,哪怕是新文件寫入,依舊會報錯。

3.解決:

對 ConfiguredFailoverProxyProvider 打了個patch,就是在client failover之後,也進行updateAddress判斷,如果有ip變動,就重新 createProxy。驗證這個patch同樣有效。不過在client那邊統一捕獲會比較好,因爲還有其他類型的HaProvider可能也有這個問題。

這個問題的 patch 已經被合入 Apache Hadoop 3.4,見 HADOOP-17068(網址鏈接https://issues.apache.org/jira/browse/HADOOP-17068)。我們用的版本是 2.6.0-cdh5.4.11 ,patch也已合入官方版本。

除了從根源問題上解決,也可以在 namenode 遷移操作時,在老節點上啓用端口轉發,再逐個重啓 yarn,避免引起大範圍故障。

三、集羣dn不均衡導致文件寫入失敗

1. 現象:

集羣將滿時,擴容了批機器緩解空間。運行了2個星期客戶端突然報文件寫入失敗。

2. 原因:

hdfs在部分datanode空間滿的情況下,理論會自動挑選其它可用的空閒節點。由於 dfs.datanode.du.reserved配置不當,導致依然會選中滿節點。具體是dfs.datanode.du.reserved如果小於分區block reserved,在磁盤用滿時就會出現。

org.apache.hadoop.ipc.RemoteException(java.io.IOException): File /kafka/xxxtmp.parquet could only be replicated to 0 nodes instead of minReplication (=1).  \
There are 14 datanode(s) running and no node(s) are excluded in this operation.

3. 解決:

①擴容完,跑rebalance;

②修改磁盤分區的block reserved,使其小於 dfs.datanode.du.reserved;

③增加單個datanode容量告警。

四、做 rebalance 時速度很慢

1. 解決:

啓動 rebalance 命令./start-balancer.sh -threshold 10,如果需要提高速度可以修改限流帶寬hdfs dfsadmin -setBalancerBandwidth 52428800 。

但是 datanode 上同時接收 blocks 併發數,是不能在線調整的(或者說只能調小),調整hdfs-site.xml默認的balance參數,並重啓。

dfs.balancer.moverThreads=1000
dfs.balancer.dispatcherThreads=200
dfs.datanode.balance.max.concurrent.moves=50

如果啓動balance時,嘗試以更高的併發執行,datanode會判斷沒有足夠的線程接收 block: IOException: Got error, status message Not able to copy block ... because threads quota is exceeded。

當 move 出現失敗時,遷移速度是指數級下降的,因爲move block失敗默認會sleep一段時間。

./start-balancer.sh -threshold 5\
 -Ddfs.datanode.balance.max.concurrent.moves=20 \
 -Ddfs.datanode.balance.bandwidthPerSec=150000000 \
 -Ddfs.balancer.moverThreads=500 \
 -Ddfs.balancer.dispatcherThreads=100
五、給datanode在線增加磁盤

1. 解決辦法:

騰訊雲上的機器,可以直接在原有 datanode 上直接掛在新的磁盤,快速給hdfs擴容。

增加磁盤,不需要重啓datanode。 (前提是設置了 dfs.datanode.fsdataset.volume.choosing.policy爲AvailableSpaceVolumeChoosingPolicy)。

① 掛 載後,先建立hadoop數據目錄並修正權限;

②在hdfs-site.xml 里加上新目錄配置 dfs.datanode.data.dir;

③可以使用 reconfig 命令使其生效:  hdfs dfsadmin -reconfig datanode dn-x-x-x-x:50020 start。

六、namenode設置了HA,但故障時未成功切換

1. 現象:

active namenode 內存故障,主備切換失敗

2. 原因:

dfs.ha.fencing.methods設置爲了ssh,但是並不能登錄其他namenode執行fence

3. 解決:

生成ssh key,免密碼登錄。或者改成shell(/bin/true),強切。注意,修改fence方式後,需要重啓zkfc。

七、hdfs client input/output error

1. 現象:

執行 hdfs 客戶端命令報錯 input/output error,試着拷貝 hadoop / jdk 的介質目錄,亦發現文件損壞。有時會發現 jvm core。

2.原因:

磁盤存在壞塊,剛好hdfs或者jdk的 jar 庫損壞。通過觀察 messages 發現有 sda IO Input/Output Error 。

使用badblocks -s -v -o bb.log /dev/sda 可以看到磁盤損壞了哪些扇區。

3. 解決:

從其他機器,拷貝一份正常的介質。

八、hdfs誤將 data 盤作爲數據盤

1. 現象:

誤將系統盤作爲了dfs.datanode.data.dir,運行一段時間後,這個分區很容易最先滿。

2.原因:

這個是配置上的問題,理解datanode的工作方式,可以快速的將這個分區裏的block挪到正確的磁盤分區。

3. 解決:

處理方法就是停止datanode,拷貝/data block到其它分區,刪掉/data的配置。因爲datanode上block的位置是每次啓動的時候,掃描上報給namenode,所以可以做物理拷貝。

可以使用拷貝命令cp -a /data/hadoopdata/current/BP-*-*/current/finalized/* /data1/hadoopdata/current/BP-*-*/current/finalized/ ,不能拷貝整個 hadoopdata 目錄,因爲VERSION文件裏面的storageID不同。

九、使用decomiss方式將datanode退服時,客戶端讀寫異常

1. 現象:

將datanode加入 exclude ,正常 decomissing 的方式退役節點,應用層反饋 spark 任務部分異常,報錯 Unable to close file because the last block doest not have enough number of replicas ,但該集羣一些其它的文件讀寫任務正常。

2. 原因:

spark任務會頻繁的創建、刪除application目錄。在decomissing時,部分磁盤性能低的節點,磁盤更加繁忙,導致出現 last contact 心跳時間長。

3. 解決:

經過驗證,發現直接 kill datanode進程的方式,不影響spark任務。但必須保證一個一個的kill,否則會出現 missing block. (這不一定是解決問題最好的辦法,但的確有效)。

十、namenode editlog 長時間未做checkpoint

1. 現象:

standby namenode 的一個作用是,定期合併從journalnode上獲取的editlog,生成新的元數據fsimage,然後推送到active namenode。

當standby namenode出現異常,如進程退出、軟件bug(比如我們遇到過 IOException: No image directories available!),導致長時間未合併editlog。一旦需要發生切換或者重啓namenode,有可能導致啓動時間過長,嚴重的editlog合併需要的內存不足,無法啓動namenode.

2.解決:

如果內存不足,一種解決辦法是借一臺高內存臨時機器合併editlog:

① 把standby停下來,將hdfs的軟件介質和配置文件,拷貝到高內存機器;

② 同時拷貝dfs.namenode.name.dir 目錄中最新能用的 fsimage_xxx 和它之後的所有 edits_xxx-xxx;

③ 在臨時機器上啓動 namenode 進程,會自動從對應目錄加載 fsiamge 、合併editlog;

預防比補救要重要,一定要監控namenode上 TransactionsSinceLastCheckpoint 這個指標,我們的閾值是達到 5000000 就告警。

十一、HDFS 3.x datanode 出現大量 CLOSE-WAIT

1. 現象:

這個問題 HDFS-15402 是在定期對 datanode http://127.0.0.1:50075/jmx jmx 進行探測的時候產生的,我們有 5 個 hadoop 3.1.3 的集羣都存在該問題。在 hadoop 2.x 中正常。

50075 端口上產生過多 close-wait 的影響是,正常的 webhdfs 會出現 504 Gateway-timeout

[root@dn-9-4-xxx-yy /tmp]# ss -ant|grep :50075 |grep CLOSE-WAIT|wc -l
16464
[root@dn-9-4-xxx-yy /tmp]# ss -ant|grep :50075 |grep CLOSE-WAIT|head -3
CLOSE-WAIT 123    0                9.4.xxx.yy:50075           9.4.xxx.yy:39706
CLOSE-WAIT 123    0                9.4.xxx.yy:50075           9.4.xxx.yy:51710
CLOSE-WAIT 123    0                9.4.xxx.yy:50075           9.4.xxx.yy:47475
 
lsof -i:39706
COMMAND    PID USER   FD   TYPE    DEVICE SIZE/OFF NODE NAME
java    134304 hdfs *307u  IPv4 429yy7315      0t0  TCP dn-9-4-xxx-yy:50075->dn-9-4-xxx-yy:39706 (CLOSE_WAIT)
  
Proto Recv-Q Send-Q Local Address               Foreign Address             State       PID/Program name
tcp      123      0 9.4.xxx.yy:50075            9.4.xxx.yy:39706            CLOSE_WAIT  134304/java

CLOSE-WAIT 狀態是客戶端(curl)發起關閉tcp連接時,服務端(datanode)收到了FIN-ACK,但在關閉socket時一直沒有完成。正常流程是關閉socket完成,然後向客戶端發送FIN:

2. 原因:

所以問題出在datanode server上,與knox還是haproxy客戶端沒有關係。並且這個問題調整os內核參數是沒有用的,除非kill datanode,否則close-wait狀態會永久存在。使用網上的kill_close_wait_connections.pl 能夠清理這些 close-wait,之後 webhdfs 請求變得好轉。

3.解決:

目前避開的方法就是,不再請求 datanode jmx 做監控,只獲取 namenode 上的指標。datanode 上採集 os 級別的指標。

十二、knox 無法上傳 8G 文件

1. 現象:

在官方 jira 裏我們提了這個問題 KNOX-2139 Can not handle 8GB file when using webhdfs ,當我們使用 webhdfs with knox 上傳 8589934592 bytes 大小的文件,會出現 (55) Send failure: Broken pipe,在 hdfs 只能看到一個空文件。而且在版本 knox 1.1、1.2 中是必現,在 0.8 版本正常。

簡單 debug 了一下代碼,knox 拿到的請求 contentLength 爲 0,8G 以外的情況 contentLength 爲 -1。

2. 解決:

我們後來使用 haproxy 代替 knox 解決 knox 自身上傳速度慢和這個 8G 文件的問題。

不過在最新的 1.4 版本,8G問題又消失了。根據官方的恢復,可能跟 jetty 的升級有關。

十三、Unable to load native-hadoop library for your platform

1.現象

Unable to load native-hadoop library for your platform... using builtin-java classes

經常在執行 hdfs 客戶端命令時會有這樣的提示,其實是個老生常談的問題。

簡單說就是系統裏沒有找到原生的 hadoop 庫 libhdfs.so,這個庫是 C 寫的,性能比較好。缺少但不影響使用,因爲 hadoop 裏有 java 實現的客戶端庫。

出現這個總結原因有 3 個:

① hadoop 安裝包裏沒有自帶 libhdfs.so。

這個情況佔很大一部分。 去到目錄${HADOOP_HOME}/lib/native/,看下是否有libhdfs.so,libhdfs.a,libhadoop.so,libhadoop.a。 如果沒有的話,可以重新下一個完整的二進制包,把lib/native拷出來用。

這種看到纔是正常的。

./bin/hadoop checknative
20/05/14 20:13:39 INFO bzip2.Bzip2Factory: Successfully loaded & initialized native-bzip2 library system-native
20/05/14 20:13:39 INFO zlib.ZlibFactory: Successfully loaded & initialized native-zlib library
Native library checking:
hadoop:  true /data1/hadoop-hdfs/hadoop-dist/target/hadoop-2.6.0-cdh5.4.11-tendata/lib/native/libhadoop.so.1.0.0
zlib:    true /lib64/libz.so.1
snappy:  true /data1/hadoop-hdfs/hadoop-dist/target/hadoop-2.6.0-cdh5.4.11-tendata/lib/native/libsnappy.so.1
lz4:     true revision:10301
bzip2:   true /lib64/libbz2.so.1
openssl: true /usr/lib64/libcrypto.so

實在不行就在自己的 os 上編譯一個。

mvn clean package -Pdist,native -DskipTests -Dtar -Dbundle.snappy -Dsnappy.lib=/usr/local/lib

② so 文件存在,但路徑不對。


現在的版本,默認路徑都能找得到 so 庫。這個  https://stackoverflow.com/questions/19943766/hadoop-unable-to-load-native-hadoop-library-for-your-platform-warning 裏面介紹的大部分方法,都是在教怎麼設置路徑。真實原因很少會因爲路徑不對,不過這個答案靠譜 https://stackoverflow.com/a/30927689 ,也就是我們的情況 3。

③ 編譯的版本,在我們的 os 上依賴庫不全。


遇到過這種,glibc 庫版本不夠:

$ ldd lib/native/libhadoop.so
lib/native/libhadoop.so: /lib64/libc.so.6: version `GLIBC_2.14' not found (required by lib/native/libhadoop.so)
linux-vdso.so.1 =>  (0x00007ffd1db6d000)
/$LIB/libonion.so => /lib64/libonion.so (0x00007f5bfd37d000)
libdl.so.2 => /lib64/libdl.so.2 (0x00007f5bfce40000)
libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f5bfcc23000)
libc.so.6 => /lib64/libc.so.6 (0x00007f5bfc88f000)
/lib64/ld-linux-x86-64.so.2 (0x00007f5bfd266000)

$ strings /lib64/libc.so.6 |grep GLIBC_
可以看當前系統支持哪些版本的 glibc

但是 glibc 安裝升級有風險,如果要安裝 2.14 版本務必先做好測試。

十四、處理 missing blocks

1. 現象:

hdfs 集羣出現 missing block,無非就是 namenode 裏還記錄的 block 元數據信息,但是所有副本都丟失了。如果是同時掛了多個機器,或者損壞了多個機器上的磁盤,是有可能會出現。

遇到過 2 次人爲產生 missing blocks:

① kill 一個 datanode 進程,就出現 missing block

② 先設置所有文件的 replication 爲 1,一小段時間後,再設置爲 2

這兩種情況都算是 bug,對應的文件確實無法 get 下來了。但第 1 中情況還好,經過排除日誌,發現實際這些丟失的 blocks 本就接收到了刪除命令,過一段時間後,missing block 一般會自動消失。第 2 種情況,是真的意外丟 block 了,比較嚴重。不要輕易把 replication 設置爲 1,再改回去可能丟 block。

如果確認這些 missing block 可以消除,可以通過 fsck 命令手動處理:

// 如果missing blocks數不是很多,可以直接逐個delete
hdfs fsck file_name -delete

// 如果missing blocks較多,可以從namenode上拿到corrupt塊
hdfs fsck / -list-corruptfileblocks -openforwrite | egrep -v '^\.+$' | egrep "MISSING|OPENFORWRITE" | grep -o "/[^ ]*" | sed -e "s/:$//" > missing_blocks.txt

1

總結  應該關注的告警

實際還有些許多問題,比如用戶supergroup 權限問題、rack-aware.sh文件缺失的問題,限於篇幅就不列舉了。

問題是不斷會出現的,但及時對大部分場景做到監控工具,能夠提前發現問題。下面是整理並上線的關鍵告警指標:

1. datanode lastcontact

datanode 與 namenode 心跳監控。 心跳時間長意味着這個 dn 沒響應了,默認超過10m30s 沒響應,dn會脫離集羣。

2. namenode and datanode web probe

namenode 50070 與 datanode 50075 從外部探測,並且 datanode 會根據 include裏面的地址自動增減。 我們使用修改過了 telegraf http_response 插件,支持動態讀取url,比如 exec bash get_datanode_urls.sh

3. d ir ctory max files

單目錄下的文件數告警。 hdfs默認限制單目錄下最大的文件數100萬,由配置項dfs.namenode.fs-limits.max-directory-items決定, 這個指標數據來源於 fsimage 目錄畫像分析。

4. transactions not merged

standby 未滾動的editlog數。 長期未checkpoint會導致下次namenode啓動消耗過多內存,甚至啓動失敗。

5 . missing blocks

異常blocks數。

6. t est write file

在2個namenode節點上,定期使用 hdfs put/get 寫入文件。 如果失敗會告警。

7 . non-active namenode

hdfs集羣namenode有且只有一個active,一個standby。 其它情況告警。

8. cluster capacity

集羣總體容量監控。

9. node usage, ioutil

單個 datanode 磁盤空間使用率預警,ioutil持續5分鐘大於95%預警。

10. failover occurs

hdfs namenode發生failover

11. namenode heap size

namenode heap size使用比率。 blocks數量多,內存使用越多。

特惠體驗雲數據庫 

↓↓更多驚喜優惠請點這兒~  

相關文章