tomcat的总结

作者: 简书徐小耳 | 来源:发表于2019-01-15 18:13 被阅读10次

    classLoader

    commonLoader:加载的jar可以提供给tomcat本身和其下面各个项目使用
    catalinaLoader:加载的jar只能提供给tomcat本身
    sharedLoader:加载的jar包可以给各个项目使用
    他们三个都是urlclassloader类型

    在bootStrap启动期间 将线程上下文的classloader设置为catalinaLoader,
    并使用catalinaLoader加载我们的catalina这个类,这就意味着这个类不会被我们项目中使用
    然后后期都是调用catalina方法的load和start方法来启动tomcat,此外我们还将catalina的parentClassLoader设置为sharedLoader
    这是为了后期设置weappclassloader的父类埋下伏笔

    相关名词解释

    catalina.home:tomcat产品的安装目录,bin 和 lib 目录被多个tomcat示例公用
    catalina.base:是tomcat启动过程中需要读取的各种配置及日志的根目录,其它目录conf、logs、temp、webapps和work 每个Tomcat实例必须拥有其自己独立的备份。
    实现在一台机器上运行多个tomcat实例的目的。
    主要就是利用catalina.base,因为它是Tomcat启动过程中读取各自配置的根目录
    java.io.tmpdir:获取操作系统缓存的临时目录,不同操作系统的缓存临时目录不一样。
    user.dir:则是获取当前tomcat容器启动的位置

    SystemLogHandler:

    startCapture方法:首先从resume(stack)中获取一个captureLog对象,如果该stack为空则new一个captureLog对象然后从logs(threadLocal)获取stack<caputure>对象,如果为空则创建一个
    stopCapture方法:从threadlocal获取stack<caputure>,然后弹出一个CaptureLog 然后写出期间的打印信息
    重置CaptureLog存入reuse。
    这个SystemLogHanlder,可以收集一段代码内的打印信息,然后统一输出且不影响性能。

    appBase和docBase的区别

     <Host name="localhost"  appBase="webapps"
                unpackWARs="true" autoDeploy="true">
    <Context path="" docBase="D:\WebContent" sessionCookiePath="/" sessionCookieName="JSESSIONID" />
    
    

    appBase:

    1.appBase是host的属性,其会自动部署appbase指定的目录的项目

    docBase

    1.docBase是context的属性,指定这个项目的路径(绝对或相对路径)
    2.访问路径我们可以通过path指定

    外置的Tomcat的启动过程:

    一、bootStrap的静态代码块和初始化:

    1.静态代码块:主要就是设置catalina.home和catalina.base,其中获取了user.dir 作为上述2个参数未设置时候的备选方案
    2.init方法:初始化三个主要的classloader,设置线程上下文的classloader为catalinaLoader生成catalina对象,并将sharedLoader设置为其parentClassLoader

    二、调用catalina的load方法

    1.初始化工作时候的temp目录-java.io.tmpdir
    2.初始化jndi相关的目录
    3.创建Digester对象,并使用该对象解析server.xml文件,最终创建Server对象
    4.设置server的属性,catalina,catalinaHome和catalinaBase
    5.优化system.out和system.error
    6.调用server的init方法

    三、调用catalina的start方法

    1.server的start方法调用
    2.如果需要使用钩子则注册一个CatalinaShutdownHook,该钩子会在触发的时候调用catalina的stop方法
    ,该方法会先删除钩子(因为有可能不是钩子线程调用的stop方法,这样会导致stop调用两次)然后调用server的stop和destory方法。
    3.然后调用await和stop方法
    4.await方法主要就是根据通过循环判断标识位来判断是否需要结束tomcat或者通过socket监听关闭端口来判断

    内置的Tomcat启动过程

    一、创建WebServer(内部包含tomcat类,所以这个webServer类似于Facade)

    1.创建tomcat类。该类主要是服务于内置或者测试的starter,包含我们基础配置信息,也是用来启动我们的server服务,并且可以添加container的类。
    2.设置tomcat的baseDir
    3.设置connector,默认协议是Http11NioProtocol类
    4.创建一个standardserver,设置catalina.home和catalina.base
    5.创建一个standardService,将connector和service互相绑定。
    6.调整connector属性,比如端口,还有就是如果我们配置了地址,则协议需要绑定该地址,还有就是给connector
    设置uriEncoding,ssl。还有是给协议设置compression。
    7.我们也可以通过实现TomcatConnectorCustomizer接口来优化connector或者其内部协议,比如修改adpter或者endpoint
    8.当然还有这个TomcatContextCustomizer接口 我们可以优化我们的context
    9.上述的两个customizer,我们可以写个BeanPostProcessor,在TomcatServletWebServerFactory初始化之后,调用其add方法添加
    10.将被自定义修改后的connector和service中的connector进行对比,如果对象被替换了 就将新的connector加入到service中否则就不用添加
    11.禁止host自动部署,在设置engine的是设置了该engine的默认的host为localhost和realm
    12.配置engine,首先设置backGroupProcessorDelay时长,这个会启动一个线程每隔一段时间执行后台任务
    13.给engine设置valve
    14.我们还可以给该service添加我们额外写的connector
    15.设置我们context=TomcatEmbeddedContext,设置name和path,displayName,指定docBase
    16.添加lifeCycleListeners和parentClassloader,设置webApploader(我们的webappclassLoader的创建者),以及webappclassLoader的类型TomcatEmbeddedWebappClassLoader
    17.将webApploader的加载模式设置为true,这就代表我们的这边webappclassLoader采用的是双亲加载模式
    18.注册DefaultServlet,将tomcatStarter和context绑定,配置context的valve,修改我们的context
    19.最终将tomcat和port包装成tomcatWebServer返回,在tomcatWebServer的构造函数中好友初始化方法
    20.该初始化方法主要是:启动tomcat的container,但是会删除connector这样就可以避免connector也被启动,启动一个后台线程每隔一段时间去检测是否关闭标识。

    二、启动webServer

    1.添加我们上述被删除的connector
    2.启动我们的connector

    tomcat启动过程就是 调用Server的init方法和start方法,所以我们着重分析这两个方法

    一、server的init:

    1.standardserver的init方法

    • 设置JMX相关类
    • 设置全局的字符串缓存
    • 初始化globalNamingResources
    • 从我们的sharedLoader中获取jar包资源时报包含MANIFEST,如果包含则添加到容器的集合中
    • 初始化service的init方法

    2.service的init方法

    • engine的初始化
    • 线程池的初始化
    • mapperlistener的初始化
    • connector的初始化

    3.engine的初始化(少了host,context,wrapper的初始化,他们的初始化都是在各自start的时候先进行进行的)

    • 创建Realm
    • 重新配置startStopExecutor,其主要是帮忙启动和关闭子容器

    4.host的初始化

    • 配置startStopExecutor

    5.context的初始化

    • 配置startStopExecutor
    • namingResources初始化

    6.wrapper的初始化

    • 配置startStopExecutor

    7.connector的初始化

    • 创建adapter并和protocolhandler绑定,其主要是用来获取httpServeletRequest和httpServeletResponse
    • 设置默认的解析bodymethod==post
    • protocolHandler的初始化

    8.protocolHandler的初始化

    • 判断是否需要升级协议
    • 创建endpoint并初始化
    • enpoint可以理解为专门负责处理socket,类似于netty的服务端或者客户端boostrap
    1. Nioendpoint的初始化-实际是调用bind方法
    • 创建socket并给socket设置相关属性,比如接受和发送的缓冲区大小,超时,拒绝连接的地址等
    • 让该socket绑定我们设置好的地址和端口,同时还设置了backlog=accpetAccount,也就是说最大可以有一定数目的
      即将要连接的该socket,超过就拒绝。这个backlog对应的accept queue
    • 设置该通道为非阻塞模式
    • 设置acceptorThreadCount(也就是acceptor线程默其专门获取socket连接)认为1,其对应我们的acceptor线程大小类似于nioeventloop,根据我们对netty的了解就算设置多了意义也不大
    • 设置pollerThreadCount最小为1,其是专门接受acceptor线程传递过来的socket,然后将该scoekt注册到selector上面
    • 初始化ssl
    • 创建共享selector,然后创建一个NioBlockingSelector去包裹该selector
    • NioBlockingSelector包含一个共享的多路复用器,BlockPoller线程
    • BlockPoller线程的作用:专门处理ssl的channel上面注册的blocking的socket,其他的非ssl裁员poller线程去注册

    二、server的start:

    1.server的start方法

    • 激活configure_start监听事件
    • 设置状态为start
    • 调用globalNamingResources的start方法
    • 调用service的start方法

    2.service的start方法

    • 设置start启动标识
    • 启动engine
    • 启动线程池
    • 启动mapperListener
    • 启动connector
    1. engine的start

    启动realm

    • 迭代启动子容器
    • 启动pipeline,设置启动标识,也就是给pipeline添加基本的vavle,并启动vavle(也就是设置启动标识)
    • 每个容器都有一个starnardpipeline,每个容器都有一个baiscvavlve--standardXXvalve,他们的主要任务是
      连接当前容器的子容器
    • 启动ContainerBackgroundProcessor线程执行后台任务(目前只有engine容器可以启动该线程,其他子类没有)
    • ContainerBackgroundProcessor的处理逻辑:对于container为context的容器 先调用其bind方法
    • 调用所有container的backgroundProcess方法,而各个container的backgroundProcess内部还包含其他组建的backgroundProcess
      比如cluster,realm,valve,Loader,Manager,WebResourceRoot,InstanceManager等
    • 而context容器的bind方法的作用就是就是确保我们的webApploader中的webAppClassLoader与线程上下文的classloader一致
      不一致的话就设置为一样的。这样的话我们热部署的时候classloader的变更 进而可以进行体会线程上下文中的classloader
    1. host的start方法
    • 配置errorValve
    • start子容器
    • start我们的pipeline
    1. context的start方法
    • 给该项目设置工作目录
    • 设置WebResourceRoot并调用其start方法 主要是将这个context中的文件比如class和配置文件变成WebResource
      留给classloader去加载
    • 创建WebappLoader,并设置其内部的WebappClassLoader的加载模式为非双亲加载模式
    • 检测context中的mainest文件,设置NamingContextListener
    • 启动WebappLoader的start方法主要就是创建WebAppClassLoader,设置加载模式 classPaths,将classes和lib下面的
      资源放入到localRepositories中
    • 设置WebAppClassLoader的属性,然后将该classloader放入到线程上下文后期只要碰到任何需要加载的类都采用我们的classloader
    • 启动子容器
    • 启动pipeline
    • 将sources,jarScanner放入到servletContext
    • 执行ServletContainerInitializer的onStarup方法
    • 启动filter和servlet
    • 将webAppClassLoader替换为原始的classloader
      -通过监听者ContextConfig的webConfig和applicationAnnotationsConfig方法加载项目 首先生成对应的web.xml文件(先从项目中寻找,没有在获取全局的) 然后获取利用spi机制去各个jar包中寻找META-INF/services/javax.servlet.ServletContainerInitializer 文件获取ServletContainerInitializer,这边使用webappclassloader去加载。 然后加载所有的/WEB-INF/classes的class,主要是处理这些class中的annotation/WebServlet ,annotation/WebFilter,annotation/WebListener。 然后在处理所有的lib目录的jar包,最后添加我们ServletContainerInitializer
    • 回收resouce中的资源
    1. wrapper的start方法
    • 启动器内部的cluster ,realm,pipeline和子容器的start方法
    • 设置available=0代表可用

    7.connector的start方法

    • 检测端口是否符合要求
    • 启动protocolHandler的start方法

    8.protocolHandler的start方法

    • 启动endpoint的start方法
    • 启动一个异步线程AsyncTimeout,每隔1秒去检测我们的异步处理的Processor是否超时,如果超时则执行超时操作
    • 该线程的优先级被设置为normal,且是后台线程
    1. Nioendpoint的start方法
    • 设置nioendpoint的启动标识
    • 设置processorCache,EventCache,nioChannels三个缓存
    • 如果我们的tomcat没有设置线程池 则我们自己创建worker线程池
    • 线程池的属性有minspareThreads,maxThreads
    • 设置maxConnection的大小默认是1万
    • 创建poller线程,数量可以设置
    • 每个poller包含独立的多路复用器,他们从acceptor线程获取socket注册到自己的多路复用器,其类似于nioeventloop
    • 开启acceptor线程

    三、tomcat接受处理请求的全流程

    -一、Acceptor线程携带endpoint,不断的循环获取socketChannel

    • 1.首先判断endPoint是否还在running,如果不是则跳出循环
    • 2.如果当前endpoint是暂停且还在running状态则现场沉睡50毫秒
    • 3.在正式获取socketChannel的时候需要先去获取connection(通过maxConnection限制)
    • 4.socketChannel是一个channel,获取的过程中如果发生io异常需要设置下errorDelay,即让主线程沉睡一会
    • 5.当我们获取到socketchannel则需要绑定到poller线程上,绑定失败或者acceptor线程暂停或者关闭 则需要关闭该通道

    -二、将socketChannel包装成Niochannel交给poller线程

    • 1.将socketChannel设置为非阻塞
    • 2.将socket的属性填充=超时,接受和发送buffer(属于socket的buffer),ReuseAddress ,tcpNoDelay等
    • 3.尝试从缓存获取niochannel,并创建了一个SocketBufferHandler,其实作为applicaiton的buffer(包含发送,接受和堆外的buffer)
    • 4.最终我们将SocketBufferHandler和socketchannel包装成niochannel注册到poller线程上
    • 5.注册过程就是讲我们的niochannel和nioendpoint包装成NioSocketWrapper
    • 6.然后给NioSocketWrapper设置poller线程,timeout,alive,ssl等属性,并注册read事件
      1. 从eventCache缓存中获取pollerEvent,并集成niochannel,NioSocketWrapper和注册事件。
    • 8.然后将该pollerEvent放入poller线程的events内部集合,如果wakeupCounter=-1 则代表当前poller线程在沉睡我们需要唤醒

    -三、Poller线程的run方法-将后续获取到的事件(链接事件)交给SocketProcessorBase处理

    • 1.从我们的events集合中循环的执行PollerEvent的run和reset方法 然后将PollerEvent放入eventCache
    • 2.run方法:第一次执行都是将我们的channel注册到该poller内部的多路复用器
    • 3.reset方法:情况pollerEvent内部对象
    • 4.设置wakeupCounter为-1 如果之前的值是大于0代表已经有其他的event加入进来 我们直接selectNow否则阻塞获取事件
    • 5.设置wakeupCounter为0 然后判断是否该线程已经关闭如果是的话则cancel 所有已经发生的SelectionKey然后关闭多路复用器再跳出循环
    • 6.否则判断是否有对应的SelectionKey或者其他的event加入
    • 7.先处理SelectionKey,然后在查看是否超时
    • 8.只有处理以下几种情况的timeout:nextExpiration已经过了,没有selectionKey或者其他event加入 或者socket已经开始clsoe
    • 9.处理selectionkey的逻辑是:如果已经close了 则直接cancel,否则检测sk是否有效且需要携带附件,只处理读写事件
    • 10.如果附件携带文件,则调用filechannel的transTo方法进行处理,其他事件则当成socket进行处理。
    • 11.在处理sk中携带文件之前需要先取消之前该channel注册的事件,等待处理完成在且是支持keepalive的则重新注册该channel否则直接关闭
    • 12.对于attacheMent是非文件的则先取消之前该channel注册的事件,并不会重新注册 因为事件已经处理完毕
    • 13.上述attacheMent是非文件是先处理read在处理write,如果处理失败就直接cancel
    • 14.然后从processorCache获取SocketProcessorBase,根据是否支持dispatch以及是否存在线程池来决定剩余的处理
      是否在poller线程中做还是交给cotainer的线程池

    -四、SocketProcessorBase的处理流程

    • 1.首先进行ssl握手
    • 2.获取对应的handler进行处理
    • 3.对应的handler获取合适的Processor
    • 4.调用Processor的process进行处理,最终交给service方法
    • 五、Processor的service方法逻辑
    • 1.获取requestIno,设置请求的stage,设置input和output的buffer
    • 2.默认是支持keepAlive,读取请求的数据
    • 3.最终通过adapter的service方法传递我们的原始request和response
    • 六、然后将在第五步得到request和response交给Adapter(CoyoteAdapter),调用其service方法处理
    • 1.首先将request和response转换成HttpServletRequest和HttpServletResponse
    • 2.获取connector 通过connector得到container的vavle,然后交给他们执行
    • 七、最终一层层调用到我们的standardWrapperValve的invoke方法
    • 1.获取对应的servlet,这边就是我们的dispatchServlet
    • 2.执行dispatchServlet的service方法,在此之前先执行过滤器
    • 3.我们的controller方法被映射成地址和我们的请求地址匹配,最终可以得到method 进而反射执行方法完成调用

    server.xml文件的各个标签的意义

    我们的engine标签会配置一个默认的host
    名称一般都是localhost,当我们用localhost去请求的时候
    我们本地的系统有hosts配置文件可以将我们的localhost解析为实际的ip
    默认都是127.0.0.1,当然我们也可以配置localhost1等127.0.0.1

    MANIFEST

    一般编写MANIFEST.MF文件只需要用到Manifest-Version(MF文件版本号)
    、Main-Class(包含main方法的类)、Class-Path(执行这个jar包时的ClassPath,第三方依赖)
    比如下面:
    Manifest-Version: 1.0
    Main-Class: test.Main
    Class-Path: ./ ./lib/commons-collections-3.2.jar ./lib/commons-dbcp-1.2.2.jar ./lib/commons-lang-2.3.jar ./lib/commons-logging-1.1.jar

    Tomcat中的backlog参数

    在linux 2.2以前,backlog大小包括了半连接状态和全连接状态两种队列大小。
    linux 2.2以后,分离为两个backlog来分别限制半连接SYN_RCVD状态的未完成连接队列大小
    跟全连接ESTABLISHED状态的已完成连接队列大小。
    当服务端接受到客户端的syn请求后放入syns的队列中,然后服务端回复syn+ack,等客户端收到ack后 再回复ack给服务端
    则服务的就把sync中的半连接放入到accpet queue。一般backlog=完全队列
    我们完全队列大小取值=min(backlog,somaxconn)
    我们半完全队列大小=
    table_entries = min(min(somaxconn,backlog),tcp_max_syn_backlog)
    roundup_pow_of_two(table_entries + 1)
    roundup_pow_of_two表示取最近的2的n次方的值,举例来说:假设somaxconn为128,backlog值为50,tcp_max_syn_backlog值为4096,则第一步计算出来的为50,
    然后roundup_pow_of_two(50 + 1),找到比51大的2的n次方的数为64,所以最终半连接队列的长度是64。

    connector容易混淆的名词解释

    acceptorThreadCount:代表从socket的完全队列获取socket连接的线程数,其socket连接交给poller
    pollerThreadCount:将该socket注册到selector上面,然后捕捉到后续的读写事件交给worker线程处理
    maxThreads:则是指worker线程--真正去处理这些socket请求的线程个数
    acceptCount: 代表acceptor线程从tcp完全队列里面取connection的限制(因为acceptorCount限制了队列大小)
    maxConnections:每当获取到一个socket就代表得到一个connection,然后该connection只有在完成或者某些异常情况下才释放
    ,从这个角度来说如果不限制connection我们不停的拿socket处理也是不行的,这就是从java层面控制

    Tomcat的reload机制

    • 1.WebappLoader的backgroundProcess方法
    • 2.首先检测是否支持热部署,其次检测项目是否有变化
    • 3.变化的依据是检测jar包和我们的其他文件的变更时间,如果不一致则将当前的线程上下文中的classloader替换为加载
      webApploader的classloader 也就是我们的sharedLoader
    • 4.然后调用standardContext的reload方法去加载
    • 5.加载完成后将线程上下文的加载器设置为webAppclassloader

    reload方法的主要逻辑

    • 设置暂停标识,可以让request请求休息1秒
    • 获取老的classloader 关闭后台线程(由于context没有后台线程所以不用关闭)
    • 获取context的子容器进行stop
    • 关闭过滤器和manager 等等
    • 然后重新调用start方法 ,设置paused为false

    相关文章

      网友评论

        本文标题:tomcat的总结

        本文链接:https://www.haomeiwen.com/subject/nnfpfqtx.html