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

背景

团队内有一组服务启动明显较其它服务要慢(线上启动超过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 runnable
java.lang.Thread.State: RUNNABLE
at 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:

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

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

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

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

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

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

总结

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

本文最先发表于: https://aops.io/article/improve-bootstrap.html

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

  1. (转)解决:本地计算机 上的 OracleOraDb10g_home1TNSListener服务启动后停止

    原文地址:http://justsee.iteye.com/blog/1320059 手动启动一个问题:本地计算机 上的 OracleOraDb10g_home1TNSListener服务启动后停止. ...

  2. 替换系统数据库解决SQLSERVER服务启动不了的问题

    替换系统数据库解决SQLSERVER服务启动不了的问题 当遇到SQLSERVER服务启动不起来的时候,我们试过把系统的四个数据库master ,model ,tempdb,msdb 替换掉,Windo ...

  3. paip.花生壳 服务启动失败 以及不能安装服务,权限失败的解决

    paip.花生壳 服务启动失败 以及不能安装服务,权限失败的解决 系统win7 NewPhDDNS_1.0.0.30166.exe  作者Attilax  艾龙,  EMAIL:1466519819@ ...

  4. 安装ArcGIS License 10.1 许可管理器 破解版 服务启动又失败的解决办法

    安装破解文件的提示执行 替换许可管理器Bin下面的service.txt  文件,之后会发现,许可管理器启动不了(有时候又可以,挺郁闷), 经过多次的试验,我找到了一种折中解决的方法,供大家参考 解决 ...

  5. Win2008服务启动不能调用Office Word的解决方法

    本文为大家分享一下如何解决Windows Server 2008 服务启动不能调用Office Word的问题,分享这个教程的原因是,今天在Windows server2008上部署一个应用时发现了一 ...

  6. win2003 HookPort 服务启动失败的解决办法!

    Win2003系统每次开机启动时都弹出个对话框报HookPort 服务启动失败,很多网友都遇到同类问题,问题根源是360安全卫士引起的,官方一直没有给出解决方案 问题描述:Win2003系统每次开机启 ...

  7. 破解windows下MySQL服务启动不了的情况下不能对其进行全然卸载的解决方式

    下面的文章主要介绍的是在MySQL服务启动不了的情况下,不能对其进行全然卸载的实际解决的方法的描写叙述,下面就是对解决MySQL服务启动不了的情况下详细方案的描写叙述,希望在你今后的学习中会对你有所帮 ...

  8. Windows 共享无线上网 无法启动ICS服务解决方法(WIN7 ICS服务启动后停止)

    Windows 共享无线上网 无法启动ICS服务解决方法(WIN7 ICS服务启动后停止) ICS 即Internet Connection Sharing,internet连接共享,可以使局域网上其 ...

  9. mysql57重新安装后无法再次启动mysql57服务“本地计算机上的MySQL服务启动后停止。某些服务在未由其他服务或程序使用时将自动。”--解决方法

    本地计算机上的MySQL服务启动后停止.某些服务在未由其他服务或程序使用时将自动. (win10,mysql5.7+) 解决方法: 第一步:查看MySQL57安装路径 只要在programData路径 ...

  10. Mysql安装后在服务里找不到和服务启动不起来的解决方法

    一,在安装完Mysql数据库后,发现在控制面板->管理->服务中找不到Mysql的服务启动 解决方法如下:开启命令行,按照如下步骤即可: 1.进入到mysql的安装包,在bin里执行:my ...

随机推荐

  1. 经验总结之 _DEBUGGER _03 _Server Tomcat v8.0 Server at localhost was unable to start within xx seconds

    经验总结之 _DEBUGGER _03 _Server Tomcat v8.0 Server at localhost was unable to start within xx seconds 好好 ...

  2. 号码变换配置对接运营商IMS

    概述 freeswitch是一款简单好用的VOIP开源软交换平台. fs直接对接运营商,调试过程中的号码变换规则比较容易出问题. 本文档记录一个较为通用的对接IMS配置方案. 环境 CentOS 7. ...

  3. 初识GO语言--并发

  4. IWeb4j 项目解决web自动化回归测试痛点!

    IWeb4j 项目思想 IWeb项目的测试用例使用数据驱动测试,当下数据驱动使用最多的无非是excel.yaml.json.数据库(后续通过SpringDataJPA实现数据库驱动)作为数据驱动的载体 ...

  5. 6. jenkins触发器

    常用的触发器 Jenkins内置4种构建触发器 触发远程 构建其他工程构建后触发(Build after other projects are build) 定时构建(Build periodical ...

  6. 想要硬件设计不用愁?首先要搞懂这三类GPIO!

    合宙低功耗4G模组经典型号Air780E,支持两种软件开发方式: 一种是传统的AT指令:一种是基于模组做Open开发. 传统AT指令的开发方式,合宙模组与行业内其它模组品牌在软件上区别不大,在硬件功耗 ...

  7. CommonsCollections2(基于ysoserial)

    环境准备 JDK1.8(8u421)这里ysoserial,我以本地的JDK8版本为准.commons-collections4(4.0 以ysoserial给的版本为准).javassist(3.1 ...

  8. html face属性

    无意间发现邮件里面的字体非常像手写的,然后点击HTML源码发现,使用了这个face属性. 代码如下: <font face="comic sans ms">PS: 你看 ...

  9. python 递归比较两个文件夹

    以下 import filecmp, os def compare_folders(folder1, folder2): dcmp = filecmp.dircmp(folder1, folder2) ...

  10. JVM调优总结:典型配置举例

    原文出处:http://developer.51cto.com/art/201201/311739.htm 一篇非常棒的关于JVM性能调优的文章,转载用于自己经常查阅 以下配置主要针对分代垃圾回收算法 ...