一次线上OOM问题的个人复盘
我们一个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堆内存到文件中,但在我们的场景中,这个方案难以生效,如下:
- 在堆占满之前,会发生很多次FGC,jvm会尽最大努力腾挪空间,导致还没有OOM时,系统实际已经不响应了,然后被kill了,这种场景无dump文件生成。
- 就算有时幸运,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()
方法调用时,还是会读取剩余未读数据,查询没法提前终止,故放弃之。
相关文章:

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

【机器学习】基础知识点的汇总与总结!更新中
文章目录 一、监督学习1.1、单模型1.1.1、线性回归1.1.2、逻辑回归(Logistic Regression)1.1.3、K近邻算法(KNN)1.1.4、决策树1.1.5、支持向量机(SVM)1.1.6、朴素贝叶斯 1.2、集成学习1.2.1、Boosting1&…...
NLP杂记
来京一周余,初病将愈,终跑通llama及ViT,记于此—— 之前都是做的图像,大模型迁移基本上都是NLP相关的知识,很多东西和CV差距还是有点,再加上大模型对算力要求较高,基于云的操作对我一个习惯在本…...

算法通过村第二关-链表白银笔记
文章目录 再战链表|反转链表剑指 Offer II 024. 反转链表熟练掌握这两种解法建立头节点的解决思路不采用建立头节点的方法采用循环/递归的方式解决 总结 再战链表|反转链表 提示:多拿些酒来,因为生命只有乌有。 剑指 Offer II 024. 反转链表 如果不使用…...

力扣题库刷题笔记75--颜色分类
1、题目如下: 2、个人Pyhon代码实现如下: 第一种思路是取巧,通过计数0、1、2的个数,去替换nums 备注第10行代码在本地可以跑过,但是力扣跑不过,所以就用了第10-16行代码进行替换 第二种思路是通过冒泡排序去…...

《面试1v1》如何提高远程用户的吞吐量
🍅 作者简介:王哥,CSDN2022博客总榜Top100🏆、博客专家💪 🍅 技术交流:定期更新Java硬核干货,不定期送书活动 🍅 王哥多年工作总结:Java学习路线总结…...

论文笔记--Distilling the Knowledge in a Neural Network
论文笔记--Distilling the Knowledge in a Neural Network 1. 文章简介2. 文章概括3 文章重点技术3.1 Soft Target3.2 蒸馏Distillation 4. 文章亮点5. 原文传送门 1. 文章简介 标题:Distilling the Knowledge in a Neural Network作者:Hinton, Geoffre…...

Mac上安装sshfs
目录 写在前面安装使用参考完 写在前面 1、本文内容 Mac上安装sshfs 2、平台 mac 3、转载请注明出处: https://blog.csdn.net/qq_41102371/article/details/130156287 安装 参考:https://ports.macports.org/port/sshfs/ 通过port安装 点击啊insta…...

MQ公共特性介绍 (ActiveMQ, RabbitMQ, RocketMQ, Kafka对比)
本章介绍 本文主要介绍所有MQ框架都具备的公共特点,同时对比了一些目前比较主流MQ框架的优缺点,给大家做技术选型作参考。 文章目录 本章介绍MQ介绍适用场景异步通信案例一案例二 系统解耦削峰填谷广播通信总结 缺点MQ对比APQP历史AMQP是什么 MQ介绍 M…...

灵雀云Alauda MLOps 现已支持 Meta LLaMA 2 全系列模型
在人工智能和机器学习领域,语言模型的发展一直是企业关注的焦点。然而,由于硬件成本和资源需求的挑战,许多企业在应用大模型时仍然面临着一定的困难。为了帮助企业更好地应对上述挑战,灵雀云于近日宣布,企业可通过Alau…...
技术方案模版
技术方案模板 概述 1.1 术语 名称 说明 1.2 需求背景 来自产品的需求可以引用PRD和设计稿 技术类的改造需要写明背景业务用例分析 从需求中抽象出的核心用例详细设计 3.1 应用架构 3.2 模型设计 领域模型的关系,可以用UML 类图来实现 3.3. 详细实现 可以通过时序图…...

【Linux命令200例】cut强大的文本处理工具
🏆作者简介,黑夜开发者,全栈领域新星创作者✌,2023年6月csdn上海赛道top4。 🏆本文已收录于专栏:Linux命令大全。 🏆本专栏我们会通过具体的系统的命令讲解加上鲜活的实操案例对各个命令进行深入…...

《论文阅读》具有特殊Token和轮级注意力的层级对话理解 ICLR 2023
《论文阅读》具有特殊Token和轮级注意力的层级对话理解 前言简介问题定义模型构建知识点Intra-turn ModelingInter-turn Modeling分类前言 你是否也对于理解论文存在困惑? 你是否也像我之前搜索论文解读,得到只是中文翻译的解读后感到失望? 小白如何从零读懂论文?和我一…...

C# 定时器封装版
一、概述 在 Winform 等平台开发中,经常会用到定时器的功能,但项目定时器一旦写多了,容易使软件变卡,而且运行时间长了会造成软件的闪退,这个可能是内存溢出造成的,具体原因我也没去深究,另一个…...

