解决问题当然重要,可是解决问题的思考过程我以为更加值得分享,因此把本身的心路历程记录下来java
原由很简单,我接手的一个spring boot项目的启动特别慢,查找问题,启动慢的位置定位在了内嵌tomcat上。在网上针对tomcat启动慢问题有不少文章,可是大部分都是在说怎么去解决。了解了以后我仍是迷惑不解,大体的疑惑在这:此次的spring boot版本是1.3,以前用的1.5版本历来没出过这个问题。是spring boot 作了啥配置了不去触发了一些tomcat初始化的步骤,仍是tomcat作了啥不去干了这样一件事情,带着这样的疑惑我开始了一场源码之旅。算法
咱们来看下网上针对Tomcat启动慢的解答。tomcat的Session ID是经过SHA1算法计算获得的,计算Session ID的时候必须有一个密钥。为了提升安全性Tomcat在启动的时候回经过随机生成一个密钥。omcat 7版本后随机数的生成依赖 java.securitySecureRandom这个类(jdk)。 这个随机数的取值能够在jre里面去配置,securerandom.source=file:/dev/random 在 http://wiki.apache.org/tomcat/HowTo/FasterStartUp (Entropy Source部分)有一段解释,咱们能够用cat 命令来进行查看一下这个/dev/random是什么东东,cat /dev/random | od -x 查看。二进制看的太乱转个十六进制看一下这里的输出即是: spring
不得不说我公司服务器生成的确是很慢,不知道运维爸爸干了啥。这个随机数的生成涉及到Linux 内核熵池的一个概念。所谓的熵就是描述混乱的一个量,熵值越大越混乱,那么所产生的随机数也就越随机,假做真时真亦假 无为有时有还无,嗯差很少就是这个意思。 想要解决能够去修改这个值,改为/dev/urandom。或者在启动时候指定jvm参数-Djava.security.egd=file:/dev/./urandom。问题解决了,可是依然仍是有不少疑惑,回到开始的问题。到底是spring boot作了什么,仍是tomcat作了什么改变。apache
为了验证个人猜想,开始看源码,先找来一个启动不满的站点。先看各个的maven版本。启动慢的站点:spring boot 1.3对应tomcat8.0.30,启动不慢的站点spring boot 1.5对应tomcat8.5.27。完了,spring boot ,tomcat都有可能作了调整,再google也没有找到啥有用的,只能本身来了,来吧看源码。tomcat
开始调试,先看日志,启动过程当中有这么一段 安全
看来tomcat仍是不错的打印了本身这一段慢的类,那么咱们就进去调试一下。 切入点有了开始debug,我这里用的远程调试,不知道的能够搜一下,由于问题出在测试环境,只能远程调试发现问题。 搜索关键字发现Tomcat的一段sessionid创建的源码,篇幅有限我就贴一半:服务器
这里即是获取随机数的位置,随机数的具体实现是jdk去作的,这里不关注了,Linux就是取的那个/dev/random。好了那么开始断点,1.3spring boot的项目命中断点,随机数的获取等了好长。1.5的spring boot....,人呢,说好的来呢,我等的花都谢了,怎么没来。1.5的启动完了都没来。看来启动慢,不是由于改变了什么参数,是1.5的spring boot 或者tomcat没有走这一段sessionid的初始化建立。session
从1.3的开始去找调用过程,看看有没有什么值得可疑的点,idea正好能够看见调用链,这是一段漫长的寻找,不断地尝试,不断地验证。起初我一直觉得tomcat的8.5.27中有啥改变,由于调用链一直显示的都是tomcat的类和方法,tomcat初始化过程是engine----->host----->context这样嵌套建立的,基本没有spring boot的干预,难道是有什么参数,可是也没看见参数控制建立过程,受不了从engine建立一步步走,忽然发现这么一个类,TomcatEmbeddedContext它是属于spring boot的类,查看类图:app
世界在这一刻豁然开朗它继承了standardXontext这个类,tomcat,context建立的类,也就是spring boot在host建立后加入了一个本身的类,作了一些不可告人的事情去改变了tomcat的一些操做。对比1.3和1.5的两个类的去别发现,1.5中多了这么一段:运维
@Override public void setManager(Manager manager) { if (manager instanceof ManagerBase) { ((ManagerBase) manager).setSessionIdGenerator(new LazySessionIdGenerator()); } super.setManager(manager); }
进入LazySessionIdGenerator:
@Override protected void startInternal() throws LifecycleException { setState(LifecycleState.STARTING); }
而tomcat本来的是什么呢,是下面这个:
@Override protected void startInternal() throws LifecycleException { // Ensure SecureRandom has been initialised generateSessionId(); setState(LifecycleState.STARTING); }
其实spring boot1.5版本后之接就去掉了generateSessionId();这个,之间初始化的时候不建立sessionid了,而这个generateSessionId()就是会调用获取随机数的那个方法。终于一切知晓了,是spring boot搞得鬼去spring boot初始化的地方看看,他是怎么初始化内嵌tomcat的
@Override public EmbeddedServletContainer getEmbeddedServletContainer( ServletContextInitializer... initializers) { Tomcat tomcat = new Tomcat(); File baseDir = (this.baseDirectory != null ? this.baseDirectory : createTempDir("tomcat")); tomcat.setBaseDir(baseDir.getAbsolutePath()); Connector connector = new Connector(this.protocol); tomcat.getService().addConnector(connector); customizeConnector(connector); tomcat.setConnector(connector); tomcat.getHost().setAutoDeploy(false); configureEngine(tomcat.getEngine()); for (Connector additionalConnector : this.additionalTomcatConnectors) { tomcat.getService().addConnector(additionalConnector); } prepareContext(tomcat.getHost(), initializers); return getTomcatEmbeddedServletContainer(tomcat); }
果然在host建立后进行了一个操做prepareContext(tomcat.getHost(), initializers);进入这里
protected void prepareContext(Host host, ServletContextInitializer[] initializers) { File docBase = getValidDocumentRoot(); docBase = (docBase != null ? docBase : createTempDir("tomcat-docbase")); final TomcatEmbeddedContext context = new TomcatEmbeddedContext(); context.setName(getContextPath()); context.setDisplayName(getDisplayName()); context.setPath(getContextPath()); context.setDocBase(docBase.getAbsolutePath()); context.addLifecycleListener(new FixContextListener()); context.setParentClassLoader( this.resourceLoader != null ? this.resourceLoader.getClassLoader() : ClassUtils.getDefaultClassLoader()); resetDefaultLocaleMapping(context); addLocaleMappings(context); try { context.setUseRelativeRedirects(false); } catch (NoSuchMethodError ex) { // Tomcat is < 8.0.30. Continue } SkipPatternJarScanner.apply(context, this.tldSkipPatterns); WebappLoader loader = new WebappLoader(context.getParentClassLoader()); loader.setLoaderClass(TomcatEmbeddedWebappClassLoader.class.getName()); loader.setDelegate(true); context.setLoader(loader); if (isRegisterDefaultServlet()) { addDefaultServlet(context); } if (shouldRegisterJspServlet()) { addJspServlet(context); addJasperInitializer(context); context.addLifecycleListener(new StoreMergedWebXmlListener()); } context.addLifecycleListener(new LifecycleListener() { @Override public void lifecycleEvent(LifecycleEvent event) { if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) { TomcatResources.get(context) .addResourceJars(getUrlsOfJarsWithMetaInfResources()); } } }); ServletContextInitializer[] initializersToUse = mergeInitializers(initializers); host.addChild(context); configureContext(context, initializersToUse); postProcessContext(context); }
spring boot 建立了final TomcatEmbeddedContext context = new TomcatEmbeddedContext();对象而后host.addChild(context);将他加入到了host建立后的步骤中完成了tomcat的初始化,同时也对内嵌的tomcat作了必定的修改。
探究的过程其实就是一个猜测------>寻找----->验证的过程,过程很累,但结果很爽。至于spring boot为啥要这么作?sessionid初始化不建立那么啥时候建立?为何测试环境熵池这么少,之前就没有问题吗?这些秘密仍是没有解决。生活还在继续,问题还在继续,不断探究吧。