记一次线上故常处理

前言

作为一个开发,肯定会遇到线上问题,遇到线上故障快速的定位解决,是开发者一项必备的能力。工作中可能会遇到各种故障,这边主要记录下之前遇到的一个线上问题

问题描述

早上到办公室打开钉钉,发现线上应用的一个实例重启了…..

卧槽感觉情况不妙!!!

迅速打开sls看下线上日志情况,一看果然有问题

oom了….

自己登录系统大致浏览了下,系统比较正常,应该是重启之后,使用过程中没发生异常情况,感觉应该是偶发情况,准备看下代码慢慢定位下问题,再解决。

就在这时,系统的用户找过来了

于此同时发现,公司内部运维系统消息推送群,出现一大波线上实例重启的日志

感觉这个问题很严重呀…...

问题分析

再次到sls看下,竟然没有发现异常日志,这是怎么回事呢?

找运维问下吧

运维给出的回复是OOM了,但是我这边最近几次oom的日志,我这边不确定和上面的是不是同一个问题(上一次有明确的日志,这几次实例重启没有日志呀)

随后运维给出了线上服务的cpu、内存使用情况

OOMKilled 是因为实例内存超过了K8S给它分配的最高允许内存,所以K8S就把它oomkill掉

但是我还是怀疑和上面是同一个问题,我结合上面日志描述的异常看了下代码,然后我找到上面报错日志指向的接口,一看真的是要骂娘(当然自己代码写的也很烂),这代码写的呀…...

这种代码频繁访问接口,线程创建的多了,肯定会有很大的内存占用,K8s检测到了那就把实例杀掉了呀

用过CompletableFuture的同学就知道,默认情况下 CompletableFuture 会使用公共的 ForkJoinPool 线程池,这个线程池默认创建的线程数是 CPU 的核数(也可以通过 JVM option:-Djava.util.concurrent.ForkJoinPool.common.parallelism 来设置 ForkJoinPool 线程池的线程数)。如果所有 CompletableFuture 共享一个线程池,那么一旦有任务执行一些很慢的 I/O 操作,就会导致线程池中所有线程都阻塞在 I/O 操作上。这样等有大请求量过来,处理逻辑又很复杂,很多线程都在等待执行,慢慢拖垮了服务器。

这个异常问题本质原因是我们创建了太多的线程,而能创建的线程数是有限制的,导致了异常的发生。能创建的线程数的具体计算公式如下

(MaxProcessMemory - JVMMemory - ReservedOsMemory) / (ThreadStackSize) = Number of threads

  • MaxProcessMemory 指的是一个进程的最大内存
  • JVMMemory JVM内存
  • ReservedOsMemory 保留的操作系统内存
  • ThreadStackSize 线程栈的大小

在java语言里, 当你创建一个线程的时候,虚拟机会在JVM内存创建一个Thread对象同时创建一个操作系统线程,而这个系统线程的内存用的不是JVMMemory,而是系统中剩下的内存(MaxProcessMemory - JVMMemory - ReservedOsMemory)。由公式得出结论:你给JVM内存越多,那么你能创建的线程越少,越容易发生 java.lang.OutOfMemoryError: unable to create new native thread

问题复现

那么问题基本可以确定了,根据接口找到对应的页面,然后在预发环境频繁浏览那个页面,然后我让运维到宿主机和容器看下具体的情况(看看这线程数量涨的…..),果然又发现预发的实例也重启了!

问题解决

问题定位了清楚就好解决了呀

刚才的代码对应的地方改成自定义线程池就好了

//可以指定线程池  
static CompletableFuture<Void> 
  runAsync(Runnable runnable, Executor executor)

改完上线,线上立马稳了…...

问题总结

  • 线程属于宝贵资源,使用的时候尽量使用线程池管理,在使用线程池的时候,要注意尽量使用自定义线程池,明确线程池中的各个参数
  • codereview很重要呀!!!

码字不易,觉得还不错可以点个赞

原文地址

http://cbaj.gitee.io/blog/2020/06/19/%E8%AE%B0%E4%B8%80%E6%AC%A1%E7%BA%BF%E4%B8%8A%E6%95%85%E5%B8%B8%E5%A4%84%E7%90%86/#more

posted @ 2020-08-19 08:38  happyfresher  阅读(154)  评论(0编辑  收藏  举报