前言
最近终于上线了历时半年多的数仓管理文件夹的优化,泪目了家人们,可谓是呕心沥血之精雕细琢之抓耳挠腮之无能狂怒之爱咋咋地的匠心巨作终于上线了,组长为了测试整体重构后的性能专门向运维申请了一台新机器部署重构后的版本代码搭建一套独立的线下集成环境,然后用历史积攒下来的数据灌到新服务里看一下整体的体验。
牛逼都吹出去了,原来1s以上的接口响应全部优化到500ms以下,代码结构优化后通过中台能力可以半天开发出一套数据管理相关业务。虽然季度会上我没说话,但是听着组长在会上哐哐吹牛逼,我听着腿肚子直转筋,后背呼呼冒冷汗。。。。
之前写的重构的一些思路的文章贴在下面,主要理了一下文件夹相关的一些恶心的点以及解决方案。
接口调优
由于代码结构变动比较大,基于的表逻辑也发生了一些改动,线下测试的时候是用了一个纯净的环境去做测试,包括崭新的mysql镜像。这样搞的话线下的数据就远远低于了生产环境下的数据量,这次优化大部分是一些查询类的sql,虽然测试都快结束了,但是我还是有一点点的虚。
环境一搭建好我就立马进行测试,数据量一大果然露出了马脚,改造后的首页,首先会加载一个平平无奇的类型树接口,点进去页面后loading转了三四圈后才加载出来.....
完了,当时第一反应是离职散伙饭去哪家吃。。。不行门口那家云南菜吧,刚抢到张50代金券。。。
测试环境里几千条数据毫秒级响应不在话下,数据量上了十万之后直接干到3s左右了。直接原形毕露,好在组长给了我两天反应时间。这下得拿点东西出来了家人们!
首先架构已经定了,而且技术评审了好几轮应该没有太大的问题,有也不能全赖我。想到这一点稍微缓和了一下,先分析日志。我们的日志都会打出毫秒级的执行时间,根据接口的trace_id获取所有的请求信息,马上就排查出导致慢的第一宗罪。
反序列化
由于构建树的时候,一些接口是全量获取,十万级别的单表查询其实是非常快的,顶多损耗个几十毫秒在网络IO上,但我发现在执行SQL到SQL执行完足足用了差不多1s,每一个全量接口都消耗了很多时间,将sql单独摘出来执行又很快,第一反应就是反序列化出了问题。
由于我们使用JPA做数据库层的ORM框架,为了解决原生sql联表查询返回自定义DTO,公共组件里定义了一个用于反序列化的注解@JpaResultDto,这个注解是用aop的形式在服务启动时用反射将DTO类注册一个Converter到Spring默认转换器里。
问题就出在这里,每一条数据从DB反序列化的时候都要通过反射拿到类信息,再通过静态代理的方式将真实的数据写入到对象里。每条数据都这么搞,性能损耗就起来了。当断则断,这帮做中间件的做的什么玩意,差点害我翻大车。。。。
骂骂咧咧的用Object接收,手动反序列化,果然接口成功徘徊到了800ms左右,偶尔会抽风到1s,果然高端的食材只需要最简单的烹饪方式。。。虽然离500ms还有一定距离,不过我丝毫不慌,调优三板斧的连招打出第一招优化了50%。
SQL调优
首页的树接口性能上来了,试一下全部数据的分页接口,果不其然.....也在1s多,完了完了,呕心沥血的接口竟然发挥成这样,反序列化的事已经统一了,内存操作的损耗几乎可以忽略。马上反应过来是SQL的执行问题,打开一下JPA的慢SQL监控。
yaml
jpa:
show-sql: true
properties:
hibernate:
session:
events:
log:
LOG_QUERIES_SLOWER_THAN_MS: 100 # 记录大于100ms的慢查询
打开后日志里如果有超过了配置的100ms的SQL都会打出来数据库里真正执行的SQL便于分析,结果打开后tail -f 监控日志,那个SlowQuery Sql呼呼的打,打的我心都要碎了。。。。
根本原因是当时为了方便后续的调试我把查询条件都整合成了一句SQL,然后用if语句去判断要不要join表查询条件,这样肯定是有一些消耗的,不过这块我留了一手。
按需加载
所谓按需加载就是非常时期用非常手段,这种高性能且经常访问的接口可以忽略一些代码结构上的不合理,理论上数据库层查询都要把SQL定义在DAO层去执行。但是这样对于多条件查询的分页接口下显得十分不灵活,既然长痛不如短痛,干脆把SQL定义到Service层,我们在Servcie层基于查询条件按需拼接对应的查询语句。这样的话默认进入页面的时候本质上是一个单表查询,能不起飞吗。
由于当时留了一手,顺带着树接口很快就改造好了,分页接口果然到了200ms左右,全量树到了500ms左右徘徊,ok了ok了,散伙饭看来可以推到拿完年终了。
数据库参数调优
三板斧劈完两板斧之后,接口虽然速度是上来了,但是还很不稳定,隔一段时间就会有一次较慢的查询,虽然不是特别慢,但是能明显感受到,比如树接口偶尔会到1s多。那这肯定不行啊,万一组长演示的时候突然抽筋一下子,那我仕途不是走到头了。
抖动的问题浪费了很久的时间去分析原因,一直没有啥头绪,偶然一次看日志的时候发现还是sql执行的问题,那么相同的sql相同的机器,会有哪些原因导致执行性能不同呢。SQL实在是优化不动了,于是打起了MySQL的主意,数据库执行查询如果刨去SQL执行的损耗。
那大部分都会消耗在IO上,平时我们的数据库都进行过参数调优,这台新机器新部署的数据库肯定稍显逊色,于是立刻iotop滚动个五分钟观察机器指标。
发现MySQL的io隔一段时间就干到100%冲到第一,隔一段时间就冲上来。那么OK了,跑不了,绝对是磁盘IO导致的。那么如何规避一下呢。
回顾之前的MySQL八股文,我们知道MySQL这层是有一层buffer缓存机制的,innodb_buffer_pool_size ,缓冲池是数据和索引缓存的地方,它属于MySQL的核心参数,默认为128MB,正常的情况下这个参数设置为物理内存的60%~70%。合理猜测一波,接口出现抖动是因为缓存到buffer后大部分时间接口相应是很快的,但是由于buffer_pool大小为128M,满了之后被清掉就会导致重新进行一次磁盘IO导致抖动。
以在my.cnf里加上重启后果然成功稳定在了500ms以下。