HBase實戰:記一次Safepoint導致長時間STW的踩坑之旅


本文記錄了HBase中Safepoint導致長時間STW此問題的解決思路及辦法。

上篇文章回顧:HBase Replication詳解

?過 程 記 錄

現象:小米有一個比較大的公共離線HBase集群,用戶很多,每天有大量的MapReduce或Spark離線分析任務在進行訪問,同時有很多其他在線集群Replication過來的數據寫入,集群因為讀寫壓力較大,且離線分析任務對延遲不敏感,所以其G1GC的MaxGCPauseMillis設置是500ms。

但是隨著時間的推移,我們發現了一個新的現象,線程的STW時間可以到3秒以上,但是實際GC的STW時間卻只有幾百毫秒!

打印GC日志

  • -XX:+PrintGC

  • -XX:+PrintGCDetails

  • -XX:+PrintGCApplicationStoppedTime

  • -XX:+PrintHeapAtGC

  • -XX:+PrintGCDateStamps

  • -XX:+PrintAdaptiveSizePolicy

  • -XX:+PrintTenuringDistribution

具體的日志log示例如下:

[Times: user=1.51 sys=0.67, real=0.14 secs]
2019-06-25T12:12:43.376+0800: 3448319.277: Total time for which application threads were stopped: 2.2645818 seconds, Stopping threads took: xxx seconds復制代碼

-XX:+PrintGCApplicationStoppedTime會打出類似上面的日志,這個stopped時間是JVM里所有STW的時間,不止GC。我們遇到的現象就是stopped時間遠大于上面的GC real耗時,比如GC只耗時0.14秒,但是線程卻stopped了2秒多。這個時候大概率就是GC時線程進入Safepoint耗時過長,所以需要2個新的GC參數來打印出Safepoint的情況。

打印Safepoint相關日志

  • -XX:+PrintSafepointStatistics

  • -XX:PrintSafepointStatisticsCount=1

需要注意的是Safepoint的情況是打印到標準輸出里的,具體的日志log示例如下:

vmop    [threads: total initially_running wait_to_block] 
65968.203: ForceAsyncSafepoint [931   1   2]復制代碼

這部分日志是時間戳,VM Operation的類型,以及線程概況,比如上面就顯示有2個線程在等待進入Safepoint:

[time: spin block sync cleanup vmop] page_trap_count
[2255  0  2255 11  0]  1復制代碼

接下來的這部分日志是到達Safepoint時的各個階段以及執行操作所花的時間:

spin:因為JVM在決定進入全局Safepoint的時候,有的線程在Safepoint上,而有的線程不在Safepoint上,這個階段是等待未在Safepoint上的用戶線程進入Safepoint。

block:即使進入Safepoint,用戶線程這時候仍然是running狀態,保證用戶不在繼續執行,需要將用戶線程阻塞

sync:spin+block的耗時

所以上面的日志就是說,有2個線程進入Safepoint特別慢,其他線程等待這2個線程進入Safepoint等待了2255ms。

打印進入Safepoint慢的線程

  • -XX:+SafepointTimeout

  • -XX:SafepointTimeoutDelay=2000

這兩個參數的意思是當有線程進入Safepoint超過2000毫秒時,會認為進入Safepoint超時了,這時會打出來哪些線程沒有進入Safepoint,具體日志如下:

# SafepointSynchronize::begin: Timeout detected:
# SafepointSynchronize::begin: Timed out while spinning to reach a safepoint.
# SafepointSynchronize::begin: Threads which did not reach the safepoint:
# "RpcServer.listener,port=24600" #32 daemon prio=5 os_prio=0 tid=0x00007f4c14b22840 nid=0xa621 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
# SafepointSynchronize::begin: (End of list)復制代碼

從上面的日志可以看出來是RpcServer.listener進入Safepoint耗時過長,那么該如何解決這個問題呢?這就需要了解一點GC和Safepoint的背景知識了。

GC及Safepoint

GC

GC(Garabage Collection):垃圾回收,是Java這種自動內存管理語言中的關鍵技術。GC要解決的三個關鍵問題是:哪些內存可以回收?什么時候回收?以及如何回收?對于哪些內存可以回收,常見的有引用計數算法和可達性分析算法來判斷對象是否存活。可達性分析算法的基本思路是從GC Roots出發向下搜索,搜索走過的路徑稱為引用鏈,當一個對象到GC Roots沒有任何引用鏈相連時,則稱該對象不可達,也就是說可以回收了。常見的GC Roots對象有:

  • 虛擬機棧中引用的對象

  • 方法區中靜態屬性引用的對象

  • 方法區中常量引用的對象

  • Native方法棧中引用的對象