前端学习——Vue (Day4)
组件的三大组成部分 组件的样式冲突 scoped <template><div class"base-one">BaseOne</div> </template><script> export default {} </script><style scoped> /* 1.style中的样式 默认是作用到全局的2.加上scoped可以让样…...

如果你是一个嵌入式面试官,你会问哪些问题?
以下是一些嵌入式面试中可能会问到的问题: 1.你对嵌入式系统有什么理解?它们与桌面或服务器系统有什么不同? 2.你用过哪些单片机和微处理器?对其中哪一款最熟悉? 3.你用什么编程语言编写嵌入式软件?你觉…...

学习笔记十三:云服务器通过Kubeadm安装k8s1.25,供后续试验用
Kubeadm安装k8s1.25 k8s环境规划:初始化安装k8s集群的实验环境先建生产环境服务器,后面可以通过生成镜像克隆node环境修改主机名配置yum源关闭防火墙关闭selinux配置时间同步配置主机 hosts 文件,相互之间通过主机名互相访问 **192.168.40.18…...

【Maven】Maven配置国内镜像
文章目录 1. 配置maven的settings.xml文件1.1. 先把镜像mirror配置好1.2. 再把仓库配置好 2. 在idea中引用3. 参考资料 网上配置maven国内镜像的文章很多,为什么选择我,原因是:一次配置得永生、仓库覆盖广、仓库覆盖全面、作者自用的配置。 1…...

ChatGPT有几个版本,哪个版本最强,如何选择适合自己的?
ChatGPT就像内容生产界的瑞士军刀。它可以是数学导师、治疗师、职业顾问、编程助手,甚至是旅行指南。只要你知道如何让它做你想做的事,ChatGPT几乎可以提供你要的任何东西。 但重要的是,你知道哪个版本的ChatGPT最能满足你的需求吗&#x…...
pg_standby备库搭建
1.主库 1.1主库参数文件修改 -- 该路径也需要在从库创建 mkdir -p /postgresql/archive chown -R postgres.postgres /postgresql/archive-- 主库配置归档 wal_levelreplica archive_modeon archive_commandcp %p /postgresql/archive/%f restore_commandcp /postgresql/arch…...

(二)TensorRT-LLM | 模型导出(v0.20.0rc3)
0. 概述 上一节 对安装和使用有个基本介绍。根据这个 issue 的描述,后续 TensorRT-LLM 团队可能更专注于更新和维护 pytorch backend。但 tensorrt backend 作为先前一直开发的工作,其中包含了大量可以学习的地方。本文主要看看它导出模型的部分&#x…...

如何在最短时间内提升打ctf(web)的水平?
刚刚刷完2遍 bugku 的 web 题,前来答题。 每个人对刷题理解是不同,有的人是看了writeup就等于刷了,有的人是收藏了writeup就等于刷了,有的人是跟着writeup做了一遍就等于刷了,还有的人是独立思考做了一遍就等于刷了。…...
2023赣州旅游投资集团
单选题 1.“不登高山,不知天之高也;不临深溪,不知地之厚也。”这句话说明_____。 A、人的意识具有创造性 B、人的认识是独立于实践之外的 C、实践在认识过程中具有决定作用 D、人的一切知识都是从直接经验中获得的 参考答案: C 本题解…...
rnn判断string中第一次出现a的下标
# coding:utf8 import torch import torch.nn as nn import numpy as np import random import json""" 基于pytorch的网络编写 实现一个RNN网络完成多分类任务 判断字符 a 第一次出现在字符串中的位置 """class TorchModel(nn.Module):def __in…...

人机融合智能 | “人智交互”跨学科新领域
本文系统地提出基于“以人为中心AI(HCAI)”理念的人-人工智能交互(人智交互)这一跨学科新领域及框架,定义人智交互领域的理念、基本理论和关键问题、方法、开发流程和参与团队等,阐述提出人智交互新领域的意义。然后,提出人智交互研究的三种新范式取向以及它们的意义。最后,总结…...
JS手写代码篇----使用Promise封装AJAX请求
15、使用Promise封装AJAX请求 promise就有reject和resolve了,就不必写成功和失败的回调函数了 const BASEURL ./手写ajax/test.jsonfunction promiseAjax() {return new Promise((resolve, reject) > {const xhr new XMLHttpRequest();xhr.open("get&quo…...

AI语音助手的Python实现
引言 语音助手(如小爱同学、Siri)通过语音识别、自然语言处理(NLP)和语音合成技术,为用户提供直观、高效的交互体验。随着人工智能的普及,Python开发者可以利用开源库和AI模型,快速构建自定义语音助手。本文由浅入深,详细介绍如何使用Python开发AI语音助手,涵盖基础功…...

[论文阅读]TrustRAG: Enhancing Robustness and Trustworthiness in RAG
TrustRAG: Enhancing Robustness and Trustworthiness in RAG [2501.00879] TrustRAG: Enhancing Robustness and Trustworthiness in Retrieval-Augmented Generation 代码:HuichiZhou/TrustRAG: Code for "TrustRAG: Enhancing Robustness and Trustworthin…...
高防服务器价格高原因分析
高防服务器的价格较高,主要是由于其特殊的防御机制、硬件配置、运营维护等多方面的综合成本。以下从技术、资源和服务三个维度详细解析高防服务器昂贵的原因: 一、硬件与技术投入 大带宽需求 DDoS攻击通过占用大量带宽资源瘫痪目标服务器,因此…...

边缘计算网关提升水产养殖尾水处理的远程运维效率
一、项目背景 随着水产养殖行业的快速发展,养殖尾水的处理成为了一个亟待解决的环保问题。传统的尾水处理方式不仅效率低下,而且难以实现精准监控和管理。为了提升尾水处理的效果和效率,同时降低人力成本,某大型水产养殖企业决定…...