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.mzph.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…