记一次线上故常处理
前言
作为一个开发,肯定会遇到线上问题,遇到线上故障快速的定位解决,是开发者一项必备的能力。工作中可能会遇到各种故障,这边主要记录下之前遇到的一个线上问题
问题描述
早上到办公室打开钉钉,发现线上应用的一个实例重启了…..
卧槽感觉情况不妙!!!
迅速打开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很重要呀!!!
码字不易,觉得还不错可以点个赞
原文地址