关注公众号【1024个为什么】,及时接收最新推送文章!
1、背景
新接手的一个服务,对整个服务熟悉后,发现调用量 TOP1 的一个接口,完全超乎我对这个接口使用场景的预期,预期几万的接口,实际调用量近 400万,和调用方交涉后,暂时无法推动调用方优化,所以只能从接口内部优化。
2、存在的问题
2.1 不合理的重复调用
同一调用链内,连续调用3次
貌似还有定时任务10分钟一次
2.2 日志文件过大,浪费磁盘空间且不利于排查问题
24G,一个下游且相对边缘的服务,单节点的日志竟然 24G,除上游不合理的调用量外,肯定有日志打印不当的地方。
排查问题时搜索到关键字要几十秒,严重影响到问题排查。
2.3 接口平均耗时高
平均耗时 11.5ms,其实内部调用逻辑很简单,就是数据库查询、redis、RPC调用,按调用链路预估 6ms 左右就正常。
2.4 冗余的数据库调用
接口内 18 次数据库调用中,很多是冗余的,可以优化或者减少调用。
2.5 缓存使用不当
redis 调用 8 次,有6次都是重复调用;
缓存内容不合理,缓存了很多无用内容,无需缓存的也用了缓存
3、从哪里入手?
3.1 减少数据库调用、低效查询
1)有一个 2 次单表组合查询的逻辑,接口中总共会调用 3 次。
select * from A where id=?; // 其中包含 B.id
select * from B where id=?;
把 B 中的 amount,覆盖掉 A 中的 amount,返回。
优化为联表查询,都会走主键索引,少一次数据库查询。
2)3 次调用中,最后一次查询是重复的,在第二次查询处理后已经返回了最新的数据,去掉这次查询,直接把第二次返回的数据给第三次查询后的逻辑使用,减少一次数据库查询。
3)有一次提前的无用的数据库查询,查询结果只在后续逻辑特定条件分支才会用到,概率极低,绝大多数会提前返回结果。把这次查询后移到真正使用处,减少一次数据库查询。
3.2 减少 redis 调用
有一个场景,从根据品类ID从缓存获取归属业务线ID的,实际只会有 3 个品类ID,映射出 2 个业务线ID,完全没必要走缓存,直接改为写死返回,可以减少 6 次 redis 调用。
3.3 修改不合理缓存
有一个缓存商家标签的缓存,会缓存标签集合的所有属性,而逻辑中只需要用到标签ID,改为只缓存标签ID,缓存内容减少为原来的 0.8%,节省 redis 缓存,减少网络耗时。
3.4 增加接口缓存
前面提到调用方存在不合理的调用,一次调用链中,连续多次调用,几次的入参、结果都相同,所以没必要每次都走一遍接口逻辑。
对接口层面加缓存,后面几次的访问直接从缓存获取。
由于接口返回内容仅有 200 个字符左右,缓存时间为 2 秒,QPS 为 50 左右,占用缓存空间很小 30K 左右。
3.5 减少不合理的日志
梳理发现,有很多不合理的日志,总结为以下几点:
1)直接把全量集合内容直接打印出来
改为只打印 size,或者只打印关键ID。
2)DAO层冗余的日志
每行日志除了我们要打印的信息外,还有额外的信息,例如时间、日志级别、调用链、当前位置 等等。
对于实际日志内容很短的场景,很不划算,把 2 行改为 1 行,可以减少很多日志输出。
原则是既不影响排查问题,又不多打无用的日志。
3)低效的日志
正如上图所示,要打印的对象,一律都加了 JSON.toJSONString(),json 框架内部会有很长的调用逻辑,严重影响性能。
删除String 以及基础数据类型的 toJSONString 的转换。
4、效果
优化前耗时:
优化后耗时:
日志大小:
从 24G 减少到 4 G。
5、总结
我的格局很小,
会抠每一行日志,
会抠整个调用链路上有无重复的查询,
会抠是否需要缓存,
会抠缓存的内容是否都是必要内容,
会抠每一次 rpc 调用是否必要,是否可以缓存。
扯两句
你的问题背景肯定和我的不一样,但可以参考我的思路,死抠每一行代码。
我始终相信,每一个细节都把控好了,就不可能有低效的接口。
原创不易,多多关注,一键三连,感谢支持!