背景
過年前,寂寞哥給我三台機器,說搞個新的openTSDB集群。機器硬件是8核16G內存、3個146G磁盤做數據盤。
我說這太摳了,寂寞哥說之前的TSDB集群運行了兩年,4台同樣配置的機器,目前hdfs才用了40%,所以前期先用着這三台機器,不夠再加。
於是我只好默默地搭好了CDH5、openTSDB(2.1版本,請注意此版本號)、bosun,並在20台左右的機器上部署了scollector用來測試,然后將dfs.replication
改為了2,一切正常。
過完年回來后,開始批量在主要業務機器上部署scollector,大概增加到了160台左右。運行了一個星期后,正在添加各種grafana dashboard的時候,就開始發現各種異常了。
這篇文章主要是總結了處理openTSDB各種狀況的過程。需要注意的是,這是個持續的過程,中間修改了非常多的參數,有些問題是做了某項修改后,有很明顯的改善,而有些問題的解決其實回頭看的時候並不能知道究竟是哪些修改解決了問題,也還沒有時間重新去修改驗證。因此,本文並不能作為一份解決問題的FAQ文檔,純當本人處理問題的記錄罷了。
打開文件數
首先是發現了無法打開master:4242
頁面了,查看了openTSDB的日志,很顯眼的too many open file
,於是檢查了ulimit
,還是默認的1024,這肯定不夠用的。需要修改系統的資源限制:
首先是/etc/security/limits.conf
# 添加 * soft nofile 102400 * hard nofile 102400
然后是/etc/profile
# 文件末尾添加 ulimit -HSn 102400
重新登錄,重啟openTSDB,然后檢查進程的打開文件數:
# cat /proc/$(ps -ef | /bin/grep 'opentsd[b]' | awk '{print $2}')/limits | /bin/grep 'open files' Max open files 102400 102400 files
修改已生效,打開文件數的問題解決了。
內核參數
接着沒過1小時,就出現了查詢非常久都出不來的情況,於是就注意觀察openTSDB的打開文件數,然而只有1500左右,懷疑可能是並發數和backlog的問題,檢查了連接數以及當前的內核參數,發現此時連接數並沒有超過內核設置。不過本着盡量排除影響的原則,還是改大了這兩個內核參數:
net.ipv4.tcp_max_syn_backlog = 16384 net.core.somaxconn = 65535
沒有什么新的線索的情況下,只好又重啟了TSDB。重啟后又恢復正常了。於是用watch命令持續盯着openTSDB的連接數,一邊用grafana每30秒刷新頁面。發現正常情況時,連接數大概是900以下,而出現問題時(grafana刷不出來圖時),連接數突然就上升到1200以上,然后1分鍾內蹦到了2900。趕緊使用netstat看下是什么狀態的連接數上漲了,發現是TIME-WAIT。檢查net.ipv4.tcp_tw_reuse
已經是1了,然后這天由於有別的事情要忙,就暫時沒再看了。
regionserver java堆棧大小
第二天早上的時候發現又是grafana刷新不出圖,后來上CDH管理頁面才發現是其中有一個regionserver(tsdb3)掛了:經大數據組大神提醒,多半是GC導致regionserver掛了。查看果然有個小時級別的GC:
查看了HBase Regionserver java堆棧大小,原來才設置為2.44G。請教了大神,說是因為我部署的這個CDH集群,除了HBase之外還有其他的其實對於openTSDB沒有用的Hive、Hue、Oozie、Sqoop2等,所以CDH會根據角色的情況分配java堆棧大小,但是我們完全可以將這些沒有用到的服務關掉,手動將HBase Regionserver java堆棧大小設置為物理內存的一半,也就是8G。
改完配置重啟了HBase,貌似一切又正常了。而且,由於有更重要的問題要處理,因此就先暫時放一放了。
HBase表壓縮
那件更重要的事情,就是磁盤空間用得太快了。才160台機器一星期的數據,磁盤空間就才90%下降到80%,我一開始覺得不可能,之前的舊集群,用了兩年才用了40%,新的怎么一星期就用了10%呢,這么下去難道只能用10個星期?后來看了下CDH的圖,每秒datanode寫入都有1M左右,146G的硬盤前途堪憂啊!原來是舊的tcollector是自己寫的收集腳本,一台機器收集的metric平均才30來個,而scollector不算自己寫的外部腳本,本身就提供了上百個metric,再加上自己寫的外部收集腳本,這么下來兩者的數據根本就不是一個數量級了。
想起之前在初始化TSDB的HBase的時候,沒有采用壓縮:
env COMPRESSION=NONE HBASE_HOME=/usr/lib/hbase /usr/share/opentsdb/tools/create_table.sh
不知道現在還能不能開啟表壓縮,趕緊去請教大神。大神甩給我一個文檔:
# hbase shell # 查看表是否啟用壓縮 # describe "tsdb" # 暫停對外服務,將指定的HBase表disable # disable "tsdb" # 更改壓縮類型 # alter 'tsdb', NAME => 't', COMPRESSION => 'gz' # 重新啟用表 # enable "tsdb" # 確認是否開啟了壓縮 # describe "tsdb" # 使壓縮在全站生效 # major_compact "tsdb" # disable "tsdb" # 更改壓縮類型 # alter 'tsdb', NAME => 't', COMPRESSION => 'NONE' # 重新啟用表 # enable "tsdb" # 確認是否開啟了壓縮 # describe "tsdb" # 使壓縮在全站生效 # major_compact "tsdb"
需要注意的是,千萬不要在表繁忙期間執行大合並操作
我說,用gz
壓縮是不是對性能影響大啊,不是很多地方都在用snappy
嗎?大神解釋說,gz
是壓縮比最高的,只對CPU資源有所損耗;按你這個集群的情況,CPU還有負載,都還是比較閑的,加上磁盤資源又這么緊張,最好還是用gz
吧,如果有影響,再改為snappy
唄。
我想也是,於是就開啟了表壓縮,磁盤空間問題解決了:從上圖可以很明顯地看到開啟壓縮前后的對比。
HBase memstore flush and store files compaction
解決完壓縮問題后,回頭來再看openTSDB查詢偶爾沒有及時響應的問題。通過查看bosun的Errors頁面,經常會出現net/http: request canceled
的報錯,這是因為我設置了bosun每分鍾檢查一次某些metric作為報警的來源,這些報錯是因為讀取數據時候超時了,那么應該重點看下openTSDB為何反應慢。看了下TSDB的日志,並沒有發現什么異常,於是把目光集中在了HBase上。
Google了一下tsdb hbase performance
,發現了這篇文章,里面的這張圖(以下簡稱為cycle圖)總結得很好: 那么HBase反應慢,應該是這兩種情況:
- 1->8->9
- 1->2->3->4->5
可以看到memstore flush
很明顯在這張圖中處於一個核心地位,那么減少memstore flush
是否可以改善呢?於是將hbase.hregion.memstore.flush.size從默認的128M改大為1G。
CDH中的hbase.hregion.memstore.flush.size作用解釋如下:如memstore大小超過此值(字節數),Memstore將刷新到磁盤。通過運行由hbase.server.thread.wakefrequency指定的頻率的線程檢查此值。
但是這樣修改后有個很嚴重的副作用:GC時間更長了(箭頭指向為修改前后的比較):后來就把這個參數恢復為128M這個默認值了。
至於compaction方面的參數,看着解釋貌似不好修改,於是就沒有改了:
HBase GC 調優
再來看下GC的時間規律,發現都是集中在一個小時的0分左右。查看了scollector自帶的hbase.region.gc.CollectionTime
這個指標值,確實在一個小時的0分左右就有一個GC的高峰:(紅線為ParNew,而藍線為ConcurrentMarkSweep)大神一看,說每小時開始的時候集群網絡IO會飆高,問我TSDB這時候在干什么。
於是我在發生問題的時候用
iftop -NP
檢查網絡IO,發現基本上都是60020和50010端口之間的流量:看不出來openTSDB在做什么,之前用的TSDB都不用怎么改配置直接用默認值的。
大神沉思下,說我還有辦法可以優化下GC時間,優化調整的目的應該是削平GC的波峰,讓整個系統的正常服務時間最大化。 大神在HBase RegionServer 的 Java 配置選項
加上以下參數:
-XX:+PrintGCDateStamps -XX:+PrintGCDetails -Xloggc:/data/logs/gc.log
接着修改了以下HBase參數:
- HBase Server 線程喚醒步驟 (hbase.server.thread.wakefrequency):該值決定了Hbase Memstore刷新的檢測頻率,該值默認值為10s,在數據高峰時,每秒寫入的數據達到20M左右,調整該值到5s,來幫助盡量使得MemStore的值不超過128M。
- RegionServer 中所有 Memstore 的最大大小 (hbase.regionserver.global.memstore.upperLimit):該值是小數形式的百分比,默認為0.4,該值與Hfile緩存塊大小的總和不能超過0.8,不然會造成HBase啟動失敗,其目的是為了在Memstore占用的內存達到Java堆棧的該百分比時強制執行刷新數據到磁盤的操作,這里我們將Memstore的百分比設置為0.5,目的是為了盡量避免強制刷新。還有一個最小大小的值(hbase.regionserver.global.memstore.lowerLimit)為0.38,表示要刷新到0.38才結束刷新,未做修改,后續可以調整。
- HFile 塊緩存大小 (hfile.block.cache.size): 該值默認值為0.4,調整為0表示Hbase的磁盤寫入不使用內存緩存,測試發現調整為0后性能有一定的退化,尤其是在數據刷新操作的過程中消耗的時間有所上升,這里我們把該值調整為0.3(因為hbase.regionserver.global.memstore.upperLimit已改為了0.5)。
- HStore 阻塞存儲文件 (hbase.hstore.blockingStoreFiles):該值默認為10,如在任意 HStore 中有超過此數量的 HStoreFiles,則會阻止對此 HRegion 的更新,直到完成壓縮或直到超過為 'hbase.hstore.blockingWaitTime' 指定的值。將該值改大到100,就是為了減少cycle圖中的第9步。
然后根據GC打印日志的分析,還修改了HBase RegionServer的Java配置選項:
默認情況下Hbase在新代中采用的GC方式是UseParNewGC,在老代中采用的GC方式為UseConcMarkSweepGC,這兩種GC方法都支持多線程的方法,CMS的GC耗費的時間比新代中的GC長,同時如果內存占滿還會觸發Full GC,我們的優化方向是讓GC盡量在新代中進行,通過GC日志發現新代的內存大小只有600M,而總的Java堆棧大小為8G,官方的推薦是新代內存占用為總堆棧的3/8,於是在這里增加參數-Xmn3000m,來擴大新代的大小。
經過大神的一番調優后,可以明顯看到GC時間明顯下降了:
文件系統讀寫優化
考慮到是寫入數據太多,查看系統的磁盤所有寫所消耗的時間確實都比較高,嘗試了以下優化方案:(以sdb為例)
- ext4掛載參數:
mount -o remount,noatime,nodelalloc,barrier=0 /dev/sdb1
- 調度電梯算法改為deadline
echo "deadline" > /sys/block/sdb/queue/scheduler
- 開啟文件系統的預讀緩存
blockdev --setra 32768 /dev/sdb
經過一番修改后,linux.disk.msec_write
(Total number of ms spent by all writes) 這個指標值大幅下降,但不清楚是GC調優還是文件系統讀寫優化的效果,但根據經驗,ext4性能相比ext3是有所回退的,這么改動應該是有效果的,但barrier=0
這個掛載參數需謹慎使用。
增加機器/hotspotting
雖然GC時間下降了,但是還是在挺高的數值上,而且bosun的Errors還是偶爾出現,為此我增加了bosun的Errors監控指標,通過bosun的api,專門監控bosun出現request canceled
的情況:可以看到出現此類錯誤的時候,基本都是在每個小時的0分附近。
反反復復試過了很多HBase調優之后,依然有偶爾出現request canceled
的問題,且每到整點左右時,用grafana查詢也是要1到2分鍾才將一個dashboard刷新完,就是說1/30的時間是不可服務的。
既然之前搜到的那篇文章中有提到最好的方式是增加機器了,剛好舊的TSDB集群剛好也壓縮過了,用兩個regionserver就可以了,於是將剩下的第三台機器加到了新的TSDB集群中來。
新的regioserver加進來后,發現情況還是不是太好,依舊是每到一小時的0分附近就是各種slow response。查看了master:60010
頁面,各regionserver之間的request分配並不平均,通常是新加入的regionserver只是舊的兩台的30%不到,這可能會導致大量的寫入集中在兩台舊的regionserver上,因而造成slow response。所以先嘗試手動平衡下region。
比如從master:60010
上看數據,發現這個region的request比較多,因此決定將它手動遷移到新的tsdb4(tsdb4.domain.com,60020,1457011734982)
tsdb,\x00\x00\xF5V\xA6\x9A\xE0\x00\x00\x01\x00\x00\xDA\x00\x00\x13\x00\x00c\x00\x00\x15\x00\x00\xDE,1454242184814.29987604fab49d4fd4a0313c6cf3b1b6.
操作如下:
# hbase shell move "29987604fab49d4fd4a0313c6cf3b1b6" "tsdb4.domain.com,60020,1457011734982" balance_switch false
記得關閉balance,否則過5分鍾,被移動的region又自動回來了
可以看到修改完后,3個regionserver的writeRequestCount比之前平均多了:
增加內存
每到整點就是各種slow response的問題依然存在。沒辦法之下只好給regionserver增加內存進行測試了。將3台regionserver的內存都增加到了32G,然后將java堆棧大小改為16G。
其他優化
另外看到bosun的Errors里還出現了10000 RPCs waiting on ... to come back online
,懷疑可能是處理程序計數不足,於是調大了這兩個參數:
最終解?TSDB compaction
各種方法都試過的情況下,還是出現這個“每到整點左右就是各種slow response的問題”。
既然系統、hadoop、HBase方面都調整過了,最后只能找openTSDB和bosun下手了。看到scollector已經提供了相當多的tsd指標值,於是增加了一個TSDB的grafana dashboard把所有的相關指標值都顯示出來,看看每個小時0分的時候是否有些蛛絲馬跡可循。
最明顯的一個指標值就是tsd.hbase.rpcs
,每到整點的時候才出現這些delete
、get
操作,平時都是0,那么100%可以確定這些操作是有關聯的:
直接上Google搜tsdb delete
,結果都是問怎么從TSDB中刪除數據的。
回過頭來再看其他的指標值,發現tsd.compaction.count
也是在整點的時候特別高:需要注意的是TSDB的compaction和HBase的compaction其實不是同一個概念。
然后用tsdb compaction
作為關鍵詞一搜,出來了官網的文檔,仔細看下這一段:
是不是很符合我們的情況,確實是compaction在前而delete在后。原來是TSDB在每小時整點的時候將上個小時的數據讀出來(get
),然后compact成一個row,寫入(put
)到HBase,然后刪除(delete
)原始數據,所以我們看整點時的RPC請求,這些操作都會出現個凸起。
接着在這個鏈接(貌似都是小集群容易出現這個問題)里看到開發者的討論:
於是看下openTSDB的配置文檔,果然發現了相關配置:都是在openTSDB-2.2才新增的配置,趕緊升級了openTSDB。
那么將tsdb的compaction給關閉了有什么副作用呢?這個鏈接中開發者解釋了有兩點壞處:
- 磁盤使用率會增加5到10倍;
- 讀操作會變慢。
這兩點顯然都是不可取的,而采用修改compaction間隔等效果並不明顯,最后采用了tsd.storage.enable_appends = true
的方式,終於將此問題解決了:
看下修改前后的性能比較:GC時間下降明顯。
memstore變化平緩多了。
slow put 也不再出現了。
在整點的時候經常出現的request cancle
不再出現了,整點時使用grafana也不會出現轉圈圈到timeout的情況了。
不過值得注意的是,磁盤容量也有所下降(bosun圖的時間是UTC時區):
經驗總結
整理信息的時候偶然Google發現了這篇2014年的文章,其實是一樣的問題,這次雖然經歷了很多波折,但是也是一個難得的學習機會。雖然作為運維,我們能接觸到大量優秀的開源產品,但有多少是能仔細看完一個開源產品的文檔的?一個軟件,不同的情景下面就有不同的表現,不能一概而論。直到遇到坑才去填,明顯是本末倒置的行為,以后要戒掉這種浮躁的心態,同一份配置不要以為之前沒有問題就能一直用。