How to calculate picture
How to calculate picture
某天当我在沉浸式摸鱼的时候,突然发现线上发出了告警:OutOfMemoryError
。
顿时这鱼也不敢摸了,兴趣顿时起来了。PS:不是自己的业务,但是本着要有项目own的精神。我就开始排查为什么会有OOM的告警。
这时候又要大家开始回答八股文了,线上哪里地方会发生OOM?
回答:略略略~~,老板需要你自己去看下这八股文了。
我们通过上面的异常告警,可以直接知道是堆空间的OOM,你问我怎么知道的?
老板这时候看英文呢?java.lang.OutOfMemoryError: Java heap space
接下来我跟老板先介绍下本次的业务,获取颜色主色业务
,并且在这里插一个眼,本次是因为只上传了一张1.5M的图片,导致线上堆空间不足。
这里先给出图片,图片链接:https://img.noahpan.cn/img-2022/202208191537393.png
我写个最小代码原型。
|
接着又开始新的的八股文了,当线上OOM的时候,你会怎么做?
希望老板可以通过我的步骤,可以总结直接的方法论。
Online monitoring
发生异常的时候,我们应该第一时间看监控。这时候可以从监控大盘看出个大概。
这时候我们要牢记,我们是来解决:java.lang.OutOfMemoryError: Java heap space
从监控我们可以看到堆的空间,在某些时间短的时间,堆的空间基本都消耗完了。
此时我们应该优先看服务是否crash,然后导致重启。
这时候我们JVM通常都配置了-XX:ErrorFile=/data/java/logs/hs_err_%p.log
当进程被crash了,会收集被crash的一些关键信息方便我们分析问题。
说到我分析问题到这一步,我们容器没有被crash重启。
这里插个眼,关于k8s健康检查不通过,重启pod的配置。我们后面特定说下,因为这也是很重要的!!
这时候就可能有老板要问了,为什么heap OOM了,为什么没有重启容器呢?
这里读者如果你有这样的想法就错了。
OOM的发生表示了此刻JVM堆内存告罄,不能分配出更多的资源,或者gc回收效率不可观。
一个线程的OOM,在一定程度的并发下,若此时其他线程也需要申请堆内存,那么其他线程也会因为申请不到内存而OOM,连锁反应才导致整个JVM的退出。
我们发现问题后第一时间进行了扩容,从3G扩容到了8G。但是还是会heap OOM。
这时候就要开始看代码了,也到了经典八股文了,heap OOM的原因
- 代码中可能存在大对象分配
- 可能存在内存泄露,导致在多次GC之后,还是无法找到一块足够大的内存容纳当前对象
Code Review
通过上面的分析,我们已经扩容到8G还是会有OOM。通过上面的监控和分析,我们可以知道我们写的代码有问题。
这时候进入我们大家喜闻乐见的源码分析环节。
在分析源码之前,我要在这里跟大家特地介绍下上传的资源。
图片链接:https://img.noahpan.cn/img-2022/202208191537393.png
- 我们从第一点可以知道,这张图片只有1.7MB的大小
- 这张图片的重点是第二点,图片的尺寸:16667✖️16667
上面已经贴了最小原型代码,我们现在要找出大对象的代码到底是哪个对象。
这时候我们就要科普2种查看占用内存大小的工具了。
/** |
其中Instrumentation的方式可能不够精准,因为无法统计嵌套子对象所暂用的内存。推荐使用第一种方式。
这时候我们本地跑,is是文件的流,bi是图片的像素流。PS:这里我先注释颜色主色调的代码。
这个我们的运行结果,我们很惊讶的发现,图片的像素流bi居然占了1GB的堆空间,我们从JVM看和日志打印一直。
这里我们先引出这个问题,为什么我获取图片的宽/高,需要消耗1G的堆空间。
并且我看很多网上的所谓”教程”,也是通过上面的代码获取图片的宽高。
这里就不点击到里面去了,但是里面都是坑。这里提示一个思路,我们都知道每个文件都是自己的魔术值,表示这是什么文件,比如.java,.txt等。这些被称为元信息的数值。再比如文件的大小,图片的长宽高这些,在文件的元信息已经存在的。
我们只需要读取头部信息就可以知道了,就不需要再读区整个图片像素流了。怎么做呢?show code环节。
|
其实为什么这样就可以获取到宽高呢。其实看源码我们就可以知道了。
你调用ImageIO#read
,图片的宽高也是这样set进去的。
最后我们比较下这两种方式获取图片宽高的性能差距。
性能的差距不是一点点。
最后我们说下,我们已经知道了GC的原因了。因为读取的文件只有1MB的大小,但是图片的像素大小是1.6w✖️1.6w的大小。
当我们需要计算图片主色调的时候,需要根据每个像素计算主色调。
所以我们的解决方案:
- 使用Apollo配置值,限制图片的宽高的乘积最大值。
- 使用优化后的方案来获取图片宽高。
- 图片的宽高乘积没有超过Apollo的限制值,则计算主色调。
好了,到此我们fix了我们的OOM问题。
下文私带一些干货。K8s Liveness
K8s Liveness
Liveness: http-get http://:http-metrics/actuator/health delay=300s timeout=1s period=5s #success=1 #failure=5 |
上面的两个配置是k8s健康检查的配置,Liveness存活探针,Readiness就绪探针。
下面重点解析下他是怎么配置出来的,以及他们的含义。
kubectl get deploy deploy-name -n namespace-xx -o json |
Liveness的配置生成,是通过delpoy中的livenessProbe属性生成的。
我们可以重点解读下配置的含义,如下:
- httpGet:对应HTTPGetAction对象,属性包括:host、httpHeaders、path、port、scheme
- initialDelaySeconds:容器启动后开始探测之前需要等多少秒,如应用启动一般30s的话,就设置为 30s
- periodSeconds:执行探测的频率(多少秒执行一次)。默认为10秒。最小值为1。
- successThreshold:探针失败后,最少连续成功多少次才视为成功。默认值为1。最小值为1。
- failureThreshold:最少连续多少次失败才视为失败。默认值为3。最小值为1。
- timeoutSeconds:探测的超时时间,默认 1s,最小 1s
我们整体解读下:在程序启动延迟300s后,开始执行存活探针任务,每隔5s执行一次,发起http请求,超市时间为1s,如果连续失败超过5次,则重启容器。也就意味着25s内都健康检查失败就重启容器,有一次成功了则重置。