中心自研网关需要搭建一套全新的环境支持公司级项目,在搭建这套环境的时候架构组要求我们使用高配置机器,实际上我们之前使用低配置机器已经能应对来自线上环境的日均一个亿的请求流量,并且经过了长时间验证。更换高配机器之后我们需要调整JVM配置使其充分利用机器性能,最终给了4G的堆大小压测出了一个最佳性能。
最近准备灰度生产流量到新部署的环境,在切量之前业务在我们压测基础上进行了业务压测,业务压测仅才40tps就触发了网关自己的过载保护,过载保护的机制是从Netty线程池提交到业务线程池耗时超过1秒网关便会主动抛出异常实现过载保护,过载保护主要是为了应对流量突涨之后这种场景,但是Full GC过长也会导致这种问题。业务反馈问题到网关的时候,我们先询问了压测tps和业务接口耗时情况,查看流水核对tps和耗时都不可能触发网关的过载保护,第一时间推测是否GC出现了问题,进入监控系统发现Full GC每分钟高达几十次,且每3个小时会触发一次超过1秒Full GC,恰巧业务压测的时候碰到了超过1秒的Full GC节点。
每分钟几十次的Full GC一开始怀疑是否监控配置错误,所以获取生产环境有权限的账号之后进入机器查看GC信息如下
第一眼看过去Full GC确实非常频繁,第二眼发现老年代只有64个字节大小,傻眼了。反应过来回去查看代码提交记录,在之前进行压测的时候,最后一次提交代码配置了-Xms4096m -Xmx4096m却忘记同步将-XX:NewSize改为目标值,-XX:NewSize被设置为了-XX:NewSize=4096m,这样会导致老年代无法分配内存。
复习一下上面几个JVM配置的含义,-Xms配置初始化堆内存大小,Xmx配置最大可分配堆内存大小,-XX:NewSize配置新生代最小空间大小。
那么为什么当时第一时间为什么没有发现问题呢,首先进行上述JVM配置后老年代的大小不是0字节,而是依然能分配到64字节,这里没找到相关文档,猜测JVM源码设定老年代最小就是64个字节,或者有其它冷知识。然后这64个字节需要进行非常多次的年轻代GC才能触发被占满的情况,没有被占满之前是无法触发每分钟几十次的Full GC的,然后我们在进行JVM配置的时候会进行多次重启很难触发老年代被占满的条件,另外在第一轮压测的时候是在所有压测场景(包括连续进行24小时以上请求)完成之后架构组才通知我们换用高配机器,所以后续研发调整JVM配置的时候只是在JVM配置调整之后进行了短时间的高并发压测导致后续的压测场景其实是不全的所以没有发现问题。