图片验证码事故总结
事故描述
2016.10.22.09.30左右验证码无法显示,一直超时。初步调查发现2016.10.21日晚上10点已经出现此问题。加上往前几天内也发现了图片验证码服务变慢,偶尔超时的现象。但是服务器一直运行,未出现宕机。
事故原因
最终调查发现,是因为图形验证码服务器中缓存的10000张图片导致了内存过多占用(>90%),服务器实际的请求处理能力变弱。之前2个多月的运行没有暴露是因为图片验证码用量小。最近用量稍微增大后,就会出现此问题。
事故分析
一开始考虑是并发量超过处理能力导致服务器变慢,于是在本地进行了压测,发现增大线程确实可以增大QPS,然而忽略了JVM运行参数,因此结论并不准确。
后来统计Nginx访问量后发现,接口调用在正常值范围内,并不多。
- 查看线上日志后,发现了heap space的OOM错误,但是并不确定是请求拥塞还是其他原因。
- 在本地使用和线上相同的JVM参数后再进行压测,由于生成图片验证码池需要一定时间,所以长时间压测后发现出现相同错误。参数中配置了dump OOM。
- 使用MAT工具对dump文件进行分析。以下是MAT推荐分析结果。
- 继续查看Histogram:
-
发现BufferImage占用了近470M内存(默认启动参数里设置的是500M)。Int数组为像素数组。验证码池刚好占用了91%,并没有完全占满。所以具备一定服务能力,也是为什么之前运行2个月都没有暴露的原因。不过请求数量变多,按道理会触发消耗图片验证码,反而是释放内存才对,确实,所以上述验证码对象并没有到达1万,dump前肯定有一定的消耗,但是由于剩余内存已经完全不够当时请求处理需要的内存,所以请求处理线程无法创建,请求一直积压导致timeout。 请求线程需要内存,同样也会释放内存,每0.5秒触发消耗验证码,但若是0.5秒内进来的请求所需的内存大于省下的内存,就会造成OOM。由于验证码对象个数并没有超过10000,所以初步断定不是内存泄漏导致。但是还需要进一步排除。
-
在本地分别对单独循环生产验证码逻辑和生成到验证码池做测试,发现后者在内存一定的情况下,确实会引起OOM,前者并不会,排除内存泄漏。
-
把JVM的 Xmx参数改为800M后,长时间压测没有影响系统稳定性,佐证了结论。
事故处理
调整JVM内存大小或者减小图片验证码池大小。目前一方面稍微增大了JVM内存,并且合理的设置了图片验证码大小。
事故总结
之前只是通过最终网页展示的图片大小(<5KB)毛估了1万张图片的占用量(约50M),所以认为对内存需求并不大,事实是,每张图片对应的内存对象平均50KB左右(包括无法GC的直接或者间接强引用对象)。当时得出10000张的结论也没有经过科学计算,也是毛估。现在对其进行分析,得到了如下公式:
S = T3/T2 - T3/T1
其中:
S 为验证码池大小
T1 为生成到验证码池的周期
T2 为从验证码池消费验证码的周期
T3 为最大连续高峰服务时间(S值能保证这段时间内不用临时生成新的图片验证码而浪费CPU)
(时间单位均为s)
巧的是验证码池刚好接近500M占用,但是并不完全占用,所以小量运行没有运行就出错,而且后台生成到池子的速度也很慢,所以短期高并发压测也并不会出现异常。总之因为很多巧合,图片验证码系统侥幸运行至今。在设计系统的时候,还是要多一点科学的精打细算。