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

背景

团队内有一组服务启动明显较其它服务要慢(线上启动超过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. 批处理-- 查询进程,杀进程,启动pythond程序,任务计划程序

    @echo off wmic process where caption="python.exe" get processid,commandline | findstr &quo ...

  2. 3.使用kubeadm方式搭建K8S集群

    使用kubeadm方式搭建K8S集群 kubeadm是官方社区推出的一个用于快速部署kubernetes集群的工具. 这个工具能通过两条指令完成一个kubernetes集群的部署: # 创建一个 Ma ...

  3. CF2025E Card Game 题解

    太喜欢这个题了,这个题出得很启发性,我以前还没见过,于是把这个题记录下来. 题面 在伯兰最流行的纸牌游戏中,使用的是一副 \(n \times m\) 纸牌.每张牌都有两个参数:花色和等级.游戏中花色 ...

  4. Flink 中的事件时间触发器和处理时间触发器

    EventTimeTrigger EventTimeTrigger 的触发完全依赖 watermark,换言之,如果 stream 中没有 watermark,就不会触发 EventTimeTrigg ...

  5. 6、oracle网络(监听)

    oracle包含 1.软件 2.数据库 3.实例 4.监听(listener) 监听的特点 可以独立启动,就是说,数据库没有启动,监听可以启动:数据库启动,监听也可以不启动:数据库启动,监听也启动 监 ...

  6. Java线程池架构2-多线程调度器

      http://ifeve.com/java线程池架构2-多线程调度器(scheduledthreadpoolexecutor)/ 在前面介绍了java的多线程的基本原理信息:<Java线程池 ...

  7. java——棋牌类游戏斗地主(webddz1.0)之一

    这是本人最近一段时间写的斗地主的java代码,大体框架都实现了,主要缺少,AI的智能算法. 本版本是在singleddz3.0的基础上修改的,修改为了网络版本. 主要修改代码如下: package c ...

  8. 从零开始的Python世界生活——基础篇(Python字典)

    从零开始的Python世界生活--基础篇(Python字典) 1.Python字典是什么? ​ Python字典是python中非常重要的非常灵活和强大的内置数据结构,用于存储键值对(key-valu ...

  9. 开源 - Ideal库 - Excel帮助类,TableHelper实现(二)

    书接上回,我们今天开始实现对象集合与DataTable的相互转换. 01.接口设计 上文中已经详细讲解了整体设计思路以及大致设计了需要哪些方法.下面我们先针对上文设计思想确定对外提供的接口.具体接口如 ...

  10. WinForm 开源组件 Realtiizor

    Realtiizor 的优势 现代美观的界面设计 Realtiizor 为 WinForm 应用带来了现代感十足的界面风格.它采用了流行的设计理念,如 Material Design 的元素融入,使得 ...