Safepoint

Java虛擬機HotSpot的實現中,使用一組稱為OopMap的數據結構來存放對象引用,從而可以快速且準確的完成GC Root掃描。但程序執行的過程中,引用關系隨時都可能發生變化,而HotSpot虛擬機只會在特殊的指令位置才會生成OopMap來記錄引用關系,這些位置便被稱為Safepoint。換句話說,就是在Safepoint這個點上,虛擬機對于調用棧、寄存器等一些重要的數據區域里什么地方包含了什么引用是十分清楚的,這個時候是可以很快完成GC Roots的掃描和可達性分析的。HotSpot會在所有方法的臨返回之前,以及所有Uncounted loop的循環回跳之前放置Safepoint。當需要GC時候,虛擬機會首先設置一個標志,然后等待所有線程進入Safepoint,但是不同線程進入Safepoint的時間點不一樣,先進入Safepoint的線程需要等待其他線程全部進入Safepoint,所以Safepoint是會導致STW的。

Counted loop

JVM認為比較短的循環,所以不會放置Safepoint,比如用int作為index的循環。與其對應的是Uncounted loop。

Uncounted loop

JVM會在每次循環回跳之前放置Safepoint,比如用long作為index的循環。所以一個常見的問題是當某個線程跑進了Counted loop時,JVM啟動了GC,然后需要等待這個線程把循環跑完才能進入Safepoint,如果這個循環是個大循環,且循環內執行的比較慢,而且不存在其他函數調用產生其他Safepoint,這時就需要等待該線程跑完循環才能從其他位置進入Safepoint,這就導致了其他線程可能會長時間的STW。

解 決 問 題

UseCountLoopSafepoints

-XX:+UseCountedLoopSafepoints這個參數會強制在Counted loop循環回跳之前插入Safepoint,也就是說即使循環比較短,JVM也會幫忙插入Safepoint了,用于防止大循環執行時間過長導致進入Safepoint卡住的問題。但是這個參數在JDK8上是有Bug的,可能會導致JVM Crash,而且是到JDK9才修復的,具體參考JDK-8161147。鑒于我們使用的是OpenJDK8,所以只能放棄該方案。

修改循環index為long型

上面的Safepoint Timeout日志已經明確指出了,進Safepoint慢的線程是RpcServer里的listener線程,所以在仔細讀了一遍代碼之后,發現其調用到的函數cleanupConnections里有個大循環,具體代碼如下:

