草庐IT

完了!线上又发生 OOM 了!

艾小仙 2023-03-28 原文
本文转载自微信公众号「艾小仙」,作者艾小仙 。转载本文请联系艾小仙公众号。

现象

线上某个服务有接口非常慢,通过监控链路查看发现,中间的 GAP 时间非常大,实际接口并没有消耗很多时间,并且在那段时间里有很多这样的请求。

原因分析

先从监控链路分析了一波,发现请求是已经打到服务上了,处理之前不知道为什么等了 3s,猜测是不是机器当时负载太大了,通过 QPS 监控查看发现,在接口慢的时候 CPU 突然增高,同时也频繁的 GC ,并且时间很长,但是请求量并不大,并且这台机器很快就因为 Heap满了而被下掉了。

去看了下日志,果然有 OOM 的报错,但是从报错信息上并没办法找到 Root Cause。

system error: org.springframework.web.util.NestedServletException: Handler dispatch failed; nested exception is java.lang.OutOfMemoryError: Java heap space at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1055) at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943) at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909) at javax.servlet.http.HttpServlet.service(HttpServlet.java:681)
另外开发同学提供了线索,在发生问题的时候在跑一个大批量的一次性 JOB,怀疑是不是这个 JOB 导致的,马上把 JOB 代码拉下来分析了下,JOB 做了分批处理,代码也没有发现什么问题。

虽然我们系统加了下面的 JVM 参数,但是由于容器部署的原因,这些文件在 pod 被 kill 掉之后没办法保留下来。

-XX:+HeapDumpOnOutOfMemoryError -XX:ErrorFile=/logs/oom_dump/xxx.log -XX:HeapDumpPath=/logs/oom_dump/xxx.hprof
这个现象是最近出现的,猜测是最近提交的代码导致的,于是去分析了最近提交的所有代码,很不幸的都没有发现问题。。。

在分析代码的过程中,该服务又无规律的出现了两次 OOM,只好联系运维同学优先给这个服务加了 EFS (Amazon 文件系统)等待下次出现能抓住这个问题。

刚挂载完 EFS,很幸运的就碰到了系统出现 OOM 的问题。

dump 出来的文件足有 4.8G,话不多说祭出 jvisualvm 进行分析,分析工具都被这个dump文件给搞挂了也报了个java.lang.OutOfMemoryError: Java heap space,加载成功之后就给出了导致OOM的线程。

找到了具体报错的代码行号,翻一下业务代码,竟然是一个查询数据库的count操作,这能有啥问题?

仔细看了下里面有个foreach​遍历userId​的操作,难道这个userId的数组非常大?

找到class按照大小排序,占用最多的是一个 byte 数组,有 1.07G,char 数组也有1.03G,byte 数组都是数字,直接查看 char 数组吧,点进去查看具体内容,果然是那条count语句,一条 SQL 1.03G 难以想象。。。

这个userId​的数据完全是外部传过来的,并没有做什么操作,从监控上看,这个入参有 64M,马上联系对应系统排查为啥会传这么多用户过来查询,经过一番排查确认他们有个bug,会把所有用户都发过来查询。。。到此问题排查清楚。

解决方案

对方系统控制传入userId​的数量,我们自己的系统也对userId做一个限制,问题排查过程比较困难,修改方案总是那么的简单。

别急,还有一个

看到这个问题,就想起之前我们还有一个同样类似的问题导致的故障。

也是突然收到很多告警,还有机器 down 机的告警,打开 CAT 监控看的时候,发现内存已经被打满了。

操作和上面的是一样的,拿到 dump 文件之后进行分析,不过这是一个漫长的过程,因为 down了好几台机器,最大的文件有12GB。

通过 MAT 分析 dump 文件发现有几个巨大的 String(熟悉的味道,熟悉的配方)。

接下来就是早具体的代码位置了,去查看了下日志,这台机器已经触发自我保护机制了,把代码的具体位置带了出来。

经过分析代码发现,代码中的逻辑是查询 TIDB(是有同步延迟的),发现在极端情况下会出现将用户表全部数据加载到内存中的现象。

于是找 DBA 拉取了对应时间段 TIDB 的慢查询,果然命中了。

总结

面对 OOM 问题如果代码不是有明显的问题,下面几个JVM参数相当有用,尤其是在容器化之后。

-XX:+HeapDumpOnOutOfMemoryError -XX:ErrorFile=/logs/oom_dump/xxx.log -XX:HeapDumpPath=/logs/oom_dump/xxx.hprof
另外提一个参数也很有用,正常来说如果程序出现 OOM 之后,就是有代码存在内存泄漏的风险,这个时候即使能对外提供服务,其实也是有风险的,可能造成更多的请求有问题,所以该参数非常有必要,可以让 K8S 快速的再拉起来一个实例。

-XX:+ExitOnOutOfMemoryError
另外,针对这两个非常类似的问题,对于 SQL 语句,如果监测到没有where​条件的全表查询应该默认增加一个合适的limit作为限制,防止这种问题拖垮整个系统。

