1、问题背景
我们的服务遇到线上环境 FGC 频繁时,我们是如何解决的。
2、遇到的问题及解决方案
2.1、pinpoint 打点存在大批量300ms以上
- 排查方向
- 检查db是否存在慢sql
- 定位调用链中具体接口
- pinpoint inspector查看是否存在fullGC
- 应用dubbo线程池是否耗尽(默认200个线程)
- 打点存在大批量红点
- 排查方向:确认红点出现源头,判断自己调用其他的业务服务是否存在超时)
- 重启应用
2.2、应用 cpu 突然升高
- 排查方向:
- 应用是否存在fullGC
- 检查应用流量是否突增
- 是否存在不合理调用(可参考监控大盘)
- 1、阈值标准:
- pinpoint最近5min出现大批量超过5s以上的打点
- 应用cpu 90%以上持续3min
- 应用内存90%以上持续3min(瞬间暴增除外)
- 收到上游最多两个以上业务方反馈
- 2、解决措施:
- 打印线程栈:jstack l <路径文件名.bin>
- dump jvm堆 jmap dump:format =路径文件名.bin
- 重启应用:运维执行命令/jenkins
- 下游业务服务方响应超时
- sentinel consumer限流
- 如果存在恶意攻击是,联系安全部门拦截
- 如果单业务流量大,针对url或dubbo进行限流
- 升级应用机器配置(一般是加机器)
2.3、内存升的很快
- 排查方向:
- 1、排查是否存在新版本发布的影响;
- 2、查询下调用频率较高的接口是否存在内存缓存的使用;
- guava cache 和 caffeine cache
- 3、查询下日志确定应用是否存在大批量的异常抛出
- 2、解决措施:
- 打印线程栈:jstack l <路径文件名.bin>
- dump jvm堆 jmap dump:format =路径文件名.bin
- 重启应用:运维执行命令/jenkins
- 下游业务服务方响应超时
- sentinel consumer限流
- 如果存在恶意攻击是,联系安全部门拦截
- 如果单业务流量大,针对url或dubbo进行限流
- 升级应用机器配置(一般是加机器)
2.4、数据库 cpu持续90%以上10分钟
- ops是否明显增高(是否爬虫等异常流量)
- 是否存在大批量数据查询(索引/动态sql)
- 阈值标准:
- 数据库cpu100%持续2min且没有下降趋势
- 链接数一直增加没有下降的趋势,iops打满且没有下降的趋势
- 内存使用率80%以上持续5min,
- 解决措施
- 定向业务限流
- 运维kill慢sql
- 运维kill锁进程
- 重启应用
- 升级配置(但是需要考虑升级时间的影响问题)
2.5、连接数线性增高且长时间不下
- 查看是否存在慢sql
- 查看应用的数据库连接池配置
- 查看数据库中是否存在死锁
show engine innodb status;
- 解决措施
- 定向业务限流
- 运维kill慢sql
- 运维kill锁进程重启应用
- 升级配置(但是需要考虑升级时间的影响问题)
2.6、iops飙高
- ddl操作
- 是否针对大表新增索引
- 是否存在变更大表字段,业务上是否存在大批量操作(写入或读取)
- 解决措施:
- 定向业务限流
- 运维kill慢sql
- 运维kill锁进程
- 重启应用/升级配置(但是需要考虑升级时间的影响问题)
2.7 内存升高
- 确认下数据库连接数是否异常(连接也占用内存)
- 解决措施:
- 定向业务限流
- 运维kill慢sql
- 运维kill锁进程
- 重启应用/升级配置(但是需要考虑升级时间的影响问题)
2.8、中间件 MQ 消息堆积
- 基本不需要处理
2.9 canal delay延时高
- 排查方向:
- 大批量数据操作,是否存在和其他业务共用canal,受其他业务影响
- 阈值标准:garafa canal delay 延迟超过用户不可接受的范围
- 解决措施:业务评估无影响情况下联系运维重置消费位点
2.10、线上环境 FGC 频繁
1、明确分工
这个步骤应该是在事前就已经明确好的,类似于一个故障处理流程规范,这边需要明确出3个主要角色:
1)总指挥
- 负责故障处理整体指挥的人,例如快速拉起线上/线下会议、通知到系统核心同学,对各同学工作进行分工等等。
- 该角色一般由主管来担任,或者主管指定的另一个同学。
2)故障恢复小组
- 负责故障恢复的同学,该部分同学的任务就是让系统以最快的速度恢复正常。
3)故障定位小组
- 负责故障定位的同学,该部分同学的任务就是定位出问题的根本原因。
- 大部分同学的回答可能会集中在故障定位中,但其实另外两个角色也是非常重要的,在线上出现问题时,止损永远是放在第一位,其次才是定位。
2、故障恢复
2.1、线上发布导致
- 查看故障服务近期是否存在上线操作,如果有的话优先采取回滚解决。
- 分析:该场景应该是最常见的场景,就是刚上的代码存在bug。
2.2、非线上发布导致
1)小面积故障
- 如果故障机器只是发布在少量机器,例如:某机台机器、某个机房机器、某个地域的机器、某个泳道的机器等等。
- 此时优先采取禁用服务器解决,禁用前先评估禁用这批机器后是否存在服务容量问题,如果是则先扩容。
- 分析:该场景一般是代码存在bug,但是该场景的使用频率很低,所以上线时没暴露出来,只是偶尔会被触发到。也是出现概率比较大的场景。
2)大面积故障
- 如果故障机器不存在共性,这个概率说实话非常非常小,但是如果碰到了,故障恢复小组能做的事情就比较有限了。
- 因为这个场景大概率是代码存在bug,同时发布时没有被大量触发,而是到现在才被大量触发,需要定位才能根本解决。
- 此时,故障恢复小组能做的就是使用扩容、重启、限流等手段,来尽量维持服务的运转,同时给故障定位争取时间。
- 分析:之所以说该场景概率小是因为,分布在大量机器上的故障通常是使用频率高的场景,这部分场景一般在服务发布时就会暴露出问题,而不是发布很久之后。但是这个场景也是确实存在的,但是会比较少。
3、故障定位
故障定位主要是用于应对故障恢复中的最后一个场景,也是将故障恢复和故障定位分成两个小组并行执行的主要原因。
3.1、FGC 能正常回收到内存
- 通过监控或GC日志,我们能看到每次FGC后都能正常回收到内存,但是内存很快又被占满,导致又出现FGC,从而出现FGC频繁。
这个场景通常可能由于两个原因导致:
- 1)Eden 区配置太小,导致大量对象直接进入老年代,从而导致老年代快速被占满。
- 2)业务量较大,特别是在业务高峰期。而当前的服务器配置已经无法满足当前的业务量。
对于这两个原因,代码本身可能没有大问题,优先采取扩容机器,降低单台服务器压力即可解决。
后续则需要重新评估当前服务器的配置是否满足当前的业务量,JVM参数是否存在优化空间等等。
- 分析:该场景是由于业务量增大没有及时评估或者JVM参数配置存在问题导致的,整体来说,出现的概率较小。
3.2、FGC 不能正常回收到内存
- 通过监控或GC日志,我们能看到每次FGC后只能回收到很小的空间,甚至回收不到空间,从而出现FGC频繁。
- 对于这个场景,二话不说,先dump下内存,使用工具看下当前的内存泄漏情况、内存分布情况等等,查看是哪个对象占用了大量内存。具体工具常见的例如 Eclipse MAT,如果公司内部有封装的工具就更好了。
- 通常查看完内存占用情况,大概率会看到个别对象占用了大量的内存,结合其引用链定位出在代码中的位置。
接着就是根据代码分析问题的严重程度:
- 如果是小概率触发的场景,大部分请求其实正常,则可以先禁用问题机器,后续上线修复即可。
- 如果是大概率触发的场景,则查看是否存在降级开关,如果有则优先降级解决,如果没有则只能修改代码,走紧急修复流程。
总结
- 整体的解决流程其实还是比较简单的,没有太复杂的东西。大多数情况下,用好扩容、禁用、重启这几个常见手段即可解决大部分问题。
- 个人经验而言,线上频繁FGC问题90%以上是由于开发同学代码存在问题导致的,例如常见的存在死循环、开无界队列等等。以上的问题在dump后,很容易就能定位到根本原因。
- 而如果遇到诸如依赖的第三方jar存在bug导致的问题,例如Guava、Log4j,这种场景一般是在极端情况下出才会出现,所以一般只会出现在少数机器,禁用即可临时解决,然后后续再慢慢排查。