为了解决服务启动慢的问题,我为什么要给Apollo和Spring提交PR?

news/2025/1/7 12:11:16/文章来源:https://www.cnblogs.com/shawyeok/p/18653715

最近在整理之前记录的工作笔记时,看到之前给团队内一组服务优化启动耗时记录的笔记,简单整理了一下分享出来。问题原因并不复杂,主要是如何精准测量和分析,优化后如何定量测量优化效果,说人话就是用实际数据证明优化效果。

背景

团队内有一组服务启动明显较其它服务要慢(线上启动超过2分钟),偶尔还会超过容器liveness的最大超时时间导致服务启动到一半又被kill掉重新启动,由于服务没有启动完成是不会接入流量的,影响最明显的是日常变更维护时等待服务滚动部署的时间较长,另外服务在本地启动也比较慢,浪费程序员的时间。

当时我并不是该服务的开发人员,相关的业务逻辑了解不多,据相关同学反映,该服务在Apollo(一个开源的应用配置管理中心)上有大量配置,启动时需要加载这些配置,随着配置逐渐增多,启动时间也越来越慢。我去Apollo上统计了一下,大约有5000行properties配置。这5000行配置加载到Spring容器的配置bean需要分钟级?听起来就不大合理。

分析过程

配置项大致长下面这个样子,层级说多不算多,说少也不算少。

xxxx.dp.caption.converter[0].convertRules[0].castTo=BYTESTOSTRING
xxxx.dp.caption.converter[0].convertRules[0].sourceField=city_id
xxxx.dp.caption.converter[0].convertRules[0].targetField=cityId
xxxx.dp.caption.converter[0].convertRules[1].castTo=BYTESTOSTRING
xxxx.dp.caption.converter[0].convertRules[1].sourceField=city_name
xxxx.dp.caption.converter[0].convertRules[1].targetField=cityName

由于启动时间很长,有充足的时间执行jstack抓取threaddump,main线程如下:

"main@1" prio=5 tid=0x1 nid=NA runnablejava.lang.Thread.State: RUNNABLEat java.util.HashMap.resize(HashMap.java:735)at java.util.HashMap.putVal(HashMap.java:663)at java.util.HashMap.put(HashMap.java:612)at com.ctrip.framework.apollo.internals.DefaultConfig.stringPropertyNames(DefaultConfig.java:115)at com.ctrip.framework.apollo.internals.DefaultConfig.getPropertyNames(DefaultConfig.java:105)at com.ctrip.framework.apollo.spring.config.ConfigPropertySource.getPropertyNames(ConfigPropertySource.java:24)at org.springframework.core.env.CompositePropertySource.getPropertyNames(CompositePropertySource.java:87)at org.springframework.boot.context.properties.source.SpringIterableConfigurationPropertySource$CacheKey.get(SpringIterableConfigurationPropertySource.java:214)at org.springframework.boot.context.properties.source.SpringIterableConfigurationPropertySource.getCache(SpringIterableConfigurationPropertySource.java:134)
...

由于stacktrace过长,这里就不贴完整的,感兴趣的读者可以查看我在Apollo github仓库提交的issue分析。

看上面的stacktrace,可以看到Apollo内部的一个方法调用DefaultConfig.getPropertyNames在不断向一个HashMap中填充数据致使其扩容。通过静态的stacktrace,我们能知道的信息也就仅限于此了,我们还不知道这段看起来消耗比较大的逻辑在整个启动过程的占比大概是多少,因此这里就需要用到Profiler工具了,当时我在本地用的是JProfiler,一款商业的Profiler工具。今天我们完全可以用开源免费的async-profiler替代。通过对启动过程进行profiling,得到下面结果:

有了这样自顶向下的树状分析结果,消耗最大的方法就很明显了,这里是ConfigPropertySource#getPropertyNames,到这里就需要结合Apollo的源代码进行分析了,最终定位到上面方法在启动过程中会在Binder.bind中调用很多次,这个方法的底层实现又是每次构造一个全新的数组,时间复杂度和加载的配置数量呈线性关系。在配置较多的场景下,消耗就非常明显了。由于这里的配置发生变化时,Apollo的客户端是可以感知到的,因此我们完全可以替换成使用缓存,配置变化后清理掉缓存的方式。因此基于这个思路,给Apollo提了PR:

  • https://github.com/apolloconfig/apollo/issues/3800
  • https://github.com/apolloconfig/apollo/pull/3816

测试结果显示在配置加载阶段的耗时,从平均2分钟优化到不到1秒。

在对大量配置场景下的启动测试profiling过程中,同时也发现了Spring framework的一处优化点,上面Apollo的热点方法优化之后,通过profile观测到的下一个热点方法就是CompositePropertySource#getPropertyNames了,CompositePropertySource顾名思义是对多个PropertySource的Composite模式。

在Spring这种超级流行的项目中,哪怕是启动过程可以优化1秒,全世界大量项目可以从中收益,总量还是相当可观的。因此抱着这样的心态,给Spring也提交了PR:

  • https://github.com/spring-projects/spring-framework/pull/27236

由于涉及到spring-core模块的变动,并且这个方法调用在启动过程中也属于hot code path,因此是需要编写相应的微基准测试的。Spring使用openjdk衍生出的Java Microbenchmark Harness (JMH)工具做微基准测试。

通过前后对比维基准测试的结果,形成实实在在的数据验证代码优化后的效果,上面的调整最终大约有-30%的执行时间缩减。

