您好,登錄后才能下訂單哦!
這篇文章給大家分享的是有關(guān)怎么用Arthas來診斷 HBase異常進(jìn)程的內(nèi)容。小編覺得挺實(shí)用的,因此分享給大家做個(gè)參考,一起跟隨小編過來看看吧。
HBase 集群的某一個(gè) RegionServer 的 CPU 使用率突然飆升到百分之百,單獨(dú)重啟該 RegionServer 之后,CPU 的負(fù)載依舊會(huì)逐漸攀上頂峰。多次重啟集群之后,CPU 滿載的現(xiàn)象依然會(huì)復(fù)現(xiàn),且會(huì)持續(xù)居高不下,慢慢地該 RegionServer 就會(huì)宕掉,慢慢地 HBase 集群就完?duì)僮恿恕?/p>
CDH 監(jiān)控頁(yè)面來看,除 CPU 之外的幾乎所有核心指標(biāo)都是正常的,磁盤和網(wǎng)絡(luò) IO 都很低,內(nèi)存更是充足,壓縮隊(duì)列,刷新隊(duì)列也是正常的。
普羅米修斯的監(jiān)控也是類似這樣的,就不貼圖了。
監(jiān)控指標(biāo)里的數(shù)字,只能直觀地告訴我們現(xiàn)象,不能告訴我們異常的起因。因此我們的第二反應(yīng)是看日志。
與此同時(shí),日志中還有很多類似這樣的干擾輸出。
后來發(fā)現(xiàn)這樣的輸出只是一些無關(guān)緊要的信息,對(duì)分析問題沒有任何幫助,甚至?xí)蓴_我們對(duì)問題的定位。
但是,日志中大量 scan responseTooSlow 的警告信息,似乎在告訴我們,HBase 的 Server 內(nèi)部正在發(fā)生著大量耗時(shí)的 scan 操作,這也許就是 CPU 負(fù)載高的元兇。可是,由于各種因素的作用,我們當(dāng)時(shí)的關(guān)注點(diǎn)并沒有在這個(gè)上面,因?yàn)檫@樣的信息,我們?cè)跉v史的時(shí)間段里也頻繁撞見。
監(jiān)控和日志都不能讓我們百分百確定 CPU 負(fù)載高是由哪些操作引起的,我們用 top 命令也只能看到 HBase 這個(gè)進(jìn)程消耗了很多 CPU,就像下圖看到的這樣。
命令 top 定位到的異常的 HBase 進(jìn)程 ID 是 1214,該進(jìn)程就是 HRegionServer 的進(jìn)程。輸入序號(hào) 1,回車,就進(jìn)入了監(jiān)聽該進(jìn)程的命令行界面。
輸入 thread 命令回車,查看該進(jìn)程下所有線程的執(zhí)行情況。
單位時(shí)間為 5 秒內(nèi),資源占用前三名的線程。
生成火焰圖的最簡(jiǎn)單命令。
profiler start
隔一段時(shí)間,大概三十秒。
profiler stop
關(guān)于火焰圖的入門級(jí)知識(shí):
查看 jvm 進(jìn)程 cpu 火焰圖工具。
火焰圖里很清楚地定位到 CPU 時(shí)間占用最高的線程是綠框最長(zhǎng)的那些線程,也就是 scan 操作。
通過以上的進(jìn)程分析,我們最終可以確定,scan 操作的發(fā)生,導(dǎo)致 CPU 負(fù)載很高。我們查詢 HBase 的 API 基于 happybase 封裝而成。
其實(shí)常規(guī)的 scan 操作是能正常返回結(jié)果的,發(fā)生異常查詢的表也不是很大,所以我們排除了熱點(diǎn)的可能。抽象出來業(yè)務(wù)方的查詢邏輯是:
from happybase.connection import Connectionimport time start = time.time() con = Connection(host='ip', port=9090, timeout=3000) table = con.table("table_name") try: res = list(table.scan(filter="PrefixFilter('273810955|')", row_start='\x0f\x10&R\xca\xdf\x96\xcb\xe2\xad7$\xad9khE\x19\xfd\xaa\x87\xa5\xdd\xf7\x85\x1c\x81ku ^\x92k', limit=3)) except Exception as e: pass end = time.time()print 'timeout: %d' % (end - start)
PrefixFilter 和 row_start 的組合是為了實(shí)現(xiàn)分頁(yè)查詢的需求,row_start 的一堆亂碼字符,是加密的一個(gè) user_id,里面有特殊字符。日志中看到,所有的耗時(shí)查詢,都有此類亂碼字符的傳參。于是,我們猜想,查詢出現(xiàn)的異常與這些亂碼字符有關(guān)。
但是,后續(xù)測(cè)試復(fù)現(xiàn)的時(shí)候又發(fā)現(xiàn)。
# 會(huì)超時(shí) res = list(table.scan(filter="PrefixFilter('273810955|')", row_start='27', limit=3)) # 不會(huì)超時(shí) res = list(table.scan(filter="PrefixFilter('273810955|')", row_start='27381095', limit=3))
也就是,即使不是亂碼字符傳參,filter 和 row_start 組合異常,也會(huì)導(dǎo)致 CPU 異常的高,row_start 指定的過小,小于前綴,數(shù)據(jù)掃描的范圍估計(jì)就會(huì)變大,類似觸發(fā)了全表掃描,CPUload 勢(shì)必會(huì)變大。
我們操作 HBase 的公共代碼是由 happybase 封裝而成,其中還用到了 happybase 的線程池,在我們更深入的測(cè)試中又發(fā)現(xiàn)了一個(gè)現(xiàn)象,當(dāng)我們使用連接池或在循環(huán)中重復(fù)創(chuàng)建連接時(shí),然后用 arthas 監(jiān)控線程情況,發(fā)現(xiàn) scan 的線程會(huì)很嚴(yán)重,測(cè)試代碼如下:
from happybase.connection import Connectionimport time con = Connection(host='ip', port=9090, timeout=2000) table = con.table("table")for i in range(100): try: start = time.time() res = list(table.scan(filter="PrefixFilter('273810955|')", row_start='\x0f\x10&R\xca\xdf\x96\xcb\xe2\xad7$\xad9khE\x19\xfd\xaa\x87\xa5\xdd\xf7\x85\x1c\x81ku ^\x92k', limit=3)) except Exception as e: pass end = time.time()print 'timeout: %d' % (end - start)
程序開始運(yùn)行時(shí),可以打開 arthas 進(jìn)入到 HRegionServer 進(jìn)程的監(jiān)控,運(yùn)行 thread 命令,查看此時(shí)的線程使用情況:
小部分在運(yùn)行,大部分在等待。此時(shí),CPU 的負(fù)載情況:
代碼如下:
from happybase.connection import Connectionimport timefor i in range(100): try: start = time.time() con = Connection(host='ip', port=9090, timeout=2000) table = con.table("table") res = list(table.scan(filter="PrefixFilter('273810955|')", row_start='\x0f\x10&R\xca\xdf\x96\xcb\xe2\xad7$\xad9khE\x19\xfd\xaa\x87\xa5\xdd\xf7\x85\x1c\x81ku ^\x92k', limit=3)) except Exception as e: pass end = time.time()print 'timeout: %d' % (end - start)
下圖中可以看到開始 RUNNING 的線程越來越多,CPU 的消耗也越來越大。
CPU 被之前的實(shí)驗(yàn)拉高,重啟下集群使 CPU 的狀態(tài)恢復(fù)到之前平穩(wěn)的狀態(tài)。然后繼續(xù)我們的測(cè)試,測(cè)試代碼:
沒有超時(shí)時(shí)間
from happybase import ConnectionPool import timepool = ConnectionPool(size=1, host='ip', port=9090)for i in range(100): start = time.time() try: with pool.connection(2000) as con: table = con.table("table") res = list(table.scan(filter="PrefixFilter('273810955|')", row_start='\x0f\x10&R\xca\xdf\x96\xcb\xe2\xad7$\xad9khE\x19\xfd\xaa\x87\xa5\xdd\xf7\x85\x1c\x81ku ^\x92k', limit=3)) except Exception as e: pass end = time.time()print 'timeout: %d' % (end - start)
如果不指定超時(shí)時(shí)間,會(huì)只有一個(gè)線程持續(xù)運(yùn)行,因?yàn)槲业倪B接池設(shè)置為 1。
指定超時(shí)時(shí)間
from happybase import ConnectionPool import timepool = ConnectionPool(size=1, host='ip', port=9090, timeout=2000)for i in range(100): start = time.time() try: with pool.connection(2000) as con: table = con.table("table") res = list(table.scan(filter="PrefixFilter('273810955|')", row_start='\x0f\x10&R\xca\xdf\x96\xcb\xe2\xad7$\xad9khE\x19\xfd\xaa\x87\xa5\xdd\xf7\x85\x1c\x81ku ^\x92k', limit=3)) except Exception as e: pass end = time.time()print 'timeout: %d' % (end - start)
此次測(cè)試中,我指定了連接池中的超時(shí)時(shí)間,期望的是,連接超時(shí),及時(shí)斷開,繼續(xù)下一次耗時(shí)查詢。此時(shí),服務(wù)端處理 scan 請(qǐng)求的線程情況:
參考大神的博客,以及自己對(duì)這個(gè)參數(shù)的理解,每一個(gè)客戶端發(fā)起的 RPC 請(qǐng)求(讀或?qū)懀l(fā)送給服務(wù)端的時(shí)候,服務(wù)端就會(huì)有一個(gè)線程池,專門負(fù)責(zé)處理這些客戶端的請(qǐng)求,這個(gè)線程池可以保證同一時(shí)間點(diǎn)有 30 個(gè)線程可運(yùn)行,剩余請(qǐng)求要么阻塞,要么被塞進(jìn)隊(duì)列中等待被處理,scan 請(qǐng)求撐滿了服務(wù)端的線程池,大量的耗時(shí)操作,把 CPU 資源消耗殆盡,其余常規(guī)的讀寫請(qǐng)求也勢(shì)必大受影響,慢慢集群就完?duì)僮恿恕?/p>
首先,這個(gè) hbase.regionserver.handler.count
的參數(shù)不能被調(diào)小,如果太小,集群并發(fā)高時(shí),讀寫延時(shí)必高,因?yàn)榇蟛糠终?qǐng)求都在排隊(duì)。理想情況是,讀和寫占用不同的線程池,在處理讀請(qǐng)求時(shí),scan 和 get 分別占用不同的線程池,實(shí)現(xiàn)線程池資源隔離。如果是我的話,第一反應(yīng)可能也會(huì)簡(jiǎn)單、粗略地搞仨線程池,寫線程池,get 線程池、scan 線程池。scan 線程池分配很小的核心線程,讓其占用很小的資源,限制其無限擴(kuò)張。但是真實(shí)的情況是這樣嗎?暫時(shí),我還沒仔細(xì)研究源碼,HBase 提供了如下參數(shù),可以滿足讀寫資源分離的需求。
hbase.regionserver.handler.count
描述 在RegionServer上旋轉(zhuǎn)的RPC偵聽器實(shí)例數(shù)。主機(jī)將相同的屬性用于主機(jī)處理程序的計(jì)數(shù)。過多的處理程序可能適得其反。使它成為CPU計(jì)數(shù)的倍數(shù)。如果大多數(shù)情況下是只讀的,則處理程序計(jì)數(shù)接近c(diǎn)pu計(jì)數(shù)的效果很好。從兩倍的CPU計(jì)數(shù)開始,然后從那里進(jìn)行調(diào)整。 默認(rèn)30
hbase.ipc.server.callqueue.handler.factor
描述 確定呼叫隊(duì)列數(shù)量的因素。值為0表示在所有處理程序之間共享一個(gè)隊(duì)列。值為1表示每個(gè)處理程序都有自己的隊(duì)列。 默認(rèn)0.1
hbase.ipc.server.callqueue.read.ratio
描述 將呼叫隊(duì)列劃分為讀寫隊(duì)列。指定的間隔(應(yīng)在0.0到1.0之間)將乘以呼叫隊(duì)列的數(shù)量。值為0表示不拆分呼叫隊(duì)列,這意味著讀取和寫入請(qǐng)求都將被推送到同一組隊(duì)列中。小于0.5的值表示讀隊(duì)列少于寫隊(duì)列。值為0.5表示將有相同數(shù)量的讀取和寫入隊(duì)列。大于0.5的值表示將有比寫隊(duì)列更多的讀隊(duì)列。值1.0表示除一個(gè)隊(duì)列外的所有隊(duì)列均用于調(diào)度讀取請(qǐng)求。示例:給定呼叫隊(duì)列的總數(shù)為10,讀比率為0表示:10個(gè)隊(duì)列將包含兩個(gè)讀/寫請(qǐng)求。read.ratio為0.3表示:3個(gè)隊(duì)列將僅包含讀取請(qǐng)求,而7個(gè)隊(duì)列將僅包含寫入請(qǐng)求。read.ratio為0.5表示:5個(gè)隊(duì)列僅包含讀取請(qǐng)求,而5個(gè)隊(duì)列僅包含寫入請(qǐng)求。read.ratio為0.8表示:8個(gè)隊(duì)列將僅包含讀取請(qǐng)求,而2個(gè)隊(duì)列將僅包含寫入請(qǐng)求。read.ratio為1表示:9個(gè)隊(duì)列將僅包含讀取請(qǐng)求,而1個(gè)隊(duì)列將僅包含寫入請(qǐng)求。 默認(rèn)0
hbase.ipc.server.callqueue.scan.ratio
描述 給定讀取呼叫隊(duì)列的數(shù)量(根據(jù)呼叫隊(duì)列總數(shù)乘以callqueue.read.ratio計(jì)算得出),scan.ratio屬性會(huì)將讀取呼叫隊(duì)列分為小讀取隊(duì)列和長(zhǎng)讀取隊(duì)列。小于0.5的值表示長(zhǎng)讀隊(duì)列少于短讀隊(duì)列。值為0.5表示將有相同數(shù)量的短讀和長(zhǎng)讀隊(duì)列。大于0.5的值表示長(zhǎng)讀取隊(duì)列比短讀取隊(duì)列多。值為0或1表示使用相同的隊(duì)列進(jìn)行獲取和掃描。示例:假設(shè)讀取呼叫隊(duì)列的總數(shù)為8,則scan.ratio為0或1表示:8個(gè)隊(duì)列將同時(shí)包含長(zhǎng)讀取請(qǐng)求和短讀取請(qǐng)求。scan.ratio為0.3表示:2個(gè)隊(duì)列將僅包含長(zhǎng)讀請(qǐng)求,而6個(gè)隊(duì)列將僅包含短讀請(qǐng)求。scan.ratio為0.5表示:4個(gè)隊(duì)列將僅包含長(zhǎng)讀請(qǐng)求,而4個(gè)隊(duì)列將僅包含短讀請(qǐng)求。scan.ratio為0.8表示:6個(gè)隊(duì)列將僅包含長(zhǎng)讀請(qǐng)求,而2個(gè)隊(duì)列將僅包含短讀請(qǐng)求。 默認(rèn)0
這幾個(gè)參數(shù)的作用官網(wǎng)解釋的還挺詳細(xì),按照其中的意思,配置一定比例,就可以達(dá)到讀寫隊(duì)列,get 和 scan 隊(duì)列分離的目的,但是,調(diào)配參數(shù)后,繼續(xù)如上測(cè)試,發(fā)現(xiàn),并不難控制 RUNNING 的線程的數(shù)量,發(fā)現(xiàn)沒毛用。
這里有一個(gè)疑問,隊(duì)列和我所理解的線程池直接到底是什么關(guān)系?是否是一個(gè)東西?這個(gè)之后需要觀其源碼,窺其本質(zhì)。
感謝各位的閱讀!關(guān)于“怎么用Arthas來診斷 HBase異常進(jìn)程”這篇文章就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,讓大家可以學(xué)到更多知識(shí),如果覺得文章不錯(cuò),可以把它分享出去讓更多的人看到吧!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。