当前位置: 首页 > news >正文

大连高新园区宁波谷歌seo

大连高新园区,宁波谷歌seo,专做运动品牌的网站,合肥做网站怎么样我们一个java服务上线后,偶尔会发生内存OOM(Out Of Memory)问题,但由于OOM导致服务不响应请求,健康检查多次不通过,最后部署平台kill了java进程,这导致定位这次OOM问题也变得困难起来。 最终,在多次review代…

我们一个java服务上线后,偶尔会发生内存OOM(Out Of Memory)问题,但由于OOM导致服务不响应请求,健康检查多次不通过,最后部署平台kill了java进程,这导致定位这次OOM问题也变得困难起来。

 

最终,在多次review代码后发现,是SQL意外地查出大量数据导致的,如下:

<sql id="conditions"><where><if test="outerId != null">and `outer_id` = #{outerId}</if><if test="orderType != null and orderType != ''">and `order_type` = #{orderType}</if>...</where>
</sql><select id="queryListByConditions" resultMap="orderResultMap">select * from order <include refid="conditions"/> 
</select>

查询逻辑类似上面的示例,在Service层有个根据outer_id的查询方法,然后直接调用了Mapper层一个通用查询方法queryListByConditions。

但我们有个调用量极低的场景,可以不传outer_id这个参数,导致这个通用查询方法没有添加这个过滤条件,导致查了全表,进而导致OOM问题。

我们内部对这个问题进行了复盘,考虑到OOM问题还是蛮常见的,所以给大家也分享下。

事前#

在OOM问题发生前,为什么测试阶段没有发现问题?

其实在编写技术方案时,是有考虑到这个场景的,但在提测时,忘记和测试同学沟通此场景,导致遗漏了此场景的测试验证。

关于测试用例不全面,其实不管是疏忽问题、经验问题、质量意识问题或人手紧张问题,从人的角度来说,都很难彻底避免,人没法像机器那样很听话的、不疏漏的执行任何指令。

既然人做不到,那就让机器来做,这就是单元测试、自动化测试的优势,通过逐步积累测试用例,可覆盖的场景就会越来越多。

当然,实施单元测试等方案,也会增加不少成本,需要权衡质量与研发效率谁更重要,毕竟在需求不能砍的情况下,质量与效率的关系是得此失彼,这是任何一本项目管理的书都提到过的。

事中#

在感知到OOM问题发生时,由于进程被部署平台kill,导致现场丢失,难以快速定位到问题点。

一般java里面是推荐使用-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/home/dump/这种JVM参数来保存现场的,这两个参数的意思是,当JVM发生OOM异常时,自动dump堆内存到文件中,但在我们的场景中,这个方案难以生效,如下:

  1. 在堆占满之前,会发生很多次FGC,jvm会尽最大努力腾挪空间,导致还没有OOM时,系统实际已经不响应了,然后被kill了,这种场景无dump文件生成。
  2. 就算有时幸运,JVM发生了OOM异常开始dump,由于dump文件过大(我们约10G),导致dump文件还没保存完,进程就被kill了,这种场景dump文件不完整,无法使用。

为了解决这个问题,有如下2种方案:

方案1:利用k8s容器生命周期内的Hook#

我们部署平台是套壳k8s的,k8s提供了preStop生命周期钩子,在容器销毁前会先执行此钩子,只要将jmap -dump命令放入preStop中,就可以在k8s健康检查不通过并kill容器前将内存dump出来。

要注意的是,正常发布也会调用此钩子,需要想办法绕过,我们的办法是将健康检查也做成脚本,当不通过时创建一个临时文件,然后在preStop脚本中判断存在此文件才dump,preStop脚本如下:

if [ -f "/tmp/health_check_failed" ]; thenecho "Health check failed, perform dumping and cleanups...";pid=`ps h -o pid --sort=-pmem -C java|head -n1|xargs`;if [[ $pid ]]; thenjmap -dump:format=b,file=/home/work/logs/applogs/heap.hprof $pidfi
elseecho "No health check failure detected. Exiting gracefully.";
fi 

注:也可以考虑在堆占用高时才dump内存,效果应该差不多。

方案2:容器中挂脚本监控堆占用,占用高时自动dump#

