关注公众号【
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 调用是否必要,是否可以缓存。
扯两句
你的问题背景肯定和我的不一样,但可以参考我的思路,死抠每一行代码。
我始终相信,每一个细节都把控好了,就不可能有低效的接口。
原创不易,多多关注,一键三连,感谢支持!