关于JMH,可以通过视频和它的代码仓中自带的samples中学习一下如何使用。

总结

  1. 通过对启动过程进行profiling定位到消耗大的方法,结合源代码理解相关逻辑,找到可行的优化点,代码优化后再对比前后测量结果验证。
  2. 通过JMH等微基准测试工具对单个方法进行基准测试,形成容易被复现的测试结果的数据。
  3. 找到可以帮助review方案和提供建议的人,文章的例子是找到了Apollo社区成员同时也是Apollo作者之一,通过讨论最终形成的方案相较最初的“补丁式”优化更加优雅和合理。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/864495.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【MATLAB】自学记录之读取DEM高程数据文件并渲染成三维地形图

1. 前言 近日在学习MATLAB编程以及地理高程数据处理等相关知识时,希望通过MATLAB的绘图等相关函数,读取高程数据文件,最后以可视化的方式展示全球陆地范围内的三维高程数据图。 2. 运行环境及数据序号 配置项 说明1 CPU Intel i5-12490F2 内存 16G*2, 3600MHz3 磁盘 256G,S…

Redpanda Console - 流数据管理控制台

Redpanda Console - 流数据管理控制台 简介 Redpanda是一个与Kafka兼容的流媒体数据平台,该平台具有高性能、操作友好和云就绪性。这家总部位于旧金山的公司成立于2019年,专注于Kafka公司关键任务系统的替代产品。 Redpanda使用C++重写Kafka,与Kafka API完全兼容,可以与所有…

Prometheus+Grafana监控flink任务指标

Prometheus+Grafana监控flink任务指标 前期准备 Prometheus 是一款基于时序数据库的开源监控告警系统,由go语言开发,Prometheus的基本原理是通过HTTP协议周期性抓取被监控组件的状态,任意组件只要提供对应的HTTP接口就可以接入监控。 Grafana 是一款采用Go语言编写的开源应用…

Visual Studio中的C#项目连接本地mysql数据库

一、给项目搭载Mysql连接所需的NuGet包 1.点击visual studio上方导航栏的"工具"选项 2.在下拉框中选中"NuGet包管理器" 3.在展开的侧边栏中选择"管理解决方案的 NuGet 程序包" 4.在新打开的窗口中选择"浏览" 5.搜索框中输入"mysq…

P6822 [PA 2012 Finals] Tax

一个小Trick。很牛的题目!! 直接做可能比较困难,你要考虑一些东西重构一下原图。 这一个题目因为是与边相关的,考虑拆边,拆成两条有向边,那么对于一个点的贡献,我们暴力枚举他的边,两条边的贡献就是取 max。 但这个显然过不了,我们有一种差分建边的方式,按照边权排序…

20241421 《计算机基础与程序设计》课程总结

第一周作业 1.学习了有关专业的技能培养方向 2.对《计算机基础与程序设计》进行了大致的浏览,大概了解了计算机的相关理论知识 3.学习了有关2进制、8进制、10进制、16进制之间的转换第二周作业 1.《计算机科学概论》:第一章向我们介绍了计算机科学这门学科的内容以及其基本概…

[.NET] 单位转换实践:深入解析 Units.NET

在现代软件开发中,准确处理不同单位的转换是一个常见而复杂的需求。无论是处理温度、长度、重量还是其他物理量,都需要可靠的单位转换机制。本文将深入介绍 Units.NET 库,展示如何在 .NET 应用中优雅地处理单位转换。单位转换实践:深入解析 Units.NET 摘要 在现代软件开发中…

卡诺图化简

卡诺图化简 卡诺图(Karnaugh Map,简称K图)是一种用于简化布尔代数表达式的工具。它通过将真值表的值图形化,帮助我们更直观地找到最小项和最大项,从而简化逻辑表达式。 卡诺图的基本概念单元格:每个单元格代表一个布尔变量的可能取值组合。 邻接:两个单元格如果只有一个…

Postman与ElasticSearch交互

为了方便测试,修改ES安装路径下的config/elasticsearch.yml中的安全配置,不使用密钥访问。 以下配置的enabled都改为false # Enable security features xpack.security.enabled: falsexpack.security.enrollment.enabled: false# Enable encryption for HTTP API client conn…

本地搭建ElasticSearch

1:在elastic.co下载安装包 https://www.elastic.co/downloads/elasticsearch windows版本的是个zip包,下载解压缩后可直接通过命令行运行 cd [安装路径]/bin elasticsearch2:配置 默认需要ssl、密码等安全配置,会导致localhost:9200访问失败。可通过以下方式关闭安全配置,…

20241307《计算机基础与程序设计》课程总结

目录 第一节 第二节 教材学习内容总结 C语言程序设计第十三章和第十四章的总结: 第十三章:文件操作文件的打开与关闭 • 使用fopen()函数打开文件,它接受文件名和模式作为参数,返回一个FILE指针。 • fclose()函数用于关闭文件,释放资源。 文件的读取 • fgetc()和getc…

错误记录:[Synth 8-6895] The reference checkpoint

报错详情点击查看代码 [Synth 8-6895] The reference checkpoint E:/Projects/Vivado2023/2.ExampleDesign_my/iic_ms/iic_ms.srcs/utils_1/imports/synth_1/Master.dcp is not suitable for use with incremental synthesis for this design. Please regenerate the checkpoin…