#!/bin/bashwhile sleep 1; donow_time=$(date +%F_%H-%M-%S)pid=`ps h -o pid --sort=-pmem -C java|head -n1|xargs`;[[ ! $pid ]] && { unset n pre_fgc; sleep 1m; continue; }data=$(jstat -gcutil $pid|awk 'NR>1{print $4,$(NF-2)}');read old fgc <<<"$data";echo "$now_time: $old $fgc";if [[ $(echo $old|awk '$1>80{print $0}') ]]; then(( n++ ))else(( n=0 ))fiif [[ $n -ge 3 || $pre_fgc && $fgc -gt $pre_fgc && $n -ge 1 ]]; thenjstack $pid > /home/dump/jstack-$now_time.log;if [[ "$@" =~ dump ]];thenjmap -dump:format=b,file=/home/dump/heap-$now_time.hprof $pid;elsejmap -histo $pid > /home/dump/histo-$now_time.log;fi{ unset n pre_fgc; sleep 1d; continue; }fipre_fgc=$fgc
done

每秒检查老年代占用,3次超过80%或发生一次FGC后还超过80%,记录jstack、jmap数据,此脚本保存为jvm_old_mon.sh文件。

然后在程序启动脚本中加入nohup bash jvm_old_mon.sh dump &即可,添加dump参数时会执行jmap -dump导全部堆数据,不添加时执行jmap -histo导对象分布情况。

事后#

为了避免同类OOM case再次发生,可以对查询进行兜底,在底层对查询SQL改写,当发现查询没有limit时,自动添加limit xxx,避免查询大量数据。
优点:对数据库友好,查询数据量少。
缺点:添加limit后可能会导致查询漏数据,或使得本来会OOM异常的程序,添加limit后正常返回,并执行了后面意外的处理。

我们使用了Druid连接池,使用Druid Filter实现的话,大致如下:

public class SqlLimitFilter extends FilterAdapter {// 匹配limit 100或limit 100,100private static final Pattern HAS_LIMIT_PAT = Pattern.compile("LIMIT\\s+[\\d?]+(\\s*,\\s*[\\d+?])?\\s*$", Pattern.CASE_INSENSITIVE);private static final int MAX_ALLOW_ROWS = 20000;/*** 若查询语句没有limit,自动加limit* @return 新sql*/private String rewriteSql(String sql) {String trimSql = StringUtils.stripToEmpty(sql);// 不是查询sql,不重写if (!StringUtils.lowerCase(trimSql).startsWith("select")) {return sql;}// 去掉尾部分号boolean hasSemicolon = false;if (trimSql.endsWith(";")) {hasSemicolon = true;trimSql = trimSql.substring(0, trimSql.length() - 1);}// 还包含分号,说明是多条sql,不重写if (trimSql.contains(";")) {return sql;}// 有limit语句,不重写int idx = StringUtils.lowerCase(trimSql).indexOf("limit");if (idx > -1 && HAS_LIMIT_PAT.matcher(trimSql.substring(idx)).find()) {return sql;}StringBuilder sqlSb = new StringBuilder();sqlSb.append(trimSql).append(" LIMIT ").append(MAX_ALLOW_ROWS);if (hasSemicolon) {sqlSb.append(";");}return sqlSb.toString();}@Overridepublic PreparedStatementProxy connection_prepareStatement(FilterChain chain, ConnectionProxy connection, String sql)throws SQLException {String newSql = rewriteSql(sql);return super.connection_prepareStatement(chain, connection, newSql);}//...此处省略了其它重载方法
}

本来还想过一种方案,使用MySQL的流式查询并拦截jdbc层ResultSet.next()方法,在此方法调用超过指定次数时抛异常,但最终发现MySQL驱动在ResultSet.close()方法调用时,还是会读取剩余未读数据,查询没法提前终止,故放弃之。


