解決問題當然重要,可是解決問題的思考過程我以爲更加值得分享,因此把本身的心路歷程記錄下來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初始化不建立那麼啥時候建立?爲何測試環境熵池這麼少,之前就沒有問題嗎?這些祕密仍是沒有解決。生活還在繼續,問題還在繼續,不斷探究吧。