当前位置: 首页 > 科技观察

被系统性能逼疯了吗?你需要这个性能优化策略

时间:2023-03-21 11:05:58 科技观察

作者介绍了供职于全球五大银行的刘迪伟。负责公司网上银行业务系统的设计与交付,擅长并持续专注于Java性能优化、DevOps等领域。XX银行网上银行系统是一个全新的企业业务渠道系统。经过两年的建设,将逐步对外提供服务。系统整合了多个原有的公共通道系统,并发量为之前系统的总和,吞吐量需求会大幅增加。为了让大众客户在使用系统时获得更快的响应时间体验,项目组对系统进行了持续的性能测试和优化。在这个过程中,形成了一套新系统性能测试和优化的方法论。方法论包括测试环境准备、测试功能优先级、性能优化原则、常用性能指标和工具、工具使用方法、常见性能问题成因和优化方法,以及典型案例讨论和进一步优化方法。由于系统已经开发完成,本着性能优化修改的范围尽可能小,不引入更多问题的原则,本文只讨论系统局部优化的方法,以及提升性能的设计系统初始设计期间不在讨论范围之内。我们使用Linux操作系统,loadrunner作为压测工具,中间件版本有IHS8.5.5.9、WAS8.5、IBMJDK1.7(IBMJ9VM)、DB2V10、Redis3.2.3。1、应用系统性能评价指标响应时间:尽快向用户返回响应,反映系统处理请求的速度;ThroughputTPS:每秒完成的事务数,反映系统处理能力;并发性:当业务请求高并发时,系统是否能稳定运行;可扩展性:当单机处理能力不足时,系统是否可以横向扩展。TPS=并发用户数/响应时间二、常用性能监控指标及工具1、操作系统监控指标及工具主要监控指标:CPU、系统CPU、内存、磁盘IO、网络IO、请求耗时。常用命令:top–H–ppid:cpu负载监控,实时查看高cpu占用线程;vmstat:系统负载监控;pidstat:cpu优惠上下文切换监控,监控锁竞争;iostat:磁盘利用率监控;nmon:监控cpu、内存、io使用情况。/nmon-f-t-s2-c100每2秒收集一次,共收集100次;netstat-anp|grepportorIP|grepESTABLISHED|wc-l:服务器连接数监控。2.JVM监控指标及工具Jconsole,监控cpu,内存垃圾回收。JVM启动参数中添加:-Dcom.sun.management.jmxremote.port=1088-Dcom.sun.management.jmxremote.authenticate=false-Dcom.sun.management.jmxremote.ssl=falsejvisualvm、cpu采样、java方法消耗时间分析、jvm线程栈快照、cpu监控、内存垃圾回收。CPU采样:线程快照:jca457.jar,ibmjavacore线程快照分析:ga456.jar,ibmjvm垃圾收集gc分析:ha456.jar,ibmjvm内存,heapdump分析,内存溢出时使用;TProfiler,cpu采样,java方法cpu消耗时间分析,cpu高响应慢时使用;JProfiler,cpu采样,java方法cpu耗时分析,cpu高,响应慢时使用;OracleDeveloperStudio12.6-linux-x86,cpu采样,java方法cpu耗时分析,cpuHigh,响应慢时使用。三、性能测试的前提条件1、数据库表数据量的精度必须与生产数据量保持一致,至少一个数量级。数据分布应尽可能均匀。2.测试环境与生产的一致性测试环境机器配置、参数、代码尽可能与生产一致(数据库服务器硬件除外)。3、合理确定并发数系统并发数的估算算法有很多:平均并发数:C=nL/T(n为考察时间内用户登录数,L为用户平均在线时间,T为检验值(时长)峰值并发用户数:C'=C+3*根数C总用户数/统计时间*影响因素:网银用户数为100万,根据2/8原则,80%的用户在上午9:00到11:00之间,下午2点到4点登录系统,每次登录耗时1到1.5秒。那么并发用户数为:1000000*0.8/4/3600*1.5=82.5,1000000*0.8/4/3600*1=55根据系统用户数计算:并发用户数=系统***在线用户8%到12%的数量按TPS估算:C=(ThinkTime+1)*TPS,网银用户的思考时间为10s,C=(10+1)*3=33.4.预估各函数的交易量,确定压测函数的优先级。根据交易量从大到小对功能进行排序,排名靠前的优先进行压测。5.设置性能问题的识别标准,比如响应时间超过3s,TPS低于10,服务器cpu使用率超过70%,jvm堆内存使用率100%,垃圾回收频繁,网络IO或磁盘IO达到瓶颈等.可能是性能问题。四、性能优化的总体思路1、寻找性能瓶颈性能瓶颈的定义:导致系统TPS低、响应时间长、资源(CPU、内存、网络)占用高等问题的关键程序模块。提高这个程序模块的性能可以大大提高性能。常见的性能瓶颈原因包括:慢数据库查询SQL、日志打印、XML大报文解析和格式转换、业务逻辑复杂、锁竞争等。2.如何找到性能瓶颈使用LoadRunner在各个接口添加事务,记录它的响应时间和TPS,最慢的接口往往是瓶颈;分析慢事务日志以查看哪个操作花费的时间最长;分析数据库快照,查看是否有执行缓慢或全表扫描的SQL;通过Javacore,查看线程正在执行的代码是大部分阻塞在IO上,还是大部分在执行计算。针对不同的问题使用不同的分析工具。详情见下一章。3、针对性能瓶颈进行合理优化性能优化原则:先优化瓶颈问题;解决方案简单,尽量不引入更多的复杂性,尽量不降低业务体验;满足系统性能要求即可,不要引入新的bug。五、常见问题及优化方法1、SQL执行时间长问题现象:系统响应时间长,数据库cpu高。问题原因:全表扫描、索引低效、排序溢出。解决方法:通过DB2数据库快照查看执行时间较长的SQL,查看SQL执行计划,对成本较高的SQL添加适当的索引。大表的所有SQL执行计划的成本要求小于100,超过100的SQL需要审核。对于排序溢出,可以参考数据中心规范设置排序堆的大小。在规范中,排序堆设置为AUTOMATIC。制定数据库表清理策略。根据数据的生命周期要求,定期清理备份流水数据,不再长期保存。定期对整个数据库的表结构进行reorg和runstats操作,提高索引效率。排错方法:获取数据库快照:db2getsnapshotforalloncorpdb>gswyzfzzshpl1207.log从快照中提取慢SQL,Toad查看SQL执行计划db2查看SQL执行计划的命令方式:db2expln-dcorpdb-t-g-q《SQL语句》执行计划查看方法:从上到下查看cost***的分支,找到没有取索引或者索引使用不当的表-dbcorpdb-evmDB2DETAILDEADLOCK>dlock.txt产生死锁监控文件,快照或监控文件存在死锁。问题原因:全表扫描、大事务、更新同表记录的SQL执行顺序等。解决方法:缩短事务路径长度,避免全表扫描。如果一定要有大事务,更新同一张表的SQL执行顺序是一致的,坚决避免全表扫描。网银系统命令发送功能全表扫描+全局大事务,导致数据库死锁。排查方法:根据死锁监控文件dlock.txt,找到导致死锁的SQL和SQL持有的锁,分析SQL可能存在的问题。3、线程在日志记录中被阻塞。现象:系统响应时间长,通过javacore打印日志阻塞了很多线程。问题原因:log4j1.x版本比较低,性能较差;多次输出大包日志。解决方法:减少无效日志,删除无用日志,减少大日志输出。将log4j组件升级为log4j2,参考log4j2官方文档,配置合理的日志缓冲区,使用高效的Appender,如RollingRandomAccessFile。但是log4j2仍然使用同步日志而不是异步日志。由于网银系统日志量大,异步日志队列很快就会满。如果单个日志消息较大,可能会造成内存溢出,不适合使用异步日志。如果日志量较小(压力测试时产生日志的速度低于写入文件的速度),可以使用异步日志来大幅提升性能。如果日志量很大,不建议使用异步日志。故障排除方法:在JVM启动参数中加入-XX:+HeapDumpOnCtrlBreak。压测时kill-3pid杀掉几个javacore,用jca457.jar工具打开分析。推荐使用这个工具,因为它可以统计所有的线程状态,并生成饼图方便查看。压测时,使用jvisualvm获取jvm快照,分析线程栈。4、多线程并发问题现象:并发压力测试数量合理,系统出现逻辑错误,事务失败,或异常报错。发现对象中的变量被异常修改。问题原因:系统中全局对象中的类变量或全局对象被多线程修改。解决方法:查看系统中所有持有全局对象或类变量的代码,查看其全局变量是否可能被多线程并行修改。修改方法:在方法中将全局变量转化为局部变量;同步全局变量,例如同步代码块或java.util.concurrent锁。排查方法:并发问题很可能是全局变量或对象引起的。准确识别全局变量,通过阅读代码发现问题。建议应用将所有可能存放全局对象的代码整理出来统一管控,或者将所有全局对象放到一个类中,方便管理。5、文件打开过多现象:事务并发压力测试合理次数失败,或者后台日志报错:Tomanyopenfiles。问题原因:读取配置文件或业务数据文件后,文件流没有关闭;/etc/security/limits.conf中配置的最大打开文件数太小。解决方法:使用lsof–ppid命令查看进程打开的文件。如果大多数文件都是同一类型,则可能无法关闭文件流。找到打开文件和关闭文件流的代码。如果未关闭的文件流没有问题,而业务本身需要处理大量的文件,修改/etc/security/limits.conf文件如下:*hardnproc10240*softnproc102406内存泄漏现象:JVM内存消耗后台日志抛出OutOfMemeryError异常;问题原因:内存溢出问题的可能原因有很多种。可能是全局的List、Map等对象不断扩容,或者程序不小心将大量数据读入内存;可能是循环操作导致的,也可能是后台线程定时触发加载数据导致的。解决方法:对于ibmjdk纯java应用,在jvm启动时设置-XX:+HeapDumpOnOutOfMemoryError参数,内存溢出时会生成heapdump文件。使用ha456.jar工具打开heapdump文件,分析大对象是如何生成的。当然,heapdump中的对象类型可能只是一个List结构,并不清楚是哪个业务代码创建了这个对象。这时候需要分析所有的全局对象,列出可疑的List或Map对象,排查溢出原因。初始化和修改全局对象和引用时要小心。建议应用将可能存放全局对象的代码全部整理出来,统一管控。7、频繁的JVM垃圾回收现象:top-H-ppid命令查看,GCSlave线程CPU占用率一直排在前三,Jconsole查看jvm内存占用高,垃圾回收频繁。使用ga456.jar分析gc日志,查看gc的频率和时长。打印gc日志的方法:在jvm启动参数中添加-verbose:gc-Xverbosegclog:/usr/ebank/logs/cobp/gcdetail.log问题原因:高并发下,内存对象较多,jvm堆内存不够。解决方法:扩大堆内存大小-Xmx2048m-Xms2048m。8、高CPU问题现象:50并发压测,监控工具显示bp,前端CPU占用90%以上。问题原因:业务处理中有大量CPU计算操作。解决方案:用更高效的算法和数据结构替换原来耗CPU的代码,或者用新的设计绕过瓶颈代码,比如找数据的逻辑,可以把List改成Map,把空间换成时间;例如,使用Json上报XML消息被XML消息替代,提高传输、解析、打印日志的效率。导致CPU计算资源消耗高的代码:报文格式转换、加解密、正则表达式、低效循环、低效正则表达式。排查方法:压测时,使用jvisualvm工具远程连接应用,点击Sampler→CPU,点击Snapshot生成线程快照。采样一段时间后,采样器会显示每个方法占用cpu时间,可以针对占用cpu时间多的方法进行优化。使用tprofiler、jprofiler、OracleDeveloperStudio12.6-linux-x86工具分析耗CPU时间长的方法,以上工具的分析结果可能有些出入。优化在CPU上计算时间最长的方法。9、批处理时间长,一条一条插入数据库慢现象:更新或插入大量数据(10万条以上)到数据库需要很长时间。问题原因:在批量处理数据的时候,如果一个一个地更新数据库,会产生大量的网络io和磁盘io,时间长,数据库资源消耗大。解决方案:使用java提供的batchUpdate方法批量更新数据库,每1000次commit一次,可以大大提高数据更新的效率。将单线程改为线程池,并行处理,充分利用多核CPU,通过数据库或其他同步锁控制并行度;增加缓冲池,降低数据库或磁盘IO访问频率。10、数据库CPU高问题现象:后台命令满负荷工作时,数据库CPU高。问题原因:后台命令发送线程每次对全查询结果进行排序,结果集很大,然后取一条记录;索引区分度不高,满载执行时;查询频率很高;压力测试表明,并行发送命令的后台线程越多,数据库CPU越高,效率越低。解决方法:去掉ORDERBY,加索引后,效果不明显。由于结果集大和查询频繁这两个问题还没有解决,考虑使用新的设计方案。新方案:设计命令发送线程池,生产者线程每个任务服务器只有一个线程,负责查询要发送的命令,每次查询50条命令。每条指令被打包成一个Runnable对象,放入ThreadPoolExecutor线程池中,线程池大小参数设置为100或200。每当线程池满时,生产者停止生产指令,并在15秒的中断后继续生产。消费者线程为线程池中的线程,参数设置为4、8或12(与不同指令类型的指令数据量成正比)。改进后的方案,数据库CPU占用率降低到10%以下,单机发送效率提升6倍,任务服务器可线性扩展。11、压力测试时TPS曲线急剧下降或抖动:50次并发压力测试,TPS曲线应该是平坦的,波动不大。如果突然急剧下降,短时间内无法恢复,则可能有问题。问题原因:一般是前端或者bpjvm的垃圾回收,或者日志记录磁盘满了导致的。解决方法:如果不是特别剧烈的波动或者TPS曲线下跌后长时间没有反弹,可以忽略这个问题。否则,有必要分析曲线下降时系统发生了什么。可以使用top命令监控当时CPU占用率高的线程,也可以用kill-3pidkilljavacore查看线程堆栈。六、优化案例一、网银系统基本情况1)压测环境系统架构压测环境不包括F5,只有1个WEB,1个前端(应用服务器),1个BP(业务处理服务器),以及1个数据库,1个Redis服务器。2)客户端请求链接:客户端按下->Web->PRE->BP->DB2;clientpress->Web->PRE->BP->baffleserver(模拟后台服务器)。3)系统特点:有多项用户和帐号权限检查。公司业务典型场景:搬运、审计、查询、下载、批处理。用户权限通过业务链进行控制。接口调用较多,接口由前端组合调用。前后端分离,前端通过一个一个调用接口来完成业务。接口粒度更细。登录、支付和转账处理需要15-19个接口才能完成一笔交易。强一致性和高可用性。在资金交易系统中,一致性需要由数据库来保证。4)网银登录压力测试曲线2、数据库消息队列案例(命令发送队列)需求是:对于需要异步处理的交易命令,需要设计一个基于数据库的消息队列,我们??称之为命令发送队列。队列不仅要满足服务器的性能约束,还要满足日常交易量的要求。1)预优化处理过程中的后台任务,每次从数据库指令表中排序,取出最早的指令,获取指令的详细事务信息,组装成消息,调用接口发送给后台核心系统。在压测环境下,如果该方案配置了一个后台任务,则达不到系统设计的命令处理速度;如果配置了多个后台任务,数据库的CPU占用率会很高,影响其他线上业务的开发。2)优化后处理流程。每个后台任务服务器配置1个生产者任务和20个消费者任务。生产者任务一次取100条(可配置)指令,依次分配给20条消费者任务中的空闲任务。消费者任务获取指令的详细信息,组装消息发送给后台核心系统;对于idleconsumer任务,等待15s再判断。该方案显着降低了数据库的CPU负载,合理利用了应用服务器的并发能力,大大提高了处理效率。线程数和每次获取的指令数都可以配置。指令发送线程池模型图:三、数据库死锁案例1)死锁问题现象当多个任务服务器同时运行大量指令发送后台线程时,即多个生产者线程并行更新数据库指令表时,databasesnapshot检测到数据库存在死锁,或者通过db2evmon-dbcorpdb-evmDB2DETAILDEADLOCK>dlock.txt生成死锁监控文件,快照或监控文件存在死锁。DB2数据库具有自动释放死锁的功能。死锁超时时间默认为10s,数据库会随机选择一个死锁事务进行kill。在这种情况下,因为是后台任务,用户不会感觉到死锁;如果是在线交易,一个用户会发现交易失败,另一个用户交易成功,但是交易会感觉比较慢。命令发送后台任务模型详见下一章。2)数据库死锁的原理两个不同的数据库事务使用排他锁锁定同一张表的不同行记录,并等待对方读取被对方锁定的行记录。3)可能导致死锁的原因有全表扫描、大事务、事务间交叉访问顺序死锁等。4)死锁排查流程第一步:分析数据库快照和死锁监控日志,查看导致死锁的SQL,定位问题SQL。Step2:本题SQL不存在死锁访问顺序跨事务的情况。当时不清楚程序中的全表扫描和大事务可能会导致死锁,所以做了如下实验:模拟问题SQL在两个不同的数据库客户端执行SQL检查数据是否更新成功.完全按照源程序的SQL逻辑执行验证:UPDATE(SELECTBP_SRVR_IPFROM${tableName}WHERETSK_STAT='TODO'AND(BP_SRVR_IPISORBP_SRVR_IP='')ANDPRTY=?ANDeff_tm<=CURRENTTIMESTAMPFETCHFIRST50ROWSONLYWITHRS)tSETBP_SRVR_IP=?没有索引,A事务在R上加X锁,B事务不能在任何记录上加X锁,B事务会等A事务提交后才加锁。加索引,A事务给R记录加X锁,B事务给S记录加X锁,互不冲突。Step3:实验发现查询SQL增加withRS隔离级别,查询效率会更高。当A事务对R记录加X锁,B事务扫描R记录时,如果是CS隔离级别,B事务会自动退出并返回空结果集;如果是RS隔离级别,B事务会等待A事务完成,跳过R记录,对符合条件的R+1记录加X锁。Step4:为PRTY字段添加索引,不存在死锁问题;或者不加索引,保持全表扫描不变,把大事务改成小事务,也没有死锁问题。5)两点经验需要熟悉DB2锁的种类和作用,隔离级别的种类和作用,表锁和行锁发生的条件。例如,在进行全表扫描时,DB2会在整个表上加一个表级锁;如果是增删改查操作,则加表级排它锁。当死锁原因不清楚,或者解锁机制和隔离级别机制未知时,可以在数据库客户端工具上手动测试不同隔离级别下SQL的影响范围,验证数据库机制和猜想。七、未来提高网银系统性能的备选方法1、增加缓存的使用。对于读多写少的数据,可以加载到分布式缓存中,减轻数据库的压力。目前,一些参数和错误代码数据已经放在分布式缓存中。后续谨慎增加缓存使用,以减轻数据库压力。2.精简BP日志。删除事务访问日志表的操作。3.合并减少接口数量,前端缓存数据。