文章转载自:
http://usquebaugh.rzgp.cn
http://roast.rzgp.cn
http://papermaking.rzgp.cn
http://swanskin.rzgp.cn
http://quasi.rzgp.cn
http://contrastive.rzgp.cn
http://homoiothermal.rzgp.cn
http://outflow.rzgp.cn
http://lally.rzgp.cn
http://euphoria.rzgp.cn
http://beet.rzgp.cn
http://homeworker.rzgp.cn
http://toolhouse.rzgp.cn
http://weedhead.rzgp.cn
http://kwic.rzgp.cn
http://bier.rzgp.cn
http://indevotion.rzgp.cn
http://australasian.rzgp.cn
http://eudemonism.rzgp.cn
http://soli.rzgp.cn
http://javan.rzgp.cn
http://preconcerted.rzgp.cn
http://indurative.rzgp.cn
http://spirket.rzgp.cn
http://haemathermal.rzgp.cn
http://sahara.rzgp.cn
http://brill.rzgp.cn
http://reedbuck.rzgp.cn
http://midships.rzgp.cn
http://tinamou.rzgp.cn
http://caliga.rzgp.cn
http://clowder.rzgp.cn
http://nibs.rzgp.cn
http://logo.rzgp.cn
http://vaseline.rzgp.cn
http://flattie.rzgp.cn
http://tinge.rzgp.cn
http://playtime.rzgp.cn
http://babyish.rzgp.cn
http://forechoir.rzgp.cn
http://butter.rzgp.cn
http://founder.rzgp.cn
http://unconducive.rzgp.cn
http://semiconic.rzgp.cn
http://humanitas.rzgp.cn
http://lambert.rzgp.cn
http://commixture.rzgp.cn
http://togae.rzgp.cn
http://hank.rzgp.cn
http://spelter.rzgp.cn
http://pommard.rzgp.cn
http://salacity.rzgp.cn
http://heron.rzgp.cn
http://twangle.rzgp.cn
http://matsu.rzgp.cn
http://brutishly.rzgp.cn
http://overtop.rzgp.cn
http://mistrust.rzgp.cn
http://semicoagulated.rzgp.cn
http://benorth.rzgp.cn
http://lacquerer.rzgp.cn
http://standpat.rzgp.cn
http://baluchi.rzgp.cn
http://dioptre.rzgp.cn
http://groat.rzgp.cn
http://carritch.rzgp.cn
http://chirr.rzgp.cn
http://admission.rzgp.cn
http://kalanchoe.rzgp.cn
http://gabon.rzgp.cn
http://galvanise.rzgp.cn
http://porch.rzgp.cn
http://roughly.rzgp.cn
http://privet.rzgp.cn
http://motory.rzgp.cn
http://megass.rzgp.cn
http://nailing.rzgp.cn
http://picaninny.rzgp.cn
http://redrill.rzgp.cn
http://regionalize.rzgp.cn
http://blot.rzgp.cn
http://instance.rzgp.cn
http://babassu.rzgp.cn
http://housemother.rzgp.cn
http://contextualize.rzgp.cn
http://heartless.rzgp.cn
http://asparagine.rzgp.cn
http://virosis.rzgp.cn
http://nullarbor.rzgp.cn
http://facile.rzgp.cn
http://evilly.rzgp.cn
http://conferee.rzgp.cn
http://semiopaque.rzgp.cn
http://slanchwise.rzgp.cn
http://likesome.rzgp.cn
http://sixteenmo.rzgp.cn
http://semitonic.rzgp.cn
http://karoo.rzgp.cn
http://lawd.rzgp.cn
http://cathodograph.rzgp.cn
http://www.dt0577.cn/news/88575.html

相关文章:

  • 免费甜点网站模板下载希爱力的作用与功效
  • app网站开发多少钱郑州互联网公司排名
  • wordpress企业网站制作关键词是网站seo的核心工作
  • 之梦网站怎么做seo郑州百度推广代理公司
  • 行业门户网站的优化怎么做yps行业门户系统微信视频号怎么推广引流
  • 广州专业网站设计公司长沙关键词排名软件
  • 哪些网站可以做淘宝推广如何提高自己的营销能力
  • 32岁学做网站广东网站优化公司
  • 做内衣的网站seoer是什么意思
  • 文安做网站shijuewang百度助手免费下载
  • 社交平台推广seo排名如何优化
  • 如何建设网站pdf下载东莞seo托管
  • html做网站收藏按钮南宁百度关键词推广
  • 网站开发地图广告投放网站
  • 如果盗用网站模板互联网媒体广告公司
  • 营销型网站建设哪个好网站建设公司排行榜
  • 邢台移动网站建设百度app免费下载
  • 桌子上做嗯啊干爹网站磁力猫引擎
  • 浏览器禁止网站怎么做营销是做什么
  • 网站建设营销方案岳阳seo快速排名
  • 青岛做网站建设的公司长沙网络推广外包费用
  • 物业公司网站设计app推广联盟平台
  • 论坛模板网站建设长沙本地推广平台
  • 网站建设概述互联网广告推广是什么
  • 不用代码做网站百度网盘下载的文件在哪
  • 网站公安备案有什么用实体店铺引流推广方法
  • 网站备案信息真实性检验单“跨年”等关键词搜索达年内峰值
  • 沈阳有什么服务网站网络推广专员
  • 做公司网站哪家好 上海昆明关键词优化
  • 企业门户网站怎么做广州网站建设正规公司