/** cleanup connections from connectionList. Choose a random range     
* to scan and also have a limit on the number of the connections     
* that will be cleanedup per run. The criteria for cleanup is the time     
* for which the connection was idle. If 'force' is true then all     
* connections will be looked at for the cleanup.     
* @param force all connections will be looked at for cleanup    
*/
private void cleanupConnections(boolean force) {  if (force || numConnections > thresholdIdleConnections) {    long currentTime = System.currentTimeMillis();    if (!force && (currentTime - lastCleanupRunTime) < cleanupInterval) {      return;    }    int start = 0;    int end = numConnections - 1;    if (!force) {      start = rand.nextInt() % numConnections;      end = rand.nextInt() % numConnections;      int temp;     if (end < start) {        temp = start;  start = end;  end = temp;  } } int i = start; int numNuked =0; while (i <= end) {   Connection c;  synchronized (connectionList) {  try {   c = connectionList.get(i);   } catch (Exception e) {return;} }        if (c.timedOut(currentTime)) { if (LOG.isDebugEnabled()) LOG.debug(getName() + ": disconnecting client " + c.getHostAddress()); closeConnection(c); numNuked++; end--; //noinspection UnusedAssignment c = null;if (!force && numNuked == maxConnectionsToNuke) break;}else i++;} lastCleanupRunTime = System.currentTimeMillis();}
}復制代碼

如注釋所說,它會從connectionList中隨機選擇一個區間,然后遍歷這個區間內的connection,并清理掉其中已經timeout的connection。但是,connectionList有可能很大,因為出問題的集群是個離線集群,會有多個MR/Spark Job來進行訪問,而每個Job又會同時起多個Mapper/Reducer/Executer來進行訪問,其每一個都是一個HBase Client,而Client為了性能考慮,默認連同一個RegionServer的connection數使用了配置4,這就導致connectionList里面可能存在大量的從client連接過來的connection。更為關鍵的是,這里循環的index是int類型,所以這是一個Counted loop,JVM不會在每次循環回跳的時候插入Safepoint。當GC發生時,如果RpcServer的listener線程剛好執行到該函數里的循環內部時,則必須等待循環跑完才能進入Safepoint,而此時其他線程也必須一起等著,所以從現象上看就是長時間的STW。

Debug的過程很曲折,但問題解決起來其實很簡單,就是把這里的循環index從int類型改為long型即可,這樣JVM會在每次循環回跳前插入Safepoint,即使GC時候執行到了循環內部,也只需執行到單次循環體結束便可以進入Safepoint,無需等待整個循環跑完。具體代碼修改如下:

至此,問題得到解決。

最后,本文重點不是介紹Safepoint原理,主要是對一次線上真實Case的的踩坑記錄,希望文中的JVM參數配置和Debug過程可以對大家有所幫助,如有錯誤,歡迎指正。

參考文檔:
https://bugs.openjdk.java.net/browse/JDK-8161147
http://calvin1978.blogcn.com/articles/safepoint.html
https://xhao.io/2018/02/safepoint-1/
https://www.zhihu.com/question/29268019
《深入理解Java虛擬機》周志明著

本文首發于公眾號“小米云技術”,轉載請注明出處,點擊查看原文鏈接。


轉載于:https://juejin.im/post/5d1b1fc46fb9a07ef7108d82

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/248611.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/248611.shtml
英文地址,請注明出處:http://en.pswp.cn/news/248611.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

scrapy 第一個案例(爬取騰訊招聘職位信息)

import scrapy import jsonclass TzcSpider(scrapy.Spider):# spider的名字&#xff0c;唯一name tzc# 起始地址start_urls [https://hr.tencent.com/position.php?keywordspython&tid0&lid2268]# 每個url爬取之后會調用這個方法def parse(self, response):tr resp…

系統帶你學習 WebAPIs 第一講

Web APIs 本篇學習目標&#xff1a; 能夠通過ID來獲取元素 能夠通過標簽名來獲取元素 能夠通過class來獲取元素 能夠通過選擇器來獲取元素 能夠獲取body和html元素 能夠給元素注冊事件 能夠修改元素的內容 能夠區分innerText和innerHTML的區別 能夠修改像div這類普通元素的屬性…

react-webpack config webpack@3.4.1

1.最重要的一點 yarn add webpack3.4.1 -g 2. 解決跨域請求 webpack.json 中添加 https://segmentfault.com/q/1010000008190876?_ea1579884 webpack config less -----框架 ----查看考鏈接 https://blog.csdn.net/mjzhang1993/article/details/79013430轉載于:https://w…

系統帶你學習 WebAPIs 第二講

Web APIs 本篇學習目標&#xff1a; 能夠說出排他操作的一般實現步驟 能夠使用html5中的dataset方式操作自定義屬性 能夠根據提示完成百度換膚的案例 能夠根據提示完成全選案例 能夠根據提示完成tab欄切換案例 能夠區分元素節點、文本節點、屬性節點 能夠獲取指定元素的父元素 …

在微信瀏覽器中 location.reload() 不刷新解決方案(直接調用方法)

1、問題 在微信瀏覽器中&#xff0c;需要時刷新當前頁面。 正常情況下我們直接使用 location.reload 方法來刷新。 2、解決方法 function realod(){var {search,href} window.location;href href.replace(/&?t_reload(\d)/g,)window.location.href href(search?&:…

Python爬蟲學習筆記1:request、selenium、ChromeDrive、GeckoDriver等相關依賴安裝

系列學習筆記參考&#xff1a;python3網絡爬蟲開發實戰 requests # pip install requests import requestsselenium Selenium是一個自動化測試工具&#xff0c;利用它我們可以驅動瀏覽器執行特定的動作&#xff0c;如點擊、下拉等 操作 。 對于一些 JavaScript誼染的頁面來說&a…

系統帶你學習 WebAPIs 第三講

Web APIs 本篇學習目標&#xff1a; 能夠使用removeChild()方法刪除節點 能夠完成動態生成表格案例 能夠使用傳統方式和監聽方式給元素注冊事件 能夠說出事件流執行的三個階段 能夠在事件處理函數中獲取事件對象 能夠使用事件對象取消默認行為 能夠使用事件對象阻止事件冒泡 能…

CSS3文本與字體

一、CSS3 換行 1、word-break&#xff08;規定自動換行的處理方法&#xff09; word-break: normal / break-all / keep-all;/* normal&#xff1a;使用瀏覽器默認的換行規則 break-all&#xff1a;允許在單詞內換行 keep-all&#xff1a;只能在半角空格或連字符處換行 */ 兼容…

系統帶你學習 WebAPIs 第四講

Web APIs 本篇學習目標&#xff1a; 能夠說出常用的3-5個鍵盤事件 能夠知道如何獲取當前鍵盤按下的是哪個鍵 能夠知道瀏覽器的頂級對象window 能夠使用window.onload事件 能夠使用window.onresize事件 能夠說出兩種定時器的區別 能夠使用location對象的href屬性完成頁面之間的跳…

linux chrome 安裝過程記錄

最近&#xff0c;由于公司需要做爬蟲抓取一些新聞&#xff0c;在開發過程中&#xff0c;發現有些網站有一定的反爬措施&#xff0c;通過瀏覽器訪問一切正常&#xff0c;通過其他方式&#xff0c;包括&#xff1a;curl&#xff0c;urlconnection 等&#xff0c;就算加入了cookie…

系統帶你學習 WebAPIs 第五講

Web APIs 本篇學習目標: 能夠說出常見 offset 系列屬性的作用 能夠說出常見 client 系列屬性的作用 能夠說出常見 scroll 系列屬性的作用 能夠封裝簡單動畫函數 **1.1. **元素偏移量 offset 系列 1.1.1 offset 概述 offset 翻譯過來就是偏移量&#xff0c; 我們使用 offset系…

ajax請求相關問題

Ajax中async:false/true的作用&#xff1a; async. 默認是 true&#xff0c;即為異步方式&#xff0c;$.ajax執行后&#xff0c;會繼續執行ajax后面的腳本&#xff0c;直到服務器端返回數據后&#xff0c;觸發$.ajax里的success方法&#xff0c;這時候執行的是兩個線程。 async…

有贊美業微前端的落地總結

2020年4月&#xff0c;有贊美業的前端團隊歷經7個月時間&#xff0c;完成了美業PC架構從單體SPA到微前端架構的設計、遷移工作。PPT在去年6月份就有了&#xff0c;現在再整理一下形成文章分享給大家。 頭圖 目錄 Part 01 “大話”微前端 微前端是什么 背景 目標 達成價值 …

bcp文件, 逗號文件

bcp 實用工具 https://docs.microsoft.com/zh-cn/sql/tools/bcp-utility?viewsql-server-2017 大容量復制程序實用工具 (bcp) 可以在 Microsoft SQL Server 實例和用戶指定格式的數據文件間大容量復制數據。 使用 bcp 實用工具可以將大量新行導入 SQL Server 表&#xff0c;或…

遠程登錄和復制文件

命令&#xff1a; ssh 對應英文&#xff1a; secure shell 使用&#xff1a; ssh [-P] 用戶名ip 優點&#xff1a; 加密和壓縮&#xff0c;即安全和提高傳輸速度 注意&#xff1a; 除了windows系統外的系統默認有ssh客戶端&#xff0c;直接使用命令便可&#xff1b; windows系統…

Markdown 編輯器才是yyds|CSDN編輯器測評

前言 今天小編為大家介紹一款編輯器&#xff0c;也正是小編書寫這篇文章所使用的Markdown編輯器&#xff0c;正是廣大博友想要發布文章的工具。那么 你知道他的都有哪些方便之處么 下面小編帶你了解一下 Markdown是什么 Markdown是一種輕量標記語言,通過簡單的語法&#xff…

JVM對象已死

(一) 引用計數法 每有一個引用就加1&#xff0c;每失效一個就減1&#xff0c;為0表示可回收&#xff1b;但是此方法無法解決相互引用的情況 (二) 根搜索算法 從一系列的GCRoots對象為起點向下搜索&#xff0c;搜索的路徑稱為引用鏈&#xff0c;當一個對象沒有任何引…

pcl和opencv多版本共存

pcl和opencv多版本共存 在Ubuntu 16.04系統下安裝kinetic版本ROS&#xff0c; 會默認安裝opencv 3.3.1和pcl1.7. opencv安裝路徑/opt/ros/kinetic/ pcl安裝路徑/usr/ 如果想安裝opencv2.4版本&#xff0c;或者安裝pcl 1.8版本&#xff0c;那么如何設置安裝路徑&#xff0c;同時…

45天帶你玩轉Node(第三天)Node環境安裝

本篇目標 能夠搭建 Node 運行環境掌握 NodeJS 程序的運行方法理解模塊化開發理解系統模塊和第三方模塊理解package.json文件作用 1.Node 開發概述 1.1為什么要學習服務器端開發技術 前端人員為什么要學習服務器端開發技術&#xff1f; 能夠和后端程序員更加緊密的配合網站…

給頁面加速,干掉Dom Level 0 Event

現在的web應用越來越復雜,需要響應各種各樣的用戶觸發事件,因而也就不可避免的,需要給我們的html頁面上的dom元素增加事件監聽函數. 我們知道給dom元素綁定事件監聽函數的方法有如下3種: 1 : 頁面html: <button onclick”test();”></button>2: 頁面html: <bu…