一、前提
最近公司在搞活动,预计会来一波业务高峰,流量会达到平时的6倍,所以各个系统都在优化、压测。
我所负责的系统为支付系统,也就是整个链路的最下游,目前支付系统支付接口TPS1600,10个节点,单节点TPS160,压测目标是3000+,这差了将近2倍,基于此背景下开始各种优化
二、优化
先看下支付流程,很简单:
代码层面没有复杂逻辑,整个链路比较简单,优化的余地很小,
主要的优化点有:
1、调用外部接口时,去掉了一些无用字段,减少网络开销
2、对redis缓存做了本地二级缓存(读取路由配置)
3、入库时有两个字段填充依赖于查询外部接口,已改为异步填充(支付完成后填充)
第一轮压测:
代码逻辑上也没有优化的余地了,于是又线上压测了一次,TPS从1600增加到了1700,效果很不明显
第二轮压测:
于是乎开启了王能大法:加机器,从10个节点扩到15个节点,又压测了一轮,TPS达到了2200,离目标又近了一步,随后有加了5个节点,目前已达到了20个节点,压测结果将然降了50TPS!分析了下这应该是数据库、中间件或者是外部接口有性能瓶颈。
1、检查了下数据库连接池,默认是20,以前是10个节点也就是200个链接,现在是20个节点400个链接,可能链接数太多导致,数据库使用的是polardb(阿里云的mysql plus版),官方建议是cpu核数*2+1,所以给每个节点的最大数据库连接池设置为10
2、压测期间项目大量报tomcat等待超时,获取不到链接,经排查支付项目所以来的网关项目tomcat配置有问题,其max-thread竟然配置的只有100,导致支付项目同步调用网关项目被hang住,当超过100线程时,支付项目的线程会在网关项目的线程池中等待,由于网关项目是请求三方的,同步请求时间较长,导致支付项目等待的线程大部分超时报错。将max-thread调制到1000后,tps提升了25%。期间也调整过800和1200,发现1000性能最佳,机器的配置是4核8G物理机。
第三轮压测:
基于第二轮压测后的两个改动点,有压测了一次,TPS达到了2800(果然是外部系统tomcat线程数太小导致同步请求被hang住)。
压测老师反馈压测经常会有卡顿现象,这一看就是fullgc进行了stw,打开监控平台看了下,压测期间竟然没有full gc产生,(而且近3天内也没有发生过full gc),到时young gc非常频繁,(图中波峰为压测期间)
经查询,younggc全程stw,这就有点坑了,其实支付项目属于young gc型,因为每一笔支付请求过后,整个链路创建的对象基本上都会回收,基本上都是方法内变量操作,没有大对象,基于这个前提下,我们把年轻代的空间从1G扩到2G,老年代从3G减少到2G,方法区从128M扩到256M
参数如下:
-server -Xms4608M
-Xmx4608M
-Xmn2048M
-XX:PermSize=256M
-XX:MaxPermSize=256M
-XX:TargetSurvivorRatio=60
还有一个坑点,之前的JVM参数没有设置-Xmn,导致堆内存频繁收缩,最坑的是设置了-XX:SurvivorRatio=1
,这个参数是年轻代Eden区与两个Survivor区的比值,设置为1的话表示1:1,如果不设默认是8:1:1(Eden:from:to),赶紧将其去掉
第四轮压测:
基于第三轮压测后的JVM参数调优,又进行了第四轮压测,性能竟提升了15%左右,young gc明显减少,同时TPS达到了约3300,性能压测已过,来一张图看的更明显:
22:00点为JVM参数调整前,可以发现young gc很频繁,22:00-22:40之间设置新的JVM参数并进行压测,可以从22:40之后跟22:00之前的曲线对比,JVM参数调整后 young gc明显少了很多。在看下堆内存使用情况:
22:40后内存收缩次数明显减少。之前没有制定最小内存时(-xms),最大内存没有生效,当制定了-xms=-xmx时(最小内存=最大内存)时,最大内存才达到了使用效率
第五轮压测:
疏忽了一点,也就是日志打印,这点很关键,目前项目采用的是logback同步打印日志,级别为info,从监控上看,压测期间磁盘写入有大量抖动,日志写盘造成的,在从elk中分析日志,发现部分请求写日志竟高达3s,而且时间点与磁盘抖动时间点吻合,立刻将日志打印改成了异步,看了下logback异步打印的原理是将日志放到了ArrayBlockQueue里,启了一个异步线程从队列中取日志,往磁盘里刷,而ArrayBlockQueue默认的大小是256,这压测场景下如果刷盘的速度比写入的满,队列很快就满了,对其大小改成了8096,如果超过这个数,则降级处理(丢弃),同时将系统的日志打印级别改成error,降低日志数量,以及降低日志丢弃的风险。配置如下:
<appender name="RollingFile_Async" class="ch.qos.logback.classic.AsyncAppender" neverBlock="true">
<queueSize>8096</queueSize>
<neverBlock>true</neverBlock>
<appender-ref ref="RollingFileAppender"/>
</appender>
<root level="info">
<appender-ref ref="RollingFile_Async"/>
</root>
经压测了,TPS提高了25%,TPS达到了4200,可见并发场景下同步写日志被阻塞主线程有多么可怕
三、总结
最终接口TPS达到了4200,同时对支付入口做了限流、降级操作保护系统