有关完了!线上又发生 OOM 了!的更多相关文章

  1. ruby-on-rails - 启用 Rack::Deflater 时 ETag 发生变化 - 2

    在启用Rack::Deflater来gzip我的响应主体时偶然发现了一些奇怪的东西。也许我遗漏了一些东西,但启用此功能后,响应被压缩,但是资源的ETag在每个请求上都会发生变化。这会强制应用程序每次都响应,而不是发送304。这在没有启用Rack::Deflater的情况下有效,我已经验证页面源没有改变。我正在运行一个使用thin作为Web服务器的Rails应用程序。Gemfile.lockhttps://gist.github.com/2510816有没有什么方法可以让我从Rack中间件获得更多的输出,这样我就可以看到发生了什么?提前致谢。 最佳答案

  2. ruby - 当 attr_accessor 在类方法中时会发生什么? - 2

    所以我想到了这个,想知道当下面的一些事情完成后会发生什么。classTestdefself.abcattr_accessor:Johnendendobject=Test.newputs"beforecallingclassmethodabc:#{object.class.instance_methods(false)}"Test.abcputs"aftercallingclassmethodabc:#{object.class.instance_methods(false)}"这里我检查的是,getter和setter方法是否以这种方式创建。如果是这样,是那些实例方法或类方法。首先我创

  3. ruby - 当你有一个没有参数的 case 语句并且 when 子句是 lambda 时会发生什么? - 2

    这段代码没有像我预期的那样执行:casewhen->{false}then"why?"else"ThisiswhatIexpect"end#=>"why?"这也不是casewhen->(x){false}then"why?"else"ThisiswhatIexpect"end#=>"why?"第一个then子句在两种情况下都被执行,这意味着我提供给when子句的lambda没有被调用。我知道无论when子句的主题是什么,都应该调用大小写相等运算符===。我想知道当没有为case提供参数时,===的另一边会发生什么。我在想它可能是nil,但它不可能是:->{false}===nil#=>

  4. ruby-on-rails - 获取最近发生的星期三? - 2

    如何使用Ruby(和Rails,如果有相关的辅助方法)获取最近发生的星期三?最终需要实际日期(5/1/2013)。 最佳答案 time=Time.nowdays_to_go_back=(time.wday+4)%7last_wed=days_to_go_back.days.ago 关于ruby-on-rails-获取最近发生的星期三?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions

  5. ruby-on-rails - 发生异常时发送电子邮件不起作用,使用 exception_notification - 2

    我正在从rails2.3迁移到rails3.1,我试图在生成异常时发送电子邮件。我正在使用exception_notificationgem。我的其余电子邮件都在工作。但是异常邮件不会被解雇。以下是我的staging.rb文件中的设置。config.action_mailer.perform_deliveries=trueconfig.action_mailer.raise_delivery_errors=true下面是application.rb中的代码C::Application.config.middleware.useExceptionNotification::Rack,:e

  6. 一个非常明显的现象,正在发生——元宇宙正在被越来越多的人所推崇 - 2

      一个非常明显的现象,正在发生——元宇宙正在被越来越多的人所推崇,无论是科技巨头,还是资本巨头,几乎都是如此。同时,区块链则正在一点一点地回归理性与客观。对于区块链来讲,这是一个好现象。它告诉我们,人们对于区块链的狂热而激进的认识,正在被一步又一步的校正和纠偏。由此,区块链行业的发展,将会真正进入到一个全新的发展阶段。  同以往人们仅仅只是将区块链看成是一个概念,并以此来获取资本和流量不同。当人们对于区块链的认识变得深入,资本和流量反倒不再是区块链玩家们真正关心的问题。至少从当下情况来看,那些依然还在区块链行业里坚守的玩家们,更多地在坚持长期主义,更多地在寻求区块链与行业结合的正确的方式和方

  7. 基于SpringBoot的线上日志阅读器 - 2

    软件特点部署后能通过浏览器查看线上日志。支持Linux、Windows服务器。采用随机读取的方式,支持大文件的读取。支持实时打印新增的日志(类终端)。支持日志搜索。使用手册基本页面配置路径配置日志所在的目录,配置后按回车键生效,下拉框选择日志名称。选择日志后点击生效,即可加载日志。windows路径E:\java\project\log-view\logslinux路径/usr/local/XX历史模式历史模式下,不会读取新增的日志。针对历史文件可以分页读取,配置分页大小、跳转。历史模式下,支持根据关键词搜索。目前搜索引擎使用的是jdk自带类库,搜索速度相对较低,优点是比较简单。2G日志全文搜

  8. ruby - 在 Ruby 中声明 "private"/"protected"时实际发生了什么? - 2

    在Ruby类定义中声明private/protected时实际发生了什么?他们不是keywords,这意味着它们必须是方法调用,但我找不到它们的定义位置。它们似乎没有记录在案。声明private/protected方法(如下所示)的两种不同实现方式是否不同?(第二种方式显然是方法调用,但在第一种方式中并不那么明显。)classFooprivatedefi_am_private;enddefso_am_i;endendclassFoodefi_am_private;enddefso_am_i;endprivate:i_am_private,:so_am_iend

  9. ruby - 处理在 keyup 事件上发生的 javascript 弹出窗口 - 2

    我在HTML页面上有一个文本字段,用于检查您是否输入了1到365之间的值。如果用户输入了无效值,如非数字字符或不在范围内的值,它显示一个弹出窗口。我在watirwiki上看到有一个select_no_wait方法,用于在您从列表中选择无效值时关闭弹出窗口。处理键盘事件时出现的弹出窗口的好方法是什么?我是否需要按照select_no_wait方法的实现方式进行操作,或者我们是否可以启动一个不同的进程来消除调用set方法时可能出现的弹出窗口。带有Javascript验证函数的HTML文件示例如下:varnum=0functionvalidate(e){varcharPressed=Stri

  10. Ruby 捕获 NoMethodError 并从发生异常的地方继续执行 - 2

    在Ruby中,我想在另一个对象中捕获在一个对象上生成的NoMethodError,然后将一些值返回到引发异常的位置并继续执行。有没有现成的方法可以做到这一点?我想到的最好的是:classExceptionattr_accessor:continuationendclassOuterdefhelloputs"hello"endclassInnerdefworldputs"world"enddefmethod_missing(method,*args,&block)x=callccdo|cc|e=RuntimeError.exception(method)e.continuation=cc

随机推荐