背景
- 公司的系统中使用了standalone模式的flink,将队列中的数据处理后送入es等存储,同时使用了独立的保活程序维持flink job的运行,当flink job启动失败时,则会一直重试。
问题发现
- 某日上班后打开flink web-ui后发现无法正常访问,观测后台日志发现存在metaspace OOM的相关错误,使用arthas attach到进程上,用dashboard命令观测metaspace的大小,发现其使用率几近于100%;
![日志报错](https://i-blog.csdnimg.cn/blog_migrate/012df0db91099cccdfdb20827c08c367.png)
技术背景
- 阅读flink启动job的相关代码可知,在读入用户提交的jar以后,会生成一个独立的classloader来加载用户jar中的class 查看详解,其目的是使job的运行环境相对独立。
- flink同时也提供了一个配置项来修改这个行为,即 classloader.resolve-order
-![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/05c9d723281e324cc347ec2039fc8b6e.png)
问题解决过程
尝试1
- 猜测: 纯属偶然的现象,调大metaspace的size
- 执行: 修改flink-conf.yaml 中的jobmanager.memory.jvm-metaspace.size 属性 设置为340M
- 结果: 无效,次日仍然出现该问题
尝试2
- 猜测: 是否有不合理的代码逻辑,未释放的资源,导致gc无法释放class(后续证明,思路正确)
- 执行: 对job的代码逻辑进行自检,尝试修改可能存在的不合理之处
- 结果: 无效,问题仍然存在
尝试3
- 猜测: 有未释放的资源,且只有当特定的逻辑执行过后才会出现, 猜测是fastjson导致(heapdump中有大量的fastjson相关类)。
- 执行:对StandaloneSessionClusterEntrypoint 进程进行heapdump,观测到大量的fastjson对象存活,猜测可能是fastjson导致的无法释放,遂在main函数中提前抛出错误,多次提交job。
- 结果: 有效, metaspace的空间被正常回收了,但是无法确定是fastjson导致的该问题,但是定位到了问题代码的大致范围。
尝试4
- 猜测: 存在问题逻辑不执行的情况下,不会导致metaspace OOM,首先排除是fastjson导致
- 执行: 修改代码,在异常之前调用一次fastjson,再次反复提交
JSONObject o = JSON.parseObject("{}");
if (true) {
throw new RuntimeException();
}
尝试5
- 猜测: flink 的 userCodeClassloader没有被回收,导致其加载的类无法没正常gc。
- 执行: 分析heapdump中的信息,发现有若干实例驻留
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/c17c46a79adfa07f4d5a28f4e7ad4a26.png)
观测其数量,约等于提交的次数
择其任一进行观察, 发现有一个相对特殊的引用
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/9d6e9ab32b546ed6282754289a209fe2.png)
查看得知该引用为某一个线程,其名称为:common-pool-evictor-thread 搜索得知该线程由jedis创建,联想到代码中存在使用jedis进行数据库的操作,遂查看
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/0e73d450058ce8f4706bbbff7e6c3e82.png)
猜测在jedis pool模式下需要使用独立的线程定期清除无效的链接,遂改为使用单连接的模式
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/b48d3ba6dc85c2fb9f3d16d2cd2bcd9a.png)
重新打包,进行反复提交的测试,metaspace被正常回收,问题解决
结论
- flink job的jar中,尽可能减少引入三方jar, 将公共的jar放至libs文件夹中(官方建议),确保其不会被多次加载
- 避免使用timer, pool等开启线程的操作,如有需要,改为自定义source的方式。
- 尽可能保证执行job的classloader被正常的回收。