最近优化了项目中,慢接口的业务逻辑,得出一些心得
最近发现一个生产问题,一个接口突然间提示异常,查日志,提示是feign接口超时,其实数据已经执行完了,只不过超过了feign设置的超时时间(60s),然后前端跳出了框架统一封装的异常信息
出现原因:
平时的业务量基本都是十几笔,几十笔,上百笔这样的业务数据,但是这次突然来了一次超过500笔的业务数据,整个过程都是复杂的业务处理,后端处理几乎要经过5个表这样去取数,并且还要做一些金额的计算等,所以这个耗时就急速上升了。由于代码都没有做过性能的测试,所以一下次就垮了。业务经理以为系统出了问题,投诉到了技术部。
因为已经定位到了具体的接口,所以单独拿出来,请求开始的代码逻辑到请求结束的代码逻辑,梳理了一遍,发现是那种所谓的陈年旧代码,屎山,很多影响性能方面的操作:
一、500+笔业务进入接口,入参是String,且是逗号分隔符拼接,如:String str = "aaaa,bbbbb,ccccc",看到这个差点气晕,这个平时还好没有超长,超长了一样被投诉
二、500+笔业务进入请求,需要拿这500+笔的业务流水号去查数据,由于这些数据存储的表,是一张很大的表,字段特别长,没具体数,上百个是有的,此时内心一万个草泥马,谁做的表设计,并且询问过,这个表n处在使用,不可以做表水平拆分
三、500+笔业务流水去查多个表的时候,是for循环一笔笔去查的,一个表500次mybatis的selectById,大概有5个表,那就是2500次去数据库查,效率异常的底下,甚至还有的是for循环里面再for的
四、500+笔业务流水昨晚数据处理后,需要插入到数据,或者有的表是做更新的,for循环insert和for循环updateById
以上是经过了1天的梳理,得出导致性能差的代码,想起一句经典的话:jvm一般是不需要调优的,要调优的都是垃圾代码导致的!!!!!
程序员还是应该具备基本的代码优化能力,什么for循环去查数据库,一般要避免这种情况,能一次lou出来就一次,跟数据库只做一次交互,提升性能
开始第一次重构代码:
一、把接口入参String改成List<String>
二、业务大表没办法拆,只能使用主键索引去查,用in('aaa','bbb','ccc')的方式一次性查出来,避免for里面再selectById,如这时候不是500+,是1000+的话,则对List<String>做一个切割,1000笔查一次,否则in超过1000笔会报错。对list切割可以自己写一个时间复杂度最低的算法,也可以用guava的list切割省事
三、缕了一次逻辑之后,发现,可以并行的去查多个表,使用异步编排CompleteFuture,并行去执行任务,不需要串行查表
四、for循环入库和更新,换成mybatisplus的saveBatch,和updateBatch,批次执行,500一次或者1000一次
修改后第一次测试:
本地模拟了生产的数据量500+笔数据发起业务,没改代码前复现了同样超时问题,执行了2分多种。经过上诉优化后,提升到了20s左右,个人感觉还不适合理想,用arthas检测了下哪个方法执行耗时情况,发现耗时还是在mybatis取数据时候:
一、mybatis默认取数是10条一次,然后再继续取,直到取完,所以在数据库执行是变快了,但是用mybatis执行任然有差异
二、for循环里面做复杂处理和计算时,也会有性能消耗,如:getName这样的操作,每次for里面执行一次,那就是500次,如果有多次调用,那就是500*n,虽然都是ms级别,但是多了这个数量还是很客观的。
三、监控jvm内存会一下次暴增,垃圾回收会很频繁,但是好消息是没有fullgc
针对问题第二次重构代码:
一、mybatis每次取数扩大,xml增加参数:fetchSize="100",经过测试100跟1000相差不大,使用保守100
二、for循环里面如果有一个参数要多次使用,第一次getName的时候,提出来做一个本地变量,节省500*n的耗时
三、修改sql语句,大数据表取行数时,只取需要的字段,如:一个表有100个字段,只取需要的5个字段,select A,B,C,D,E from table,经测试可以节省堆空间消耗,冗余字段不需要,因为如果一起带出来赋值给对象时候会生成String对象,占用堆空间
四、把for循环换成JDK1.8的foreach内部循环(不用不知道,一用吓一跳,复杂计算内部循环会节省很多消耗,但是笔数没达到一定数量,是没啥区别的)
修改第二次测试:
相同参数再次执行请求,响应时间上到8s内,此刻的主角爽度+100,继续用arthas监控,之前的mybatis取数跟循环for操作耗时已经在1s上下了,而且jvm的垃圾回收次数有相应的减少,目前所有耗时全都在insert跟update里面,入库跟更新表需要消耗IO,这个确实是感觉无法优化了(有想过多线程去入库,但是涉及子线程事务控制,比较复杂,最终放弃),最后版本修复,生产继续做相同业务,已经可以正常执行,并且执行比本地快,3s左右就可以响应,果然还是生产服务器好啊,码农板砖能用就行啊。。。
小结:
一顿优化之后,深刻体会到了开发应该具备良好的编码习惯,不规范的代码不要去写,这样可以避免很多未来会产生的问题,少bug,多摸鱼。可去阅读《阿里的代码规范手册》,《effective java》,《代码的整洁之道》这些书记,提升内功,完结